The long version: what the synthesiser actually does to your Verilog, why an incomplete case turns a wire into a latch, what that latch does to static timing analysis, and how much silicon a shared adder really saves.加長版:合成器究竟對你的 Verilog 做了什麼、為什麼不完整的 case 會把一條 wire 變成鎖存器、那個鎖存器對靜態時序分析造成什麼後果,以及共用一個加法器實際上省下多少矽面積。詳細版:合成器が Verilog に対して実際に何をするのか、なぜ不完全な case が wire をラッチに変えてしまうのか、そのラッチが静的タイミング解析に何をもたらすのか、そして加算器を共有すると実際どれだけシリコン面積が減るのか。
This page is the long-form companion to the Lecture 5 study guide. The study guide states the rules; this page explains where each rule comes from, what the synthesiser does with your code when you follow it, and exactly how the failure manifests in a netlist, a lint report or a timing report when you do not. Everything here is IEEE-1364 Verilog as it is taught for cell-based ASIC design, which is the context of this course: a standard-cell library, a logic synthesiser such as Design Compiler or Genus, and a static timing analyser downstream. Where the slides state something that is true of the lecture's era but misleading for a modern tool, that is flagged rather than repeated.本頁是第 5 講學習指南的加長版伴讀。學習指南只陳述規則;本頁則解釋每條規則從何而來、當你照做時合成器會對你的程式碼做什麼,以及當你不照做時,失效會以什麼形式出現在網表、lint 報告或時序報告裡。本頁所有內容都是 IEEE-1364 Verilog,而且是以 cell-based ASIC 設計的角度來教的,這正是本課程的脈絡:一座標準元件庫、一套像 Design Compiler 或 Genus 的邏輯合成器,以及下游的一套靜態時序分析器。凡是投影片上對當年成立、但以現代工具的角度來看會誤導的說法,本頁都會特別標示,而不是照單全收。本ページは第 5 講スタディガイドの詳細版の副読本である。スタディガイドは規則を述べるだけだが、本ページは各規則がどこから来たのか、規則に従ったとき合成器がコードに何をするのか、そして従わなかったときに不具合がネットリスト・lint レポート・タイミングレポートのどこにどう現れるのかを説明する。ここで扱うのはすべて IEEE-1364 Verilog であり、しかも本講義の文脈であるセルベース ASIC 設計の立場から教えられるもの、すなわち標準セルライブラリ、Design Compiler や Genus のような論理合成器、そして下流の静的タイミング解析器を前提とする。スライドの記述が当時は正しくとも現代のツールには誤解を招く箇所は、そのまま繰り返すのではなく注意を促す。
The first slide of this lecture is a block diagram that most students glance at and forget, because it looks like an organisation chart. It is worth slowing down on, because every rule that follows in the lecture is a consequence of the shape of this diagram. Design entry produces an HDL behavioural model. That model goes two places. It goes forward into physical optimisation and implementation, and it goes sideways into design verification by functional simulation. The implemented design then comes back to design verification a second time, now by timing simulation. The diagram is a loop, not a pipeline, and the loop is the whole point: the behavioural model is simultaneously the thing you are building and the specification against which the thing you built is checked.本講的第一張投影片是一張方塊圖,多數學生看一眼就忘了,因為它長得像一張組織架構圖。但值得停下來細看,因為這一講後面的每一條規則,都是這張圖形狀的必然結果。設計輸入產生一份 HDL 行為模型。這份模型往兩個方向走:一是往前進入實體最佳化與實作,二是往旁邊進入以功能模擬進行的設計驗證。實作完成的設計接著第二次回到設計驗證,這次用的是時序模擬。這張圖是一個迴圈,不是一條管線,而這個迴圈正是重點所在:行為模型同時是你正在打造的東西,也是用來檢查你所造之物的規格書。この講義の最初のスライドはブロック図で、組織図のように見えるため多くの学生は一瞥して忘れてしまう。しかし立ち止まる価値がある。この講義で以降に登場するすべての規則は、この図の形から必然的に導かれるからだ。設計入力は HDL 行動モデルを生む。そのモデルは二方向へ進む。前方へは物理最適化と実装へ、横方向へは機能シミュレーションによる設計検証へ進む。実装された設計はもう一度設計検証へ戻り、今度はタイミングシミュレーションにかけられる。この図はパイプラインではなくループであり、そのループこそが要点である。行動モデルは、作ろうとしている対象であると同時に、作った対象を検証するための仕様書でもあるのだ。
That dual role is unusual and it is the source of most of the discipline this lecture tries to teach. In a software project the specification and the implementation are different documents written by different people. In an RTL project they are the same file. The synthesiser reads your always block and produces gates; the simulator reads the same always block and produces waveforms; and nothing in the universe guarantees that those two readings agree. They agree only if you confine yourself to a subset of the language on which the simulator's semantics and the synthesiser's semantics coincide. That subset is what the lecture calls "synthesisable", and everything in Section 2 is the definition of that subset.這種雙重身分並不尋常,而本講試圖教的紀律大多源自於此。在軟體專案裡,規格與實作是由不同的人寫的不同文件。在 RTL 專案裡,它們是同一個檔案。合成器讀你的 always 區塊而產生邏輯閘;模擬器讀同一個 always 區塊而產生波形;而世上沒有任何東西保證這兩種讀法會一致。只有當你把自己限制在一個子集合裡——在這個子集合上,模擬器的語意與合成器的語意恰好重合——它們才會一致。那個子集合就是本講所說的「可合成」,而第 2 節的全部內容,就是這個子集合的定義。この二重の役割は珍しく、本講義が教えようとする規律のほとんどはここに由来する。ソフトウェアのプロジェクトでは、仕様と実装は別々の人が書いた別々の文書である。RTL のプロジェクトでは、両者は同じファイルである。合成器はあなたの always ブロックを読んでゲートを生成し、シミュレータは同じ always ブロックを読んで波形を生成する。そしてこの二つの読み方が一致することを保証するものは何もない。一致するのは、シミュレータの意味論と合成器の意味論が重なり合う言語の部分集合に自分を限定したときだけである。その部分集合こそが本講義のいう「合成可能」であり、第 2 節の全体はその部分集合の定義にほかならない。
The bottom of the flow diagram splits into three target technologies, and the split matters because the words change meaning. For a PLD the implementation steps are partition, map and interconnect. For an FPGA or a masked gate array they are map, place and route. For standard cells they are place and route — there is no separate "map" step listed because mapping happened inside synthesis, against a cell library. This course is a standard-cell course, so when the lecture says "technology mapping" it means: choose, from a library of a few hundred physical cells with known area, delay, input capacitance and drive strength, a set of cells whose interconnection realises the optimised Boolean network. That is a covering problem, and it is solved approximately, which is why the slide's footnote — final synthesis results depend on the synthesis tool — is not a disclaimer but a statement of fact.流程圖的下半部分岔成三種目標技術,這個分岔很重要,因為同樣的詞在不同技術下意義不同。對 PLD 而言,實作步驟是分割、映射與互連。對 FPGA 或光罩式閘陣列而言,是映射、擺放與繞線。對標準元件而言,則是擺放與繞線——之所以沒有列出獨立的「映射」步驟,是因為映射已經在合成階段對著元件庫完成了。本課程是標準元件的課程,所以當本講說「技術映射」時,意思是:從一座有幾百個實體元件、每個元件都有已知面積、延遲、輸入電容與驅動能力的元件庫中,挑出一組元件,使它們的互連實現經過最佳化的布林網路。那是一個覆蓋問題,而且只能近似求解,這也是為什麼投影片的註腳——final synthesis results depend on the synthesis tool——不是免責聲明,而是在陳述事實。フロー図の下部は三つのターゲット技術へ分岐する。この分岐は重要で、分岐の先で言葉の意味が変わるからだ。PLD では実装の工程は分割・マッピング・相互接続である。FPGA やマスク型ゲートアレイではマッピング・配置・配線である。標準セルでは配置・配線だけであり、独立した「マッピング」の工程が挙がっていないのは、マッピングがセルライブラリを相手に合成の内部で済んでいるからである。本講義は標準セルの講義なので、「テクノロジマッピング」といえば次を意味する。面積・遅延・入力容量・駆動能力が既知の数百個の物理セルからなるライブラリから、その相互接続が最適化済みのブール・ネットワークを実現するようなセルの集合を選び出すこと。これは被覆問題であり、近似的にしか解けない。だからこそスライドの脚注 final synthesis results depend on the synthesis tool は免責事項ではなく事実の記述なのである。
| Target目標技術ターゲット | Back-end steps後端步驟バックエンドの工程 | What "a cell" means「一個元件」是什麼意思「セル」とは何を指すか | Latch cost鎖存器的代價ラッチのコスト |
|---|---|---|---|
| PLD / CPLDPLD / CPLDPLD / CPLD | Partition, map, interconnect分割、映射、互連分割・マッピング・相互接続 | A product term in a fixed AND-OR array固定 AND-OR 陣列中的一個乘積項固定 AND-OR アレイ中の一つの積項 | Usually free: macrocell has a flop/latch anyway通常免費:巨集單元本來就附一個正反器或鎖存器通常ただ同然:マクロセルは元々フリップフロップかラッチを持つ |
| FPGA / masked gate arrayFPGA / 光罩式閘陣列FPGA / マスク型ゲートアレイ | Map, place, route映射、擺放、繞線マッピング・配置・配線 | A lookup table plus its register一個查找表加上它的暫存器ルックアップテーブルとそのレジスタ | Often expensive: modern LUT fabrics have no latch primitive, so a latch is emulated常常很貴:現代 LUT 結構沒有鎖存器原件,所以鎖存器只能用模擬的方式湊出來高くつくことが多い:現代の LUT ファブリックにはラッチのプリミティブがなく、ラッチはエミュレートされる |
| Standard cell (this course)標準元件(本課程)標準セル(本講義) | Place, route擺放、繞線配置・配線 | A layout-ready library cell with a characterised timing arc一個可直接佈局、且時序弧已經特徵化的元件庫元件レイアウト可能でタイミングアークが特性化されたライブラリセル | Cheap in area, very expensive in verification effort面積上便宜,驗證工時上非常昂貴面積は安いが、検証の手間は非常に高くつく |
The second slide shows a test bench with two devices under test. One is the Verilog behavioural description, the other is the gate-level description produced by logic synthesis. A stimulus generator drives both; a response comparator watches both sets of outputs; a check signal fires on any mismatch. This arrangement has a name in industry — it is a gate-level regression — and the lecture is right to put it on page three, because the whole of the rest of the lecture is a list of ways to make this comparison fail.第二張投影片畫的是一個測試平台,裡面有兩個待測元件。一個是 Verilog 行為描述,另一個是邏輯合成產生的閘級描述。一個激勵產生器同時驅動兩者;一個回應比較器同時監看兩組輸出;只要有任何不一致,檢查訊號就會觸發。這種安排在業界有個名字——它叫做閘級回歸測試——本講把它放在第三頁是對的,因為這一講接下來的全部內容,就是一張「有哪些方法會讓這個比較失敗」的清單。二枚目のスライドには、二つの被測定デバイスを持つテストベンチが描かれている。一方は Verilog の行動記述、もう一方は論理合成が生成したゲートレベル記述である。刺激生成器が両者を駆動し、応答比較器が両方の出力を監視し、不一致があればチェック信号が立つ。この構成には業界での呼び名があり、ゲートレベル・リグレッションという。これを三ページ目に置いた講義の判断は正しい。以降の講義全体が、この比較を失敗させる方法の一覧だからである。
It is worth being precise about what this check does and does not prove. It proves that for the stimulus you applied, the netlist and the RTL produce the same outputs. It does not prove functional equivalence, because the stimulus is finite and the input space is not. Formal equivalence checking, which proves the stronger statement by comparing the Boolean cones of logic between corresponding registers, is the tool actually used for sign-off in a modern flow; simulation-based comparison is used to catch the things equivalence checking cannot see, such as timing violations with back-annotated delays, or an initialisation sequence that leaves the netlist in a state the RTL never reaches.值得精確地說清楚這個檢查證明了什麼、又沒有證明什麼。它證明的是:對你所施加的激勵而言,網表與 RTL 產生相同的輸出。它並沒有證明功能等價,因為激勵是有限的,而輸入空間不是。形式等價驗證(formal equivalence checking)藉由比較對應暫存器之間的布林邏輯錐,證明的是更強的命題,那才是現代流程中真正用來簽核的工具;而以模擬為基礎的比較,則用來抓等價驗證看不見的東西,例如反標註延遲後的時序違規,或是一段初始化序列讓網表停在 RTL 永遠到不了的狀態。この検査が何を証明し、何を証明しないのかは正確に述べておく価値がある。証明するのは、与えた刺激に対してネットリストと RTL が同じ出力を出すということだけである。機能的等価性は証明しない。刺激は有限だが入力空間は有限ではないからだ。対応するレジスタ間のブール論理コーンを比較することでより強い命題を証明する形式等価検証こそ、現代のフローでサインオフに実際に使われるツールである。シミュレーションによる比較は、等価検証では見えないもの、たとえば遅延をバックアノテートしたときのタイミング違反や、RTL が決して到達しない状態にネットリストを置いてしまう初期化シーケンスなどを捕まえるために使う。
The reason this matters for a combinational-logic lecture is subtle. Most of the mistakes catalogued in Sections 6 and 7 do not make the netlist wrong in a way that equivalence checking catches, because the netlist is a faithful implementation of what you wrote. The mistake is that what you wrote is not what you meant. An inferred latch is a perfectly correct implementation of an incomplete case; equivalence checking will cheerfully confirm that the latch matches the RTL. It is the simulation comparison, with realistic stimulus, that shows the output holding a stale value when you expected it to update — and even then only if your stimulus happens to walk into the uncovered branch. This is precisely why the lecture insists on the coding rules rather than relying on downstream checks: the cheapest place to catch an inferred latch is in the lint report, before a single gate exists.這件事對一堂組合邏輯的課為什麼重要,理由很微妙。第 6 節與第 7 節列出的大部分錯誤,並不會讓網表以等價驗證抓得到的方式出錯,因為網表忠實地實現了你所寫的東西。錯的是:你寫的不是你想的。被推論出來的鎖存器,是對一個不完整 case 完全正確的實現;等價驗證只會高高興興地確認這個鎖存器與 RTL 相符。真正會顯示輸出在你期待它更新時卻抱著舊值不放的,是帶有真實激勵的模擬比較——而且還得你的激勵剛好走進那個沒被涵蓋的分支才行。這正是為什麼本講堅持寫碼規則,而不是倚賴下游檢查:抓到推論鎖存器最便宜的地方是 lint 報告,在任何一個邏輯閘誕生之前。これが組合せ論理の講義にとってなぜ重要なのかは微妙な話である。第 6 節と第 7 節に並ぶ誤りの大半は、等価検証が捕まえられる形でネットリストを誤らせはしない。ネットリストは書かれたとおりを忠実に実装しているからだ。誤っているのは、書いたものが意図したものではないという点である。推論されたラッチは、不完全な case の完全に正しい実装であり、等価検証はそのラッチが RTL と一致することを嬉々として確認する。更新されるはずの出力が古い値を保持したままであることを示してくれるのは、現実的な刺激を与えたシミュレーション比較だけであり、しかも刺激がたまたま未カバーの分岐に踏み込んだ場合に限られる。講義が下流の検査に頼らずコーディング規則を強く求めるのはまさにこのためである。推論ラッチを捕まえる最も安上がりな場所は、ゲートが一つも存在しないうちの lint レポートなのだ。
The slide's one-line definition is the one to memorise: logic synthesis operates on Boolean equations and produces optimised combinational logic. The accompanying diagram unpacks that into three engines in series, with two external inputs.投影片上那句一行定義值得背下來:邏輯合成在布林方程式上運作,並產生經過最佳化的組合邏輯。隨附的圖把這句話拆成串接的三個引擎,外加兩個外部輸入。スライドの一行の定義は暗記する価値がある。論理合成はブール方程式を対象に動作し、最適化された組合せ論理を生成する。付随する図はこれを直列につながった三つのエンジンと、二つの外部入力に分解して示している。
The translation engine is a compiler front end. It parses your Verilog, elaborates parameters and generate blocks, and converts each procedural block into a set of Boolean equations over the block's inputs. This is where "inference" happens: the tool recognises patterns in your code and decides, for each variable assigned in a block, whether that variable is a wire, a latch or a flip-flop. The output of this stage is a technology-independent network, conventionally in sum-of-products form, often called a GTECH or generic netlist. It is already a circuit, but a circuit made of imaginary gates with no area and no delay.翻譯引擎就是編譯器的前端。它剖析你的 Verilog,展開參數與 generate 區塊,並把每一個程序式區塊轉換成一組以該區塊輸入為變數的布林方程式。「推論」就發生在這裡:工具辨識你程式碼中的樣式,並為區塊中每一個被賦值的變數決定,它到底是一條 wire、一個鎖存器,還是一個正反器。這個階段的輸出是一個與技術無關的網路,慣例上以積項和的形式表示,常被稱為 GTECH 網表或泛用網表。它已經是一個電路了,但這個電路是由沒有面積也沒有延遲的想像邏輯閘組成的。翻訳エンジンはコンパイラのフロントエンドである。Verilog を構文解析し、パラメータと generate ブロックを展開し、各手続きブロックをそのブロックの入力を変数とするブール方程式の集合へ変換する。「推論」が起きるのはここである。ツールはコード中のパターンを認識し、ブロック内で代入される各変数について、それが wire なのかラッチなのかフリップフロップなのかを決める。この段階の出力はテクノロジ非依存のネットワークで、慣例的に積和形で表され、しばしば GTECH ネットリストあるいは汎用ネットリストと呼ばれる。すでに回路ではあるが、面積も遅延も持たない想像上のゲートでできた回路である。
The optimisation engine restructures that network. It does not know what cells exist; it works on literal counts and logic depth as proxies for area and delay. The five multi-level operations in Section 3 are its vocabulary. It is also the stage that exploits don't-cares, which is why Section 10's 4'bx trick produces a visibly smaller circuit.最佳化引擎重新架構這個網路。它並不知道有哪些元件存在;它用文字數(literal count)與邏輯深度來當作面積與延遲的代理指標。第 3 節的五個多層操作就是它的詞彙。它也是利用不在意項的那個階段,這就是為什麼第 10 節的 4'bx 手法會產生一個肉眼可見地更小的電路。最適化エンジンはそのネットワークを再構成する。どんなセルが存在するかは知らず、面積と遅延の代理としてリテラル数と論理段数の上で働く。第 3 節の五つの多段操作がその語彙である。ドントケアを活用するのもこの段階であり、第 10 節の 4'bx の技が目に見えて小さい回路を生むのはそのためである。
The mapping engine finally covers the optimised network with real cells. It is guided by the technology library, which for each cell gives area in square microns or in grid units, and a delay model — in a modern library, a two-dimensional non-linear table indexed by input transition time and output load capacitance. Mapping is where the constraint you supplied (a clock period, a maximum area, a maximum transition time) first bites, because it is the first stage that can compute a number comparable with the constraint.映射引擎最後用真實的元件去覆蓋最佳化後的網路。引導它的是技術元件庫,庫中每個元件都給出以平方微米或格點單位表示的面積,以及一個延遲模型——在現代的元件庫裡,那是一張以輸入轉態時間與輸出負載電容為索引的二維非線性表格。你所提供的約束(一個時脈週期、一個面積上限、一個轉態時間上限)第一次真正咬人就是在映射階段,因為這是第一個能算出可與約束相比較之數字的階段。マッピングエンジンが最後に、最適化されたネットワークを実在のセルで被覆する。これを導くのはテクノロジライブラリで、各セルについて平方マイクロメートルまたはグリッド単位の面積と、遅延モデルを与える。現代のライブラリでは、遅延モデルは入力遷移時間と出力負荷容量を索引とする二次元の非線形テーブルである。与えた制約(クロック周期、面積の上限、遷移時間の上限)が最初に効いてくるのはマッピングの段階である。制約と比較できる数値を計算できる最初の段階だからだ。
If your design is too slow, the question "which engine failed?" has very different answers. If translation inferred a latch, no amount of optimisation effort will help: you must fix the RTL. If optimisation left a 14-level logic cone, restructuring the RTL (pipelining, precomputation, resource sharing) is the cure. If mapping chose low-drive cells on a long net, the cure is in the constraints and the floorplan, not the RTL. Most wasted engineering time in an RTL course comes from applying a mapping-level fix to a translation-level bug.如果你的設計太慢,「是哪個引擎出問題?」這個問題的答案差異極大。如果是翻譯階段推論出了一個鎖存器,再多的最佳化努力都沒有用:你必須修 RTL。如果是最佳化階段留下一個十四層的邏輯錐,那麼重構 RTL(插管線、預先計算、資源共用)才是解方。如果是映射階段在一條長的連線上挑了低驅動力的元件,解方在約束與樓面規劃裡,不在 RTL 裡。在一門 RTL 課程中,浪費掉的工程時間大多來自:用映射層級的修法去對付翻譯層級的錯誤。設計が遅すぎるとき、「どのエンジンが失敗したのか」という問いの答えはまったく異なる。翻訳がラッチを推論したのなら、最適化の努力をいくら積んでも無駄で、RTL を直すしかない。最適化が 14 段の論理コーンを残したのなら、RTL の再構成(パイプライン化・事前計算・リソース共有)が治療法である。マッピングが長い配線に駆動力の弱いセルを選んだのなら、治療法は制約とフロアプランにあり、RTL にはない。RTL の講義で無駄になるエンジニアリング時間の大半は、翻訳レベルのバグにマッピングレベルの対処を当てることから生じる。
Three negatives are worth stating explicitly, because the lecture assumes them without saying them.有三件「不是」值得明說,因為本講預設了它們卻沒有講出來。明示する価値のある三つの否定がある。講義はこれらを前提にしながら口に出さないからだ。
Synthesis is not a compiler in the sense that a C compiler is. A C compiler is allowed to assume a single thread of control stepping through statements. A synthesiser is producing a structure that has no thread of control at all; every gate is live simultaneously. The sequencing you see in an always block is a description device, not an execution model. When you write y = a; y = b; inside a combinational block, the synthesiser does not build two assignments; it notices that the first value is dead and builds only the second. That dead-value elimination is exactly what makes the "default assignment at the top of the block" cure in Section 6.5 free of cost.合成不是 C 編譯器那種意義下的編譯器。C 編譯器可以假設有一條控制流一步步走過各個敘述。合成器產生的是一個根本沒有控制流的結構;每一個邏輯閘都同時活著。你在 always 區塊裡看到的順序是一種描述手段,不是執行模型。當你在一個組合邏輯區塊裡寫 y = a; y = b; 時,合成器不會造出兩個賦值;它注意到第一個值是死的,於是只造出第二個。這種死值消去正是第 6.5 節「在區塊頂端放一個預設賦值」的解法之所以不花半點代價的原因。合成は、C コンパイラのような意味でのコンパイラではない。C コンパイラは文を順に辿る単一の制御の流れを前提にしてよい。合成器が生むのは制御の流れをまったく持たない構造であり、すべてのゲートが同時に生きている。always ブロックに見える順序は記述のための道具であって、実行モデルではない。組合せブロックの中で y = a; y = b; と書いたとき、合成器は二つの代入を作らない。最初の値が死んでいることに気づき、二つ目だけを作る。この死値除去こそ、第 6.5 節の「ブロック冒頭でデフォルト代入をする」対処がまったくコストを伴わない理由である。
Synthesis is not a timing-accurate model of your code. Delays you write (#5) are ignored, sometimes silently. This is the single most common source of the pathological situation where RTL simulation passes and gate simulation fails: a testbench-like delay crept into the design code and the simulator honoured it while the synthesiser discarded it.合成不是你程式碼的時序精確模型。你寫的延遲(#5)會被忽略,有時候還是無聲無息地忽略。這是「RTL 模擬通過、閘級模擬失敗」這種病態狀況最常見的單一來源:一個像測試平台裡才會有的延遲混進了設計碼,模擬器尊重它,合成器丟掉它。合成はコードのタイミング精密なモデルでもない。書いた遅延(#5)は無視され、しかも黙って無視されることがある。RTL シミュレーションは通るのにゲートシミュレーションが落ちるという病的な状況の、最も多い単一の原因がこれである。テストベンチに属すべき遅延が設計コードに紛れ込み、シミュレータはそれを尊重し、合成器は捨てたのだ。
Synthesis does not preserve your module hierarchy or your signal names unless you ask it to. By default a tool will flatten small modules into their parents and rename internal nets. If you are hunting for a latch in a netlist and cannot find a signal called alu_reg, that is why. The netlist name of an inferred latch is derived from the library cell, not from your variable: you will see instance names like alu_reg_reg[3] driving a cell type like LATQ_X1 or, in the slide's older library, latrnb_a.除非你特別要求,否則合成不會保留你的模組階層或你的訊號名稱。工具預設會把小模組攤平進它的母模組,並把內部連線改名。如果你正在網表裡追捕一個鎖存器,卻找不到一條叫 alu_reg 的訊號,原因就在這裡。被推論出來的鎖存器,它在網表裡的名字是從元件庫元件衍生的,不是從你的變數來的:你會看到像 alu_reg_reg[3] 這樣的實體名稱,驅動著一個 LATQ_X1 型別的元件,或者在投影片那座比較老的元件庫裡,叫 latrnb_a。明示的に頼まない限り、合成はモジュール階層も信号名も保存しない。既定ではツールは小さなモジュールを親に平坦化し、内部ネットを改名する。ネットリストの中でラッチを探しているのに alu_reg という信号が見つからないのはこのためである。推論されたラッチのネットリスト上の名前は、変数名ではなくライブラリセルに由来する。alu_reg_reg[3] のようなインスタンス名が LATQ_X1 のようなセル型を、あるいはスライドの古いライブラリでは latrnb_a を駆動しているのが見えるはずだ。
Problem: For each symptom, say which of the three engines (or none of them) is the right place to look, and what you would change.題目:對以下每一個症狀,說出三個引擎中哪一個(或都不是)才是該去查的地方,以及你會改什麼。問題:次の各症状について、三つのエンジンのどれを(あるいはどれでもないことを)調べるべきか、そして何を変更するかを答えよ。
#2 inside a design module. Gate simulation shows glitches that RTL simulation did not.RTL 的某個設計模組裡含有 #2。閘級模擬出現了 RTL 模擬沒有出現的毛刺。RTL の設計モジュール内に #2 が含まれている。ゲートシミュレーションでは RTL シミュレーションに出なかったグリッチが現れる。always block.關鍵路徑報告顯示,穿過單一個 always 區塊就有十八層邏輯。クリティカルパスのレポートが、単一の always ブロックを通る 18 段の論理を挙げている。1. Translation. The netlist contains sequential elements the RTL did not intend — an inferred latch or flop. Fix the RTL (Section 6), do not touch constraints.1. 翻譯。網表中含有 RTL 並未打算要的序向元件——一個被推論出來的鎖存器或正反器。去修 RTL(第 6 節),不要動約束。1. 翻訳。ネットリストに RTL が意図しなかった順序素子、すなわち推論されたラッチかフリップフロップが入っている。RTL を直すこと(第 6 節)。制約には触らない。
2. Mapping. Inverter and buffer trees are inserted by the mapper to fix transition time and fanout. This is normal when drive-strength constraints are tight; relax max_transition, or accept it. It is not an RTL problem.2. 映射。反相器與緩衝器樹是映射器為了修正轉態時間與扇出而插入的。當驅動力約束很緊時這是正常現象;放寬 max_transition,或者就接受它。這不是 RTL 的問題。2. マッピング。インバータとバッファのツリーは、遷移時間とファンアウトを整えるためにマッパが挿入したものである。駆動力の制約が厳しければ正常な現象で、max_transition を緩めるか、そのまま受け入れればよい。RTL の問題ではない。
3. None of them — this is a modelling bug in your source. The synthesiser discarded the #2; the RTL simulator honoured it and in doing so filtered a glitch that the real hardware will produce. Remove delays from design code entirely; they belong only in testbenches (see Lecture 2 and Lecture 3 on delay modelling).3. 都不是——這是你原始碼中的建模錯誤。合成器丟掉了 #2;RTL 模擬器尊重了它,而在尊重它的同時濾掉了真實硬體會產生的一個毛刺。把延遲從設計碼中徹底拿掉;它們只屬於測試平台(關於延遲建模見第 2 講與第 3 講)。3. どれでもない。これはソースコード側のモデリングのバグである。合成器は #2 を捨て、RTL シミュレータはそれを尊重し、その結果、実ハードウェアでは実際に生じるグリッチを濾し取ってしまった。遅延は設計コードから完全に排除すること。遅延はテストベンチだけのものである(遅延モデリングは第 2 講と第 3 講を参照)。
4. Optimisation, but the real fix is architectural. Eighteen levels usually means a long dependency chain written as a loop or a nested conditional that cannot be flattened. Restructure the RTL — balance the expression tree, precompute, or pipeline. Raising the optimisation effort rarely moves a number this large.4. 最佳化,但真正的解方是架構層級的。十八層通常意味著一條很長的相依鏈,被寫成一個迴圈或一串無法攤平的巢狀條件。重構 RTL——把運算式樹配平、預先計算,或插入管線。把最佳化努力度調高,很少能挪動這麼大的數字。4. 最適化。ただし本当の対処はアーキテクチャにある。18 段はたいてい、ループや平坦化できない入れ子条件として書かれた長い依存チェーンを意味する。RTL を再構成すること。式の木をバランスさせる、事前計算する、あるいはパイプライン化する。最適化の努力度を上げても、これほど大きな数字はめったに動かない。
The lecture gives two long lists, one headed "commonly supported" and one headed "not generally supported". Memorising two lists is a poor use of a graduate student's memory. There is one principle that generates almost all of both lists, and once you have it you can classify a construct you have never seen before.本講給了兩張長長的清單,一張標題是「通常支援」,另一張是「一般不支援」。背兩張清單,對研究生的記憶力是很糟的用法。其實有一條原則幾乎能生成這兩張清單的全部內容,而一旦你掌握它,就能把你從沒見過的構件也分類出來。講義は「一般にサポートされる」と「一般にサポートされない」という二つの長い一覧を示す。二つの一覧を丸暗記するのは大学院生の記憶力の使い道として拙い。両方の一覧をほぼすべて生成できる原則が一つあり、それを握れば見たこともない構文要素でも分類できる。
A construct is synthesisable if and only if its meaning can be fixed at elaboration time as a function from input values to output values, with no reference to simulated time, to simulation-only values (x and z as observable quantities), or to the order in which independent processes happen to be scheduled.一個構件是可合成的,若且唯若它的意義能夠在展開(elaboration)時被固定成一個從輸入值到輸出值的函數,且完全不涉及模擬時間、不涉及只有模擬才有的值(把 x 與 z 當成可觀測量),也不涉及各自獨立的行程碰巧被排程的先後順序。ある構文要素が合成可能であるのは、その意味がエラボレーション時に入力値から出力値への関数として確定でき、シミュレーション時間にも、シミュレーション専用の値(観測可能な量としての x と z)にも、独立したプロセスがたまたまスケジュールされる順序にも依存しない場合、かつその場合に限る。
Run the three clauses over the supported list and watch them account for it. Module declarations, port modes, parameters, port binding by name or position — all pure structure, fixed at elaboration. Nets (wire, tri, wand, wor, supply0, supply1) and registers (reg, integer) are storage for values, with the caveat that wand and wor correspond to wired-AND and wired-OR structures that only some technologies provide. Integer literals in any radix, scalar and vector nets, subranges on the right-hand side of an assignment — again, value plumbing with a fixed shape. Continuous assignments are Boolean equations by definition. Shift, conditional, concatenation, arithmetic, bitwise, reduction, logical and relational operators all have direct combinational realisations: a shift by a constant is a rewiring, a shift by a variable is a barrel shifter, a + is an adder, a < is a magnitude comparator.把這三個子句拿去跑一遍支援清單,看它們如何逐一交代過去。模組宣告、埠的方向、參數、依名稱或依位置的埠綁定——全是純結構,在展開時就固定了。網路型別(wire、tri、wand、wor、supply0、supply1)與暫存器型別(reg、integer)是值的儲存處,但要注意 wand 與 wor 對應的是線接 AND 與線接 OR 結構,只有某些技術提供。任何進位制的整數字面值、純量與向量網路、賦值右手邊的子範圍——同樣都是形狀固定的值的管路。連續賦值依定義就是布林方程式。位移、條件、串接、算術、位元、歸約、邏輯與關係運算子全都有直接的組合邏輯實現:位移常數是重新接線,位移變數是桶式移位器,+ 是加法器,< 是大小比較器。この三つの節をサポート一覧に当てはめ、どう説明がつくか見てみよう。モジュール宣言、ポートの方向、パラメータ、名前または位置によるポート結合——すべて純粋な構造であり、エラボレーション時に確定する。ネット(wire、tri、wand、wor、supply0、supply1)とレジスタ(reg、integer)は値の格納場所である。ただし wand と wor はワイヤード AND・ワイヤード OR の構造に対応し、それを備える技術は限られる。任意の基数の整数リテラル、スカラおよびベクタのネット、代入の右辺の部分範囲——これも形の決まった値の配管にすぎない。連続代入は定義からしてブール方程式である。シフト・条件・連接・算術・ビット単位・リダクション・論理・関係の各演算子はいずれも直接の組合せ回路実現を持つ。定数シフトは配線の付け替え、変数シフトはバレルシフタ、+ は加算器、< は大小比較器である。
The procedural entries are where the principle starts doing real work. begin ... end is just grouping. if, if/else, if/else if, and case, casex, casez with default are selection, which becomes a multiplexer or a priority chain depending on whether the conditions overlap — that is the whole of Section 8. for loops are supported because the tool unrolls them, which requires the bounds to be constant at elaboration time; a loop whose bound depends on a signal value has no fixed shape and is therefore not synthesisable, which is why "data-dependent loops" appears on the forbidden list. disable of the enclosing procedural block is supported because, after unrolling, it is equivalent to a cascade of conditions guarding the remaining statements — the slide's and4_behav and comparator examples in Section 4.5 rely on exactly this. Tasks and functions are supported because they are, for synthesis purposes, macro expansion with argument binding; the asterisk on the slide's task ... endtask entry says "no timing or event control allowed", which is the principle's first clause again.程序式的那些條目,才是這條原則真正開始出力的地方。begin ... end 只是分組。if、if/else、if/else if,以及帶 default 的 case、casex、casez 都是選擇,而它會變成多工器還是優先權鏈,取決於條件是否重疊——第 8 節講的就是這整件事。for 迴圈之所以被支援,是因為工具會把它展開,而展開要求迴圈界限在展開時就是常數;界限取決於訊號值的迴圈沒有固定形狀,因此不可合成,這也是「資料相依迴圈」出現在禁止清單上的原因。disable 所在的那個程序式區塊之所以被支援,是因為展開之後,它等價於一串條件把後續敘述一層層擋住——第 4.5 節投影片上的 and4_behav 與 comparator 例子靠的正是這一點。task 與 function 之所以被支援,是因為就合成而言,它們不過是帶引數綁定的巨集展開;投影片 task ... endtask 那一條上的星號寫著「不得含時序或事件控制」,那又是這條原則的第一個子句。原則が本当に働き始めるのは手続き系の項目である。begin ... end は単なるグループ化。if、if/else、if/else if、および default 付きの case・casex・casez は選択であり、条件が重なるかどうかによってマルチプレクサにも優先順位チェーンにもなる。第 8 節はまるごとその話である。for ループがサポートされるのはツールがこれを展開するからで、展開には境界がエラボレーション時に定数であることが要る。境界が信号値に依存するループは形が定まらず、したがって合成できない。「データ依存ループ」が禁止リストに載るのはこのためである。囲っている手続きブロックの disable がサポートされるのは、展開後にはそれが残りの文を守る条件のカスケードと等価になるからで、第 4.5 節のスライドにある and4_behav と comparator の例はまさにこれに依拠している。task と function がサポートされるのは、合成の立場からは引数結合つきのマクロ展開にすぎないからである。スライドの task ... endtask の項に付いたアスタリスクは「タイミングやイベント制御は不可」と述べており、これは原則の第一節の言い換えである。
One entry on the supported list deserves suspicion: procedural continuous assignment, assign ... deassign. It appears in the lecture's "simulation efficient" mux of Section 5.2. It is listed as supported because the tools of that era accepted it and produced a mux. Modern tools frequently reject it or warn heavily, and the construct was deprecated in later standards. Treat it as something to recognise in an exam question and never to write.支援清單上有一個條目值得懷疑:程序式連續賦值 assign ... deassign。它出現在第 5.2 節那個本講稱為「模擬效率佳」的多工器裡。它被列為支援,是因為那個年代的工具接受它並產生一個多工器。現代工具經常拒絕它或大聲警告,而這個構件在後來的標準中已被廢止。把它當成考題裡要認得、但永遠不要寫出來的東西。サポート一覧の中で疑ってかかるべき項目が一つある。手続き的連続代入 assign ... deassign である。これは第 5.2 節の講義でいう「シミュレーション効率のよい」マルチプレクサに登場する。サポートされると書かれているのは、当時のツールがこれを受け付けてマルチプレクサを生成したからだ。現代のツールはこれを拒否するか強く警告することが多く、この構文は後の規格で廃止された。試験問題で見分けられればよく、自分では決して書かないものと心得よ。
Every row below carries the not synthesisable badge in spirit; the badge is used throughout this page to mark a construct you must keep out of design code.下表每一列在精神上都掛著 not synthesisable 徽章;本頁全篇都用這個徽章標示那些你必須擋在設計碼之外的構件。下表の各行は、精神としては not synthesisable のバッジを掲げている。このバッジは本ページ全体で、設計コードから締め出さねばならない構文要素を示すために使う。
| Violated clause違反的子句違反する節 | Constructs構件構文要素 | Why there is no hardware for it為什麼沒有對應的硬體なぜ対応する回路が存在しないか |
|---|---|---|
| References simulated time no hw涉及模擬時間 no hwシミュレーション時間を参照する no hw | #delay and delay specifications, intra-assignment timing control, wait, $time, specify ... endspecify, procedural loops with timing control, tasks with timing control, PCA containing event or delay control#delay 與延遲規格、賦值內時序控制、wait、$time、specify ... endspecify、含時序控制的程序式迴圈、含時序控制的 task、含事件或延遲控制的 PCA#delay と遅延指定、代入内タイミング制御、wait、$time、specify ... endspecify、タイミング制御を含む手続きループ、タイミング制御を含む task、イベントまたは遅延制御を含む PCA | Gate delay is a property of the cell and the parasitics, chosen by the mapper and the router, not something RTL can request. A number you write is at best ignored and at worst creates a simulation/synthesis mismatch.閘延遲是元件與寄生效應的性質,由映射器與繞線器決定,不是 RTL 可以要求的東西。你寫下的數字,好一點是被忽略,壞一點是製造出模擬與合成的不匹配。ゲート遅延はセルと寄生の性質であり、マッパとルータが決めるもので、RTL が要求できるものではない。書き込んだ数値はよくて無視され、悪ければシミュレーションと合成の不一致を生む。 |
References x or z as a value no hw把 x 或 z 當成值來參照 no hwx や z を値として参照する no hw | Case equality === and !==, strength specifications weak0/1, strong0/1, pull0/1, small/medium/large, pullup/pulldown, force ... release全等比較 === 與 !==、強度規格 weak0/1、strong0/1、pull0/1、small/medium/large、pullup/pulldown、force ... releaseケース等価 === と !==、強度指定 weak0/1、strong0/1、pull0/1、small/medium/large、pullup/pulldown、force ... release | A real wire carries a voltage; there is no gate that answers the question "is this node unknown?". === is a simulator primitive. Strengths model a resolution algorithm inside the simulator's net solver, not a circuit.真實的導線上載的是電壓;世上沒有哪個邏輯閘能回答「這個節點是不是未知?」這個問題。=== 是模擬器的原件。強度所建模的是模擬器網路求解器內部的一套仲裁演算法,不是一個電路。実在する配線が運ぶのは電圧であり、「このノードは不定か」という問いに答えるゲートは存在しない。=== はシミュレータのプリミティブである。強度がモデル化しているのはシミュレータのネット解決器の中の調停アルゴリズムであって、回路ではない。 |
| Depends on scheduling order no hw取決於排程順序 no hwスケジューリング順序に依存する no hw | initial, event declarations and -> triggers, fork ... join, external disable, global variables, defparaminitial、event 宣告與 -> 觸發、fork ... join、外部 disable、全域變數、defparaminitial、event 宣言と -> トリガ、fork ... join、外部からの disable、グローバル変数、defparam | Hardware powers up in a state determined by physics and by your reset network, not by an initial block. Parallel blocks and named events are simulation process-control devices with no structural meaning.硬體上電後所處的狀態是由物理與你的重置網路決定的,不是由一個 initial 區塊決定的。平行區塊與具名事件是模擬的行程控制手段,沒有結構上的意義。ハードウェアが電源投入時に取る状態は、物理と自分で組んだリセット網が決めるものであり、initial ブロックが決めるものではない。並列ブロックと名前付きイベントはシミュレーションのプロセス制御の道具であって、構造的な意味を持たない。 |
| Has no fixed shape no hw沒有固定形狀 no hw形が定まらない no hw | forever, while, repeat, data-dependent loops, a variable used as a bit select on the left-hand sideforever、while、repeat、資料相依的迴圈、在賦值左手邊用變數當作位元選擇forever、while、repeat、データ依存のループ、左辺のビット選択に変数を使うこと | A synthesiser must emit a finite, fixed structure. An unbounded loop has none. A variable LHS bit select is a decoder-plus-write-enable pattern that older tools simply did not implement.合成器必須吐出一個有限、固定的結構。無界迴圈沒有這種結構。左手邊的可變位元選擇是一個「解碼器加寫入致能」的樣式,老一輩的工具根本沒有實作它。合成器は有限で固定された構造を出力しなければならない。無限ループにはそれがない。左辺の可変ビット選択は「デコーダ+書き込みイネーブル」のパターンであり、古いツールはそもそも実装していなかった。 |
| Switch-level modelling no hw開關級建模 no hwスイッチレベルのモデリング no hw | cmos, rcmos, nmos, pmos, rnmos, rpmos, tran, tranif0, tranif1, rtran, rtranif0, rtranif1, scalared, vectored | These model transistors and bidirectional pass structures. A cell-based flow builds from characterised cells, not transistors; the mapper has no way to place a bare pass gate.這些建模的是電晶體與雙向傳輸結構。cell-based 流程是從特徵化過的元件疊起來的,不是從電晶體疊起來的;映射器沒有辦法擺放一個裸的傳輸閘。これらはトランジスタと双方向パス構造をモデル化するものである。セルベースのフローは特性化されたセルから組み上げるのであってトランジスタからではなく、マッパには裸のパスゲートを配置する手段がない。 |
| Holds state in a primitive no hw在原件內部保存狀態 no hwプリミティブ内部に状態を持つ no hw | Sequential UDPs — primitive ... table ... endtable containing state or edge entries序向 UDP——含有狀態或邊緣條目的 primitive ... table ... endtable順序 UDP——状態またはエッジのエントリを含む primitive ... table ... endtable | Combinational UDPs are a truth table and map fine (Section 4.2). A sequential UDP encodes an arbitrary state-holding automaton with its own notion of previous value; the tool has no template to match it against.組合邏輯 UDP 就是一張真值表,映射起來沒有問題(第 4.2 節)。序向 UDP 編碼的是一台帶有自己「前一個值」概念的任意狀態機;工具手上沒有可以比對的範本。組合せ論理 UDP は真理値表そのものであり、問題なくマッピングできる(第 4.2 節)。順序 UDP は独自の「前の値」の概念を持つ任意の状態保持オートマトンを符号化しており、ツールには照合すべきテンプレートがない。 |
| Is a simulator service ignored是模擬器提供的服務 ignoredシミュレータのサービスである ignored | Any $keyword system task — $display, $monitor, $readmemh, $finish任何 $keyword 系統任務——$display、$monitor、$readmemh、$finishあらゆる $keyword システムタスク——$display、$monitor、$readmemh、$finish | These are calls into the simulator. Tools usually ignore them with a warning rather than erroring, which is why stray $display calls survive in RTL for years.這些是對模擬器的呼叫。工具通常會帶著警告忽略它們,而不是報錯,這就是為什麼散落的 $display 呼叫能在 RTL 裡存活好幾年。これらはシミュレータへの呼び出しである。ツールはエラーにせず警告つきで無視することが多く、だからこそ野放しの $display 呼び出しが RTL の中で何年も生き延びる。 |
Two items on that list are worth a second look because students meet them constantly. The first is ===. Its attraction is obvious: you want to test "is this bus still unknown?" and == will not do it, because x == 1'b0 evaluates to x, not to false. But the hardware question is meaningless. Every physical node has a voltage. If you find yourself wanting === in design code, what you actually want is a valid bit — an explicit signal that says whether the data is meaningful — which is precisely the role of valid_data in the priority encoder of Section 8.3. That is the design pattern the lecture is quietly teaching by including that output.清單上有兩個項目值得再看一眼,因為學生不斷遇到它們。第一個是 ===。它的吸引力很明顯:你想測試「這條匯流排還是未知的嗎?」,而 == 辦不到,因為 x == 1'b0 的結果是 x,不是 false。但這個問題在硬體上沒有意義。每一個實體節點都有電壓。如果你發現自己在設計碼裡想用 ===,你真正想要的是一個 valid 位元——一條明確說明資料是否有意義的訊號——那正是第 8.3 節優先權編碼器裡 valid_data 扮演的角色。本講把那個輸出放進來,其實就是在默默教這個設計樣式。この一覧の中で、学生が絶えず出会う二つの項目はもう一度見ておく価値がある。一つ目は === である。魅力は明らかで、「このバスはまだ不定か」を調べたいのに == では足りない。x == 1'b0 は false ではなく x になるからだ。しかしハードウェアとしてはこの問いに意味がない。物理的なノードにはすべて電圧がある。設計コードで === が欲しくなったとき、本当に欲しいのは valid ビット、すなわちデータが有意かどうかを明示する信号であり、それこそが第 8.3 節のプライオリティエンコーダにおける valid_data の役割である。あの出力を含めることで、講義は静かにこの設計パターンを教えている。
The second is initial. In an FPGA flow initial often does synthesise, because the configuration bitstream can set a LUT's register at load time. In a standard-cell ASIC flow it does not, because there is no mechanism to put a flop into a known state except a reset input tied to a reset network. Students who learned Verilog on an FPGA board carry this habit into an ASIC course and are surprised when a register comes up unknown. The lecture's list is the ASIC list; believe it.第二個是 initial。在 FPGA 流程裡 initial 常常真的能合成,因為組態位元流可以在載入時設定 LUT 的暫存器。在標準元件 ASIC 流程裡則不行,因為除了接到重置網路的重置輸入之外,沒有任何機制能把一個正反器放進已知狀態。在 FPGA 板子上學會 Verilog 的學生把這個習慣帶進 ASIC 課程,然後對暫存器上電後是未知值感到驚訝。本講的清單是 ASIC 的清單;請相信它。二つ目は initial である。FPGA のフローでは initial が実際に合成されることが多い。コンフィギュレーションのビットストリームがロード時に LUT のレジスタを設定できるからだ。標準セル ASIC のフローではそうはいかない。リセット網につながったリセット入力以外に、フリップフロップを既知の状態に置く手段がないからである。FPGA ボードで Verilog を覚えた学生はこの習慣を ASIC の講義に持ち込み、レジスタが不定で立ち上がることに驚く。本講義の一覧は ASIC の一覧である。信じてよい。
The lecture's two lists reflect synthesis tools of the mid-1990s and should be read as the examinable answer for this course, not as a current tool manual. Modern synthesisers accept while and repeat with statically bounded iteration counts, accept a variable bit select on the left-hand side (building the decoder for you), and accept $display by ignoring it. They are also stricter in places: many now error on procedural continuous assignment, which the slide lists as supported. The safe rule for your own code is to write only what is on the supported list and to never rely on a tool being generous.本講的兩張清單反映的是 1990 年代中期的合成工具,應該把它們讀成本課程可考的標準答案,而不是現行的工具手冊。現代合成器接受迭代次數靜態有界的 while 與 repeat,接受左手邊的可變位元選擇(並替你造出解碼器),也接受 $display(忽略它)。它們在某些地方反而更嚴格:許多工具現在會對程序式連續賦值報錯,而投影片把它列為支援。對你自己的程式碼而言,安全的規則是:只寫支援清單上的東西,而且永遠不要倚賴工具的寬容。講義の二つの一覧は 1990 年代半ばの合成ツールを反映しており、本講義で問われる答えとして読むべきもので、現行のツールマニュアルではない。現代の合成器は反復回数が静的に有界な while と repeat を受け付け、左辺の可変ビット選択も(デコーダを自動生成して)受け付け、$display も無視することで受け付ける。一方でより厳しくなった点もある。スライドがサポートと記す手続き的連続代入を、いまやエラーにするツールは多い。自分のコードに対する安全な規則は、サポート一覧にあるものだけを書き、ツールの寛容さに決して頼らないことである。
Knowing the lists is examinable; knowing what the tool prints when you break them is what saves you an afternoon. There are three distinct failure modes and they look nothing alike.知道這些清單是考得出來的;知道你違反它們時工具會印出什麼,才是替你省下一個下午的東西。失效模式有三種,彼此長得完全不一樣。一覧を知っていることは試験に出る。だが午後を一つ節約してくれるのは、それを破ったときツールが何を出力するかを知っていることである。失敗の形は三つあり、互いにまったく似ていない。
A hard error. The tool refuses to elaborate. This is what you get for fork ... join, for a while loop with a non-constant bound, or for a sequential UDP. The message names the file and line. This is the best outcome, because it is impossible to ignore.硬錯誤。工具拒絕展開。fork ... join、界限非常數的 while 迴圈,或序向 UDP 都會得到這個結果。訊息會指出檔名與行號。這是最好的結果,因為你不可能忽略它。ハードエラー。ツールがエラボレーションを拒否する。fork ... join、境界が定数でない while ループ、順序 UDP などがこれに当たる。メッセージはファイル名と行番号を示す。無視しようがないので、これが最良の結末である。
A warning plus silent removal. This is what you get for delays and for most system tasks. Design Compiler emits something in the family of "Delays are ignored for synthesis" and carries on. Your netlist is now a different circuit from your RTL in a way nothing downstream will flag, because formal equivalence checking also ignores delays. The only thing that catches this is a human reading the log, which is why mature projects treat the synthesis log as a build artefact and fail the build on unexpected warning categories.警告加上無聲移除。延遲與大部分系統任務會得到這個結果。Design Compiler 會吐出類似「Delays are ignored for synthesis」這一類的訊息,然後繼續跑下去。此刻你的網表已經是一個與你的 RTL 不同的電路,而且下游不會有任何東西標示出來,因為形式等價驗證同樣忽略延遲。唯一能抓到這件事的,是一個讀 log 的人,這就是為什麼成熟的專案把合成 log 當成建置產物,並在出現非預期的警告類別時讓建置失敗。警告つきの暗黙の削除。遅延と大半のシステムタスクがこれに当たる。Design Compiler は「Delays are ignored for synthesis」の類のメッセージを出して先へ進む。この時点でネットリストはすでに RTL とは別の回路になっているが、下流の誰もそれを指摘しない。形式等価検証も遅延を無視するからだ。これを捕まえられるのはログを読む人間だけであり、だからこそ成熟したプロジェクトは合成ログをビルド成果物として扱い、想定外の警告カテゴリが出たらビルドを落とす。
A warning plus an unintended structure. This is the dangerous one, and it is the subject of Sections 6 and 7. The tool does not fail and does not remove anything; it builds something. An incomplete case produces a latch and a message of the form Warning: ... is being inferred as a latch, or in Design Compiler's dialect an entry in the "Inferred memory devices" table in the elaboration report, with a column headed Latch containing a tick. A self-assignment produces a combinational loop and a message about a feedback path that cannot be analysed. In both cases the design proceeds, builds, places, routes, and fails much later and much more expensively.警告加上非預期的結構。這個才危險,也正是第 6 節與第 7 節的主題。工具不失敗,也不移除任何東西;它造了一個東西出來。一個不完整的 case 會產生一個鎖存器,以及形如 Warning: ... is being inferred as a latch 的訊息,或者用 Design Compiler 的方言,是展開報告裡「Inferred memory devices」表格中的一列,Latch 欄位打了勾。一個自我賦值會產生組合迴路,以及一則關於「有一條無法分析的回授路徑」的訊息。這兩種情況下,設計都會繼續往下走,建置、擺放、繞線,然後在很久以後、以貴得多的代價失敗。警告つきの意図しない構造。これが危険なものであり、第 6 節と第 7 節の主題である。ツールは失敗もせず、何も削除せず、何かを作ってしまう。不完全な case はラッチと Warning: ... is being inferred as a latch の形のメッセージを生む。Design Compiler の方言では、エラボレーションレポートの「Inferred memory devices」表に一行が現れ、Latch の欄にチェックが入る。自己代入は組合せループと、解析できないフィードバック経路に関するメッセージを生む。いずれの場合も設計はそのまま進み、ビルドされ、配置され、配線され、ずっと後で、ずっと高い代償を伴って失敗する。
After elaboration, a tool prints a table of every sequential element it inferred, with the module, the variable name, the width, and the type (flip-flop, latch, with/without async set and reset). For a purely combinational module that table must be empty. For a sequential module every row must be a flop you meant to create. This one habit catches nearly every bug in Sections 6 and 7 in the first thirty seconds of a run.展開完成後,工具會印出一張表,列出它推論出來的每一個序向元件,含模組、變數名稱、位元寬度與型別(正反器、鎖存器,有無非同步設定與重置)。對一個純組合邏輯模組而言,這張表必須是空的。對一個序向模組而言,每一列都必須是你有意要造的正反器。光是養成這一個習慣,就能在一次執行的頭三十秒內抓到第 6 節與第 7 節裡幾乎每一個錯誤。エラボレーションの後、ツールは推論したすべての順序素子を、モジュール名・変数名・ビット幅・種別(フリップフロップかラッチか、非同期セット/リセットの有無)とともに表に出力する。純粋な組合せモジュールなら、この表は空でなければならない。順序モジュールなら、各行が意図して作ったフリップフロップでなければならない。この習慣一つで、第 6 節と第 7 節のほぼすべてのバグを実行開始から 30 秒以内に捕まえられる。
Problem: For each fragment, state whether it is synthesisable under the lecture's rules, which clause of the principle it violates if not, and which of the three failure modes you expect.題目:對每一段程式碼,說明它在本講的規則下是否可合成;若不可合成,它違反了原則的哪一個子句;以及你預期會看到三種失效模式中的哪一種。問題:各断片について、本講義の規則の下で合成可能かどうか、可能でないならば原則のどの節に違反するか、そして三つの失敗形態のどれを予想するかを述べよ。
assign y = (a === 1'bx) ? 1'b0 : a;always @(posedge clk) #3 q <= d;for (k = 0; k < n; k = k + 1) sum = sum + v[k]; where n is an input [3:0]for (k = 0; k < n; k = k + 1) sum = sum + v[k];,其中 n 是一個 input [3:0]for (k = 0; k < n; k = k + 1) sum = sum + v[k];(ここで n は input [3:0])for (k = 0; k < 8; k = k + 1) sum = sum + v[k];task delay_then_set; begin @(posedge clk) q = 1; end endtaskprimitive dff_udp (q, clk, d); reg q; table (01) 0 : ? : 0; ... endtable endprimitiveassign bus = oe ? data : 32'bz;$display("state = %b", state); inside a design module's always block設計模組的 always 區塊裡寫 $display("state = %b", state);設計モジュールの always ブロック内の $display("state = %b", state);1. Not synthesisable. Violates "references x as a value". Expect a hard error on === in most tools; a few downgrade it to a warning and treat it as ==, which silently changes behaviour.1. 不可合成。違反「把 x 當成值來參照」。在多數工具中預期 === 會得到一個硬錯誤;少數工具會把它降級為警告並當成 == 處理,這會無聲地改變行為。1. 合成できない。「x を値として参照する」に違反する。多くのツールでは === でハードエラーになる。一部のツールは警告に格下げして == として扱い、その場合は黙って動作が変わる。
2. Not synthesisable as written. Violates "references simulated time" (intra-statement delay). Expect a warning plus silent removal: you get a plain flip-flop and your RTL waveform no longer matches it.2. 照寫的樣子不可合成。違反「涉及模擬時間」(敘述內延遲)。預期是警告加上無聲移除:你會得到一個單純的正反器,而你的 RTL 波形不再與它相符。2. そのままでは合成できない。「シミュレーション時間を参照する」(文内遅延)に違反する。警告つきの暗黙の削除を予想せよ。得られるのは素のフリップフロップで、RTL の波形はもうそれと一致しない。
3. Not synthesisable. Violates "has no fixed shape" — the bound is a signal, so the loop cannot be unrolled. Expect a hard error. The fix is a fixed-bound loop with a masked accumulate: iterate 16 times and add (k < n) ? v[k] : 0.3. 不可合成。違反「沒有固定形狀」——界限是一條訊號,所以迴圈無法展開。預期是硬錯誤。修法是用固定界限的迴圈加上遮罩式累加:跑 16 次,每次加上 (k < n) ? v[k] : 0。3. 合成できない。「形が定まらない」に違反する。境界が信号なのでループを展開できない。ハードエラーを予想せよ。対処は固定境界のループとマスク付き加算である。16 回繰り返し、毎回 (k < n) ? v[k] : 0 を加える。
4. Synthesisable. Unrolls to eight adds, which the optimiser will then restructure into an adder tree if timing demands it. No message expected.4. 可合成。它展開成八次加法,若時序有需要,最佳化器接著會把它重組成一棵加法器樹。預期沒有任何訊息。4. 合成できる。八回の加算に展開され、タイミングが要求すれば最適化器がそれを加算器ツリーへ再構成する。メッセージは出ないはずである。
5. Not synthesisable. A task containing event control; violates clause one. Hard error, or an error that the task cannot be inlined.5. 不可合成。一個含事件控制的 task;違反第一個子句。會得到硬錯誤,或是一個「該 task 無法內聯」的錯誤。5. 合成できない。イベント制御を含む task であり、第一節に違反する。ハードエラーか、task をインライン化できないというエラーになる。
6. Not synthesisable — a sequential UDP (it declares reg q and uses an edge entry). Hard error. The combinational UDP of Section 4.2, which has no reg and no edge entries, is fine.6. 不可合成——這是一個序向 UDP(它宣告了 reg q 並使用了邊緣條目)。硬錯誤。第 4.2 節那種沒有 reg、也沒有邊緣條目的組合邏輯 UDP 則沒問題。6. 合成できない。順序 UDP である(reg q を宣言しエッジのエントリを使っている)。ハードエラー。第 4.2 節のような reg もエッジのエントリも持たない組合せ論理 UDP なら問題ない。
7. Synthesisable. A conditional whose alternative is z is the recognised tri-state template (Section 9.1). The z here is not being read as a value, it is a request for a high-impedance driver, which is a real cell.7. 可合成。一個「另一分支是 z」的條件運算式,正是被辨識的三態範本(第 9.1 節)。這裡的 z 並不是被當成值讀取,而是在要求一個高阻抗驅動器,那是一個真實存在的元件。7. 合成できる。もう一方の枝が z である条件式は、認識されるトライステートのテンプレートそのものである(第 9.1 節)。ここでの z は値として読まれているのではなく、高インピーダンスのドライバを要求しているのであり、それは実在するセルである。
8. Synthesisable in the sense that the module still builds: the tool ignores the call with a warning. It costs nothing in the netlist, but it means the RTL and the netlist differ in their logging, and some lint flows reject it. Harmless, untidy, and the reason the "warning plus silent removal" category exists.8. 在「模組仍然建得起來」的意義上是可合成的:工具會帶著警告忽略這個呼叫。它在網表裡不花任何代價,但這表示 RTL 與網表在訊息輸出上有差異,而且有些 lint 流程會拒收它。無害、不整潔,也正是「警告加上無聲移除」這一類存在的理由。8. 「モジュールはビルドできる」という意味では合成可能である。ツールは警告つきでこの呼び出しを無視する。ネットリスト上のコストはゼロだが、RTL とネットリストでログ出力が異なることを意味し、これを拒否する lint フローもある。無害だが不潔であり、「警告つきの暗黙の削除」という分類が存在する理由でもある。
The slide says the synthesis target is to minimise area, delay or power. The word "or" is doing a lot of work. These three quantities are not independent, and they are not simultaneously minimisable; what the tool actually solves is a constrained optimisation, almost always of the form minimise area subject to meeting a delay constraint. You supply the delay constraint as a clock period and a set of input and output delays; the tool treats it as a hard requirement until it proves it cannot be met, and spends every remaining degree of freedom on area.投影片說合成的目標是最小化面積、延遲或功耗。這個「或」字擔了很重的責任。這三個量並不獨立,也不可能同時最小化;工具實際求解的是一個有約束的最佳化問題,而且幾乎一定是在滿足延遲約束的前提下把面積最小化這種形式。你以一個時脈週期加上一組輸入與輸出延遲提供延遲約束;工具把它當成硬性要求,直到它證明自己做不到為止,剩下的所有自由度全都花在面積上。スライドは合成の目標を面積・遅延・消費電力の最小化だと述べる。この「または」という語は重い仕事をしている。三つの量は独立ではなく、同時に最小化することもできない。ツールが実際に解いているのは制約付き最適化であり、ほぼ必ず遅延制約を満たしたうえで面積を最小化するという形をとる。遅延制約はクロック周期と入出力遅延の集合として与える。ツールはそれを満たせないと証明するまで厳格な要求として扱い、残る自由度をすべて面積に注ぎ込む。
It helps to have concrete units in mind. In a standard-cell library, area is reported in square microns, but it is more useful to think in gate equivalents: one GE is the area of a minimum-drive two-input NAND. A two-input NAND is 1 GE by definition, an inverter is about 0.67 GE, a two-input XOR about 2 GE, a 2:1 multiplexer about 2.3 GE per bit, a D flip-flop about 5 GE, and a D latch about 3.5 GE. Delay is reported in nanoseconds or picoseconds through characterised arcs, but for reasoning at the RTL level it is enough to count levels of logic, where one level is roughly one simple gate delay. Power splits into dynamic power, proportional to $\alpha C V^2 f$ where $\alpha$ is the switching activity, and leakage, which is proportional to total transistor width and therefore, to first order, to area.腦中有具體的單位會很有幫助。在標準元件庫裡,面積以平方微米回報,但更有用的想法是用閘當量(gate equivalent, GE):1 GE 是一個最小驅動力的二輸入 NAND 的面積。依定義二輸入 NAND 是 1 GE,反相器約 0.67 GE,二輸入 XOR 約 2 GE,2:1 多工器每位元約 2.3 GE,D 正反器約 5 GE,D 鎖存器約 3.5 GE。延遲透過特徵化的時序弧以奈秒或皮秒回報,但在 RTL 層級推理時,數邏輯層數就夠了,一層大致就是一個簡單閘的延遲。功耗分成動態功耗,與 $\alpha C V^2 f$ 成正比,其中 $\alpha$ 是切換活動率;以及漏電,它與電晶體總寬度成正比,因此一階來說也就與面積成正比。具体的な単位を頭に入れておくとよい。標準セルライブラリでは面積は平方マイクロメートルで報告されるが、ゲート等価(gate equivalent, GE)で考えるほうが役に立つ。1 GE は最小駆動力の 2 入力 NAND の面積である。定義により 2 入力 NAND は 1 GE、インバータは約 0.67 GE、2 入力 XOR は約 2 GE、2:1 マルチプレクサは 1 ビットあたり約 2.3 GE、D フリップフロップは約 5 GE、D ラッチは約 3.5 GE である。遅延は特性化されたアークを通じてナノ秒やピコ秒で報告されるが、RTL のレベルで考えるには論理の段数を数えれば十分で、1 段はおよそ単純なゲート 1 個の遅延に当たる。消費電力は、$\alpha C V^2 f$ に比例する動的電力($\alpha$ はスイッチング活性度)と、トランジスタの総幅に比例し、したがって一次近似では面積に比例するリークとに分かれる。
This is why minimising area usually also minimises leakage, while minimising delay often costs both: the fast version of a function uses more parallel structure (more cells, more area, more leakage) and higher-drive cells (wider transistors, more leakage and more dynamic power). The three objectives in the slide are really one objective with two constraints, and your job as the RTL author is to make sure the structure you describe does not force the tool into a corner where no good trade-off exists.這就是為什麼把面積最小化通常也會把漏電最小化,而把延遲最小化卻常常兩頭都付代價:同一個函數的快速版本會用上更多平行結構(更多元件、更多面積、更多漏電)以及驅動力更強的元件(更寬的電晶體、更多漏電、也更多動態功耗)。投影片上的三個目標其實是一個目標加上兩個約束,而身為 RTL 作者,你的工作是確保你所描述的結構不會把工具逼進一個不存在好取捨的死角。面積の最小化がたいていリークの最小化も兼ねるのに対し、遅延の最小化はしばしば両方を犠牲にする理由がここにある。同じ関数の高速版は、より並列な構造(セルが増え、面積が増え、リークが増える)と、駆動力の高いセル(トランジスタが広く、リークも動的電力も増える)を使うからだ。スライドの三つの目標は、実際には一つの目標と二つの制約である。RTL を書く者の仕事は、記述した構造がツールを「良いトレードオフが存在しない隅」へ追い込まないようにすることである。
Two-level minimisation puts a function into sum-of-products form with the fewest product terms and the fewest literals. You met the algorithms in an earlier logic design course: Karnaugh maps for up to about five variables, Quine–McCluskey for exact minimisation at small scale, and Espresso-style heuristics for anything real. The result is a two-level AND-OR structure, which has exactly two levels of logic and is therefore as fast as a given function can be made in that style.兩層式最小化把一個函數化成乘積項最少、文字數最少的積項和形式。這些演算法你在更早的邏輯設計課上見過:五個變數以內用卡諾圖,小規模的精確最小化用 Quine–McCluskey,實際規模的東西則用 Espresso 式的啟發法。結果是一個兩層的 AND-OR 結構,它恰好有兩層邏輯,因此在那種風格下已經是一個函數能做到的最快。二段の最小化は、関数を積項数とリテラル数が最小の積和形にする。アルゴリズムは以前の論理設計の講義で習っているはずだ。変数が 5 個程度までならカルノー図、小規模な厳密最小化なら Quine–McCluskey、現実的な規模なら Espresso 系のヒューリスティックである。得られるのは 2 段の AND-OR 構造で、論理段数がちょうど 2 段しかないため、そのスタイルで作れる限り最速である。
Two-level form is the natural target for a PLA or a PLD, where the hardware physically is an AND plane feeding an OR plane. For standard cells it is nearly always wrong. Consider the parity of eight bits, $P = x_0 \oplus x_1 \oplus \cdots \oplus x_7$. Its minimal sum of products has 128 product terms of eight literals each — 1024 literals — because every odd-parity minterm is a prime implicant and none of them merge. The multi-level form is seven XOR gates, about 14 GE, with three levels of delay if you balance the tree. The ratio between the two implementations is roughly a hundred to one. Parity is an extreme case, but the direction of the effect is universal: two-level minimisation optimises the wrong cost function for cell-based design.兩層形式對 PLA 或 PLD 是自然的目標,因為那裡的硬體實體上就是一個 AND 平面餵給一個 OR 平面。對標準元件而言,它幾乎永遠是錯的。考慮八個位元的奇偶校驗 $P = x_0 \oplus x_1 \oplus \cdots \oplus x_7$。它的最小積項和有 128 個乘積項,每項八個文字——總共 1024 個文字——因為每一個奇同位最小項都是質蘊涵項,而且彼此無法合併。多層形式則是七個 XOR 閘,大約 14 GE,如果把樹配平,延遲是三層。兩種實作的比值大約是一百比一。奇偶校驗是極端案例,但效應的方向是普遍的:對 cell-based 設計而言,兩層式最小化最佳化的是錯誤的成本函數。二段形式は PLA や PLD にとっては自然な目標である。そこではハードウェアが物理的に AND 面と OR 面でできているからだ。標準セルに対しては、ほぼ常に誤りである。8 ビットのパリティ $P = x_0 \oplus x_1 \oplus \cdots \oplus x_7$ を考えよう。その最小積和形は 8 リテラルの積項を 128 個、合計 1024 リテラル持つ。奇パリティの最小項はすべて主項であり、どれも併合できないからだ。多段形式なら XOR ゲート 7 個、約 14 GE で、木をバランスさせれば遅延は 3 段である。二つの実装の比はおよそ 100 対 1 になる。パリティは極端な例だが、効果の向きは普遍的である。セルベース設計にとって、二段の最小化は誤った費用関数を最適化している。
This is the practical reason the slide's flow diagram puts "two-level logic functions" as the output of translation and "optimised multi-level logic" as the output of optimisation. Two-level form is an intermediate representation — a canonical starting point that is easy to produce from the parse tree and easy to manipulate — not a goal.這就是投影片的流程圖為什麼把「兩層邏輯函數」放在翻譯的輸出、把「最佳化後的多層邏輯」放在最佳化的輸出的實務理由。兩層形式是一種中間表示法——一個容易從剖析樹產生、也容易操作的標準起點——而不是目標。スライドのフロー図が「二段の論理関数」を翻訳の出力とし、「最適化された多段論理」を最適化の出力としている実務上の理由はここにある。二段形式は中間表現であり、構文木から作りやすく扱いやすい正準的な出発点であって、目標ではない。
Multi-level minimisation is a sequence of local rewrites on a Boolean network, where a network is a directed acyclic graph of nodes, each node holding a Boolean expression over the primary inputs and over other nodes. The cost function is usually literal count, because literal count correlates well with transistor count in a static CMOS library. The lecture lists five rewrite operations. Each one is a move in a search; the tool applies them repeatedly, accepting a move when it improves the cost and sometimes when it does not, in the manner of any local-search optimiser.多層最小化是在一個布林網路上進行的一連串區域改寫,其中網路是一個有向無環圖,每個節點持有一個以主要輸入與其他節點為變數的布林運算式。成本函數通常是文字數,因為在靜態 CMOS 元件庫裡,文字數與電晶體數相關性很好。本講列出五個改寫操作。每一個都是搜尋中的一步棋;工具反覆施加它們,當一步棋改善成本時接受它,有時即使沒改善也接受——就像任何區域搜尋最佳化器一樣。多段の最小化は、ブール・ネットワーク上の局所的な書き換えの列である。ネットワークとは有向非巡回グラフで、各ノードが主入力と他ノードを変数とするブール式を保持する。費用関数は通常リテラル数である。静的 CMOS のライブラリではリテラル数がトランジスタ数とよく相関するからだ。講義は五つの書き換え操作を挙げる。どれも探索における一手であり、ツールはこれらを繰り返し適用して、費用が改善すれば受け入れ、ときには改善しなくても受け入れる。局所探索型の最適化器がどれもそうするのと同じである。
Decomposition takes a single complicated node and replaces it with a small expression over new intermediate nodes. The slide's example:分解把一個複雜的單一節點,換成一個以新的中間節點為變數的小運算式。投影片的例子:分解は、一つの複雑なノードを、新しい中間ノードを変数とする小さな式に置き換える。スライドの例を示す。
The algebra is worth doing by hand, because this is a standard exam question. The first two terms give $abc + abd = ab(c+d) = XY$. The last two give $a'c'd' + b'c'd' = (a'+b')c'd'$. Now apply De Morgan twice: $a'+b' = (ab)' = X'$, and $c'd' = (c+d)' = Y'$. So the last two terms are $X'Y'$, and $F = XY + X'Y'$, which is the XNOR $X \odot Y$. Count the cost. Before: four products, twelve literals, and if you build it flat you need four AND gates (three of width three, counting inversions) and one four-input OR — call it nine or ten gate equivalents. After: one two-input AND for $X$, one two-input OR for $Y$, one two-input XNOR for $F$ — about 4 GE. The literal count went from twelve to six, and the gate count more than halved.這段代數值得親手算一遍,因為這是標準的考題。前兩項給出 $abc + abd = ab(c+d) = XY$。後兩項給出 $a'c'd' + b'c'd' = (a'+b')c'd'$。接著用兩次笛摩根定律:$a'+b' = (ab)' = X'$,而 $c'd' = (c+d)' = Y'$。所以後兩項就是 $X'Y'$,於是 $F = XY + X'Y'$,也就是 XNOR $X \odot Y$。算一下成本。之前:四個乘積項、十二個文字,若攤平建造需要四個 AND 閘(其中三個是三輸入,含反相計入)加一個四輸入 OR——算九到十個閘當量。之後:一個二輸入 AND 產生 $X$、一個二輸入 OR 產生 $Y$、一個二輸入 XNOR 產生 $F$——大約 4 GE。文字數從十二降到六,閘數減少超過一半。この代数は手で一度追ってみる価値がある。標準的な試験問題だからだ。最初の二項は $abc + abd = ab(c+d) = XY$ を与える。後の二項は $a'c'd' + b'c'd' = (a'+b')c'd'$ となる。ここでド・モルガンを二度使うと、$a'+b' = (ab)' = X'$、$c'd' = (c+d)' = Y'$ である。よって後の二項は $X'Y'$ となり、$F = XY + X'Y'$、すなわち XNOR $X \odot Y$ である。コストを数えよう。前:積項 4 個、リテラル 12 個。平坦に作れば AND ゲート 4 個(うち 3 個は反転を数えて 3 入力)と 4 入力 OR 1 個、おおよそ 9〜10 ゲート等価である。後:$X$ のための 2 入力 AND 1 個、$Y$ のための 2 入力 OR 1 個、$F$ のための 2 入力 XNOR 1 個で、約 4 GE。リテラル数は 12 から 6 に、ゲート数は半分以下になった。
Notice what decomposition cost: a logic level. The flat form was two levels (AND then OR). The decomposed form is two levels to produce $X$ and $Y$, then one more for the XNOR — three levels. This is the universal trade in multi-level work and it is why the tool needs a delay constraint before it can decide whether a decomposition is a good idea.注意分解付出了什麼代價:一層邏輯。攤平的形式是兩層(先 AND 後 OR)。分解後的形式是兩層產生 $X$ 與 $Y$,再一層做 XNOR——三層。這是多層工作中普遍存在的交易,也是為什麼工具必須先拿到延遲約束,才能決定一次分解是不是好主意。分解が何を支払ったかに注目したい。論理が 1 段増えている。平坦な形は 2 段(AND のあと OR)であった。分解後は $X$ と $Y$ を作るのに 2 段、XNOR にもう 1 段で 3 段になる。これは多段最適化における普遍的な取引であり、ある分解が良い手かどうかを決める前にツールが遅延制約を必要とする理由でもある。
Extraction is decomposition applied across functions: find a sub-expression that appears in more than one function and factor it out once. The slide's example has three outputs.抽出就是跨函數施行的分解:找出一個在一個以上函數中出現的子運算式,把它提出來只做一次。投影片的例子有三個輸出。抽出は、関数をまたいで適用する分解である。複数の関数に現れる部分式を見つけ、一度だけ作るようくくり出す。スライドの例には出力が三つある。
Count literals. Before: $F$ has five ($a, b, c, d, e$), $G$ has three, $H$ has three — eleven in total, with the OR $(a+b)$ built twice and the AND $cd$ built twice. After: $X$ has two, $Y$ has two, $F$ has three, $G$ has two, $H$ has two — eleven literals again, but the structure now contains one OR gate and one AND gate instead of two of each. Literal count is a crude proxy; what extraction really bought is sharing, which literal counting on the flattened form does not see. This is the single most valuable optimisation in real designs, because real designs are full of repeated address decodes, repeated comparisons against the same constant, and repeated enable terms.數一數文字。之前:$F$ 有五個($a, b, c, d, e$),$G$ 有三個,$H$ 有三個——總共十一個,而且 OR $(a+b)$ 造了兩次,AND $cd$ 也造了兩次。之後:$X$ 有兩個,$Y$ 有兩個,$F$ 有三個,$G$ 有兩個,$H$ 有兩個——還是十一個文字,但結構裡現在只有一個 OR 閘與一個 AND 閘,而不是各兩個。文字數是個粗糙的代理指標;抽出真正買到的是共用,而在攤平形式上數文字數是看不到共用的。這是真實設計中最有價值的單一最佳化,因為真實設計裡滿是重複的位址解碼、對同一個常數重複的比較,以及重複的致能項。リテラルを数えよう。前:$F$ は 5 個($a, b, c, d, e$)、$G$ は 3 個、$H$ は 3 個で合計 11 個。しかも OR $(a+b)$ を 2 回、AND $cd$ を 2 回作っている。後:$X$ が 2 個、$Y$ が 2 個、$F$ が 3 個、$G$ が 2 個、$H$ が 2 個で、やはり 11 リテラルである。しかし構造の側では OR ゲートと AND ゲートがそれぞれ 2 個から 1 個になっている。リテラル数は粗い代理指標にすぎない。抽出が本当に買ったのは共有であり、平坦形のリテラルを数えるだけではそれが見えない。これは実際の設計で最も価値のある単一の最適化である。現実の設計は、繰り返し現れるアドレスデコード、同じ定数との繰り返しの比較、繰り返しのイネーブル項であふれているからだ。
Extraction is also the reason you should not hand-optimise your RTL into unreadable Boolean algebra. If you write the same comparison in five places, the tool will extract it once. If you write it once into a named wire, it is extracted by construction and your code is readable. Both get the same netlist; only one gets reviewed successfully.抽出也是你不該把自己的 RTL 手動最佳化成難讀的布林代數的理由。如果你在五個地方寫同一個比較,工具會把它抽出成一個。如果你把它寫一次成為一條具名的 wire,那它在構造上就已經被抽出了,而且你的程式碼可讀。兩種寫法得到同一份網表;但只有一種能順利通過程式碼審查。抽出は、RTL を読めないブール代数へ手で最適化すべきでない理由でもある。同じ比較を五か所に書けば、ツールはそれを一つに抽出する。名前付きの wire に一度だけ書けば、構成上すでに抽出されており、しかもコードは読める。どちらも同じネットリストになるが、レビューを通るのは片方だけである。
Nine literals become five. Structurally, four two-input ANDs plus a five-input OR (about 7–8 GE) become two two-input ORs, one two-input AND and one two-input OR (about 4 GE). Delay goes from two levels to three. Factoring is the move that most directly converts area into delay, and it is the move whose result most resembles what a human would have written in the first place — which is the point of the next callout.九個文字變成五個。結構上,四個二輸入 AND 加一個五輸入 OR(約 7–8 GE)變成兩個二輸入 OR、一個二輸入 AND 與一個二輸入 OR(約 4 GE)。延遲從兩層變三層。因式分解是最直接把面積換成延遲的一步棋,也是結果最像人類一開始就會寫出來的那種形式的一步棋——這正是下一個提示框的重點。9 リテラルが 5 リテラルになる。構造的には、2 入力 AND 4 個と 5 入力 OR 1 個(約 7〜8 GE)が、2 入力 OR 2 個、2 入力 AND 1 個、2 入力 OR 1 個(約 4 GE)になる。遅延は 2 段から 3 段へ増える。因数分解は面積を遅延に換える最も直接的な一手であり、その結果が、そもそも人間なら最初からこう書いたであろう形に最もよく似た一手でもある。次のコールアウトの要点はそこにある。
Substitution differs from extraction in that the shared node already exists; the tool is noticing that an existing output happens to be a useful sub-function of another output. The literal count of $F$ drops from three to two and the three-input OR becomes a two-input OR. In a real design this move is what makes a carry chain reusable, or lets a comparator's equality output feed a greater-than-or-equal output. Its risk is that it lengthens the path through $G$: whatever drove $G$ now also drives $F$, so $G$'s fanout grows and its arrival time now appears on $F$'s path. If $G$ is on the critical path, substitution into $F$ can make $F$ critical too.代入與抽出的差別在於,那個被共用的節點已經存在了;工具注意到的是某個既有輸出碰巧是另一個輸出的有用子函數。$F$ 的文字數從三降到二,三輸入 OR 變成二輸入 OR。在真實設計中,這一步棋讓進位鏈可以被重用,或讓一個比較器的相等輸出去餵它的大於等於輸出。它的風險是會拉長穿過 $G$ 的路徑:原本驅動 $G$ 的東西現在也驅動 $F$,所以 $G$ 的扇出變大,而且它的到達時間現在出現在 $F$ 的路徑上。如果 $G$ 在關鍵路徑上,把它代入 $F$ 可能讓 $F$ 也變成關鍵路徑。代入が抽出と異なるのは、共有されるノードがすでに存在している点である。ツールは、既存の出力がたまたま別の出力の有用な部分関数になっていることに気づくのだ。$F$ のリテラル数は 3 から 2 に減り、3 入力 OR が 2 入力 OR になる。実際の設計では、この一手によってキャリーチェーンが再利用できるようになったり、比較器の等価出力が「以上」の出力を駆動できるようになったりする。リスクは $G$ を通る経路が長くなることである。$G$ を駆動していたものが $F$ も駆動するようになるので $G$ のファンアウトが増え、その到着時刻が $F$ の経路に現れる。$G$ がクリティカルパス上にあれば、$F$ への代入は $F$ までクリティカルにしかねない。
Elimination collapses a node back into its users. Verify it: $Ga = (c+d)a = ac+ad$, and $G'b = (c+d)'b = c'd'b$ by De Morgan, giving $F = ac + ad + bc'd'$. Literal count went from four (in $F$) plus two (in $G$) to seven. Area got worse. What got better is depth: $F$ no longer waits for $G$. Elimination is the tool's main weapon against a critical path, and it is applied selectively — eliminate the node that sits on the worst path, keep the others shared.消去把一個節點塌回它的使用者裡。驗證一下:$Ga = (c+d)a = ac+ad$,而由笛摩根定律 $G'b = (c+d)'b = c'd'b$,於是 $F = ac + ad + bc'd'$。文字數從($F$ 裡的)四個加上($G$ 裡的)兩個,變成七個。面積變差了。變好的是深度:$F$ 不再等 $G$。消去是工具對付關鍵路徑的主要武器,而且是有選擇性地施加——消去那個落在最差路徑上的節點,其餘的保持共用。消去はノードをその利用者の中へ畳み込む。検算しよう。$Ga = (c+d)a = ac+ad$ であり、ド・モルガンにより $G'b = (c+d)'b = c'd'b$ なので、$F = ac + ad + bc'd'$ となる。リテラル数は($F$ の)4 個と($G$ の)2 個の計 6 個から 7 個に増えた。面積は悪化した。良くなったのは深さである。$F$ はもう $G$ を待たない。消去はクリティカルパスに対するツールの主力武器であり、選択的に適用される。最悪経路に乗っているノードを消去し、それ以外は共有したまま残すのだ。
Four of the five moves pair up. Decomposition introduces a node, elimination removes one. Extraction introduces a shared node, substitution routes an existing output into a new user. Factoring is the odd one out — it restructures within a node without changing the node set. The optimiser's loop is: eliminate aggressively to flatten, re-extract common factors from the flattened network, then decompose and factor to recover area, measuring against the delay constraint at every step. That is why the same RTL can give you a small-and-slow netlist at a loose clock constraint and a large-and-fast one at a tight constraint, with no source change at all.五步棋裡有四步兩兩成對。分解引入一個節點,消去移除一個節點。抽出引入一個共用節點,代入則把一個既有輸出接進一個新的使用者。因式分解是落單的那一個——它在一個節點內部重構,不改變節點集合。最佳化器的迴圈是:積極消去以攤平,從攤平後的網路重新抽出共同因子,再用分解與因式分解把面積賺回來,每一步都對照延遲約束衡量。這就是為什麼同一份 RTL,在寬鬆的時脈約束下給你一個又小又慢的網表,在嚴苛的約束下給你一個又大又快的網表,而原始碼一個字都沒改。五つの手のうち四つは対になる。分解はノードを導入し、消去はノードを取り除く。抽出は共有ノードを導入し、代入は既存の出力を新しい利用者へ回す。因数分解だけが対を持たない。ノード集合を変えずにノード内部を再構成するからだ。最適化器のループはこうである。積極的に消去して平坦化し、平坦化したネットワークから共通因子を再抽出し、分解と因数分解で面積を取り戻す。そのすべての段階で遅延制約と照らし合わせる。だからこそ、同じ RTL がゆるいクロック制約では小さく遅いネットリストを、厳しい制約では大きく速いネットリストを、ソースを一字も変えずに生むのである。
assign gt = (a > b); with a and b two bits wide, $a = a_1a_0$, $b = b_1b_0$.原始碼。assign gt = (a > b);,其中 a 與 b 寬二位元,$a = a_1a_0$,$b = b_1b_0$。ソース。assign gt = (a > b);(a と b は 2 ビット幅、$a = a_1a_0$、$b = b_1b_0$)。lt and eq, the node $e_1 = a_1 \odot b_1$ is extracted once and used by all three outputs. $eq = e_1 \cdot (a_0 \odot b_0)$ reuses it directly; $lt$ reuses it as $a_1'b_1 + e_1 a_0' b_0$. One XNOR now serves three outputs — the single biggest saving in the whole module.抽出。如果這個模組同時驅動 lt 與 eq,那麼節點 $e_1 = a_1 \odot b_1$ 只抽出一次,三個輸出都用它。$eq = e_1 \cdot (a_0 \odot b_0)$ 直接重用它;$lt$ 以 $a_1'b_1 + e_1 a_0' b_0$ 的形式重用它。現在一個 XNOR 服務三個輸出——這是整個模組裡最大的一筆節省。抽出。このモジュールが lt と eq も駆動するなら、ノード $e_1 = a_1 \odot b_1$ は一度だけ抽出され、三つの出力すべてが使う。$eq = e_1 \cdot (a_0 \odot b_0)$ はそれを直接再利用し、$lt$ は $a_1'b_1 + e_1 a_0' b_0$ の形で再利用する。XNOR 1 個が三つの出力に仕えることになり、これがモジュール全体で最大の節約である。and2b1 or nor2 with input inversion absorbed). It sees $e_1 a_0 b_0'$ as a three-input AND with one inversion. It sees the final OR of two terms and considers an AOI (and-or-invert) cell followed by an inverter, which is usually smaller and faster than discrete AND and OR cells because static CMOS implements inverting functions natively. The final netlist is typically one XNOR, one AOI21 and one inverter — about 5 GE — where the naive flat SOP would have been about 11 GE.映射。映射器把 $a_1b_1'$ 看成一個有一個反相輸入的 AND,許多元件庫以單一元件提供它(and2b1,或把輸入反相吸收進去的 nor2)。它把 $e_1 a_0 b_0'$ 看成一個有一處反相的三輸入 AND。它看到最後兩項的 OR,於是考慮用一個 AOI(and-or-invert)元件後面接一個反相器,這通常比分立的 AND 與 OR 元件更小也更快,因為靜態 CMOS 原生實現的就是反相函數。最後的網表典型上是一個 XNOR、一個 AOI21 與一個反相器——約 5 GE——而天真的攤平 SOP 大約要 11 GE。マッピング。マッパは $a_1b_1'$ を入力が一つ反転した AND と見る。これを単一セル(and2b1、あるいは入力反転を吸収した nor2)として備えるライブラリは多い。$e_1 a_0 b_0'$ は反転を一つ含む 3 入力 AND と見る。最後の 2 項の OR については、AOI(and-or-invert)セルにインバータを続ける構成を検討する。静的 CMOS は反転関数をそのまま実装できるので、個別の AND と OR のセルより小さく速いのが普通だからだ。最終的なネットリストは典型的に XNOR 1 個、AOI21 1 個、インバータ 1 個の約 5 GE であり、素朴な平坦 SOP なら約 11 GE であった。Two lessons from that session carry into every design. First, the mapper prefers inverting cells, so your mental model of "AND gate, OR gate" overestimates both area and delay; a NAND-NAND implementation of an AND-OR function is faster and smaller, and the mapper will find it. Second, the win came from extraction across the three outputs, not from clever algebra on one of them. Write all related outputs in one module and let the tool share; splitting them into three modules and disabling cross-boundary optimisation would have cost you three XNORs.這次演練有兩個教訓貫串每一個設計。第一,映射器偏好反相元件,所以你腦中「AND 閘、OR 閘」的模型會同時高估面積與延遲;以 NAND-NAND 實現一個 AND-OR 函數更快也更小,而映射器會找到它。第二,贏面來自跨三個輸出的抽出,不是來自對其中一個輸出耍聰明的代數。把所有相關的輸出寫在同一個模組裡,讓工具去共用;把它們拆成三個模組並關掉跨邊界最佳化,會讓你多付三個 XNOR。この実演から、どの設計にも通じる教訓が二つ得られる。第一に、マッパは反転セルを好むので、「AND ゲート、OR ゲート」という頭の中のモデルは面積も遅延も過大評価する。AND-OR 関数の NAND-NAND 実装のほうが速く小さく、マッパはそれを見つける。第二に、勝ちをもたらしたのは三つの出力にまたがる抽出であって、どれか一つに対する巧妙な代数ではない。関連する出力はすべて同じモジュールに書き、共有はツールに任せること。三つのモジュールに分割して境界越えの最適化を切れば、XNOR を三つ余計に払うことになる。
Problem:題目:問題:
1. $F = (a+b)(c+d)e + f$. Before: thirteen literals, two levels (four three-input ANDs feeding a five-input OR). After: six literals, four levels ($a+b$ and $c+d$ in parallel, then the AND with $e$, then the OR with $f$ — or three levels if the tool uses a three-input AND). Roughly 11 GE down to 5 GE, at the cost of one or two extra levels.1. $F = (a+b)(c+d)e + f$。之前:十三個文字、兩層(四個三輸入 AND 餵一個五輸入 OR)。之後:六個文字、四層($a+b$ 與 $c+d$ 平行,再與 $e$ 做 AND,再與 $f$ 做 OR——若工具用三輸入 AND 則是三層)。大約從 11 GE 降到 5 GE,代價是多一到兩層。1. $F = (a+b)(c+d)e + f$。前:リテラル 13 個、2 段(3 入力 AND 4 個が 5 入力 OR を駆動)。後:リテラル 6 個、4 段($a+b$ と $c+d$ を並列に作り、$e$ と AND し、$f$ と OR する。ツールが 3 入力 AND を使えば 3 段)。おおむね 11 GE から 5 GE へ、1〜2 段の代償を払って減る。
2. $F = abx + (ab)'y = abx + a'y + b'y$. Before: four literals in $F$ plus two in $G$, six total, with $F$ waiting on $G$. After: seven literals, but $F$ now depends only on primary inputs. You want this when $G$'s arrival time is what makes $F$ miss timing, and only then — if $G$ is also used elsewhere, eliminating it from $F$ does not even remove the gate.2. $F = abx + (ab)'y = abx + a'y + b'y$。之前:$F$ 裡四個文字加 $G$ 裡兩個,共六個,而且 $F$ 要等 $G$。之後:七個文字,但 $F$ 現在只依賴主要輸入。只有在 $G$ 的到達時間正是讓 $F$ 時序不過的原因時,你才會想這麼做——而且如果 $G$ 在別處也被用到,把它從 $F$ 消去甚至不會移除那個閘。2. $F = abx + (ab)'y = abx + a'y + b'y$。前:$F$ の 4 リテラルと $G$ の 2 リテラルで計 6 個。かつ $F$ は $G$ を待つ。後:7 リテラルになるが、$F$ は主入力だけに依存する。これを望むのは、$G$ の到着時刻こそが $F$ のタイミング違反の原因であるときだけである。しかも $G$ が他でも使われていれば、$F$ から消去してもゲート自体は減らない。
3. Let $X = wx$ and $Y = y+z$. Then $wxy + wxz = wx(y+z) = XY$, and $w'y'z' + x'y'z' = (w'+x')y'z' = (wx)'(y+z)' = X'Y'$. So $H = XY + X'Y' = X \odot Y$ — an XNOR of an AND and an OR, three gates in place of four wide ANDs and a four-input OR.3. 令 $X = wx$、$Y = y+z$。則 $wxy + wxz = wx(y+z) = XY$,而 $w'y'z' + x'y'z' = (w'+x')y'z' = (wx)'(y+z)' = X'Y'$。所以 $H = XY + X'Y' = X \odot Y$——一個 AND 與一個 OR 的 XNOR,用三個閘取代四個寬 AND 加一個四輸入 OR。3. $X = wx$、$Y = y+z$ とおく。すると $wxy + wxz = wx(y+z) = XY$、$w'y'z' + x'y'z' = (w'+x')y'z' = (wx)'(y+z)' = X'Y'$ である。よって $H = XY + X'Y' = X \odot Y$、すなわち AND と OR の XNOR であり、幅広 AND 4 個と 4 入力 OR 1 個の代わりにゲート 3 個で済む。
4. Extract $X = p+q$. Then $F_1 = Xr$, $F_2 = Xs'$, $F_3 = X't$. The result needs one OR gate, one inverter for $X'$ (or the mapper uses a NOR cell to produce $X'$ directly and an inverter to produce $X$), plus one inverter for $s'$. The flat version needed the OR built three times — two ORs and one NOR — so extraction saved two gates on a four-gate function. In a 32-bit address decoder the same move saves thirty-one.4. 抽出 $X = p+q$。則 $F_1 = Xr$、$F_2 = Xs'$、$F_3 = X't$。結果需要一個 OR 閘、一個產生 $X'$ 的反相器(或者映射器直接用一個 NOR 元件產生 $X'$,再用一個反相器產生 $X$),外加一個產生 $s'$ 的反相器。攤平版本需要把那個 OR 造三次——兩個 OR 加一個 NOR——所以在一個四閘的函數上,抽出省了兩個閘。在一個 32 位元的位址解碼器上,同樣這一步省下三十一個。4. $X = p+q$ を抽出する。すると $F_1 = Xr$、$F_2 = Xs'$、$F_3 = X't$ となる。結果に必要なのは OR ゲート 1 個、$X'$ を作るインバータ 1 個(あるいはマッパが NOR セルで $X'$ を直接作り、インバータで $X$ を作る)、加えて $s'$ のインバータ 1 個である。平坦版では OR を 3 回作る必要があり、OR 2 個と NOR 1 個になる。つまり 4 ゲートの関数で抽出が 2 ゲートを節約した。32 ビットのアドレスデコーダなら、同じ一手が 31 個を節約する。
The lecture lists six synthesisable description styles: a netlist of Verilog primitives, a combinational UDP, a continuous assignment, a behavioural always block, a function, and a task without delay or event control — plus, trivially, any interconnection of modules built from these. It then demonstrates five of them on the same tiny function, which is the pedagogically important part: five different-looking source files, one identical netlist.本講列出六種可合成的描述風格:Verilog 原件組成的網表、組合邏輯 UDP、連續賦值、行為式 always 區塊、function,以及不含延遲或事件控制的 task——另外還有一個顯而易見的:由這些東西組成的任何模組互連。接著它在同一個極小的函數上示範其中五種,而這才是教學上重要的部分:五份長得完全不同的原始檔,一份一模一樣的網表。講義は合成可能な記述スタイルを六つ挙げる。Verilog プリミティブのネットリスト、組合せ論理 UDP、連続代入、行動記述の always ブロック、function、そして遅延やイベント制御を含まない task である。さらに自明なものとして、これらから作ったモジュールの任意の相互接続もある。続いて講義は、そのうち五つを同じごく小さな関数で実演する。教育的に重要なのはそこで、見た目のまるで違う五つのソースから、同一のネットリストが出てくるのだ。
The point being made is that synthesis is not transliteration. The tool does not emit one gate per operator you typed. It extracts the Boolean function, optimises it, and maps it. Five syntactically different descriptions of the same function therefore converge on the same cells. If you take one thing from this section, take that: the style you choose affects readability, simulation speed and your own error rate, but — within the synthesisable subset, for a function with no state — it does not affect the hardware.這裡要講的重點是合成不是逐字轉寫。工具不會為你打的每一個運算子吐出一個閘。它抽取布林函數、最佳化它、再映射它。因此同一個函數的五種語法上不同的描述,會收斂到同一組元件。如果你從本節只帶走一件事,請帶走這件:你選的風格會影響可讀性、模擬速度與你自己的出錯率,但是——在可合成子集內、對一個沒有狀態的函數而言——它不影響硬體。ここでの要点は合成は逐語的な書き換えではないということである。ツールはあなたが打った演算子ごとにゲートを一つ出力したりはしない。ブール関数を抽出し、最適化し、マッピングする。したがって同じ関数の構文的に異なる五つの記述は、同じセルの集合に収束する。本節から一つだけ持ち帰るなら、これにしてほしい。選んだスタイルは可読性・シミュレーション速度・自分のミス率には効くが、合成可能な部分集合の中で状態を持たない関数に対しては、ハードウェアには効かない。
module or_nand_1 (enable, x1, x2, x3, x4, y);
input enable, x1, x2, x3, x4;
output y;
wire w1, w2, w3;
or (w1, x1, x2);
or (w2, x3, x4);
or (w3, x3, x4); // redundant: identical to w2
nand (y, w1, w2, w3, enable);
endmodule
Gate primitives take the output first and the inputs after, so or (w1, x1, x2) means w1 = x1 | x2. This module describes a circuit with four gates. The synthesised result has two cells.邏輯閘原件把輸出寫在前、輸入寫在後,所以 or (w1, x1, x2) 的意思是 w1 = x1 | x2。這個模組描述的是一個有四個閘的電路。合成的結果有兩個元件。ゲートプリミティブは出力を先に、入力を後に書く。したがって or (w1, x1, x2) は w1 = x1 | x2 を意味する。このモジュールが記述しているのはゲート 4 個の回路である。合成結果はセル 2 個になる。
What happened to the other two? The w3 gate computes exactly what w2 computes, and both feed the same NAND. The optimiser recognises $w_2 \cdot w_3 = w_2$ by idempotence and deletes the duplicate; this is an elementary redundancy removal that happens in the first sweep. Then the remaining structure, $\overline{\text{enable}\cdot w_1 \cdot w_2}$ with $w_1 = x_1+x_2$ and $w_2 = x_3+x_4$, is matched against the library. A library almost always contains an OAI22 cell, which computes $\overline{(A_1+A_2)(B_1+B_2)}$ in a single stack of transistors, so the mapper produces $\overline{(x_1+x_2)(x_3+x_4)}$ in one cell and then needs $y = \overline{\text{enable} \cdot \overline{\text{OAI22 output}}}$, which is a two-input NAND with one inverted input — the slide's nand2i_a.另外兩個跑去哪了?w3 那個閘算的東西與 w2 完全一樣,而且兩者餵進同一個 NAND。最佳化器依冪等律認出 $w_2 \cdot w_3 = w_2$,於是刪掉重複的那個;這是第一輪掃描就會做掉的基本冗餘移除。接著剩下的結構 $\overline{\text{enable}\cdot w_1 \cdot w_2}$(其中 $w_1 = x_1+x_2$、$w_2 = x_3+x_4$)拿去與元件庫比對。元件庫幾乎一定含有一個 OAI22 元件,它以單一組電晶體堆疊算出 $\overline{(A_1+A_2)(B_1+B_2)}$,所以映射器用一個元件產生 $\overline{(x_1+x_2)(x_3+x_4)}$,然後還需要 $y = \overline{\text{enable} \cdot \overline{\text{OAI22 output}}}$,那是一個有一個反相輸入的二輸入 NAND——也就是投影片上的 nand2i_a。残る二つはどこへ行ったのか。w3 のゲートは w2 とまったく同じものを計算し、両者は同じ NAND を駆動している。最適化器は冪等律により $w_2 \cdot w_3 = w_2$ を認識して重複を削除する。これは最初のスイープで起きる初歩的な冗長性除去である。残った構造 $\overline{\text{enable}\cdot w_1 \cdot w_2}$($w_1 = x_1+x_2$、$w_2 = x_3+x_4$)がライブラリと照合される。ライブラリはほぼ必ず OAI22 セルを備えており、これは $\overline{(A_1+A_2)(B_1+B_2)}$ を一つのトランジスタスタックで計算する。そこでマッパは $\overline{(x_1+x_2)(x_3+x_4)}$ を 1 セルで作り、続いて $y = \overline{\text{enable} \cdot \overline{\text{OAI22 output}}}$ が必要になる。これは入力が一つ反転した 2 入力 NAND、すなわちスライドの nand2i_a である。
Writing at primitive level is almost never a good idea in modern RTL, for three reasons. It is verbose, it is unreadable, and — the one that actually costs money — it prevents nothing. People reach for primitives believing they are "telling the tool exactly what to build", but as this example shows, the tool rewrites primitives as freely as it rewrites expressions. The only places structural primitives earn their keep are clock-gating cells and other instances you must not let the optimiser touch, and those you instantiate explicitly from the library with a dont_touch attribute, not as generic Verilog primitives.在現代 RTL 裡以原件層級書寫幾乎永遠不是好主意,理由有三。它囉嗦、它難讀,而且——真正花錢的那一點——它什麼也防不了。人們伸手去抓原件,以為自己是在「精確告訴工具要造什麼」,但正如這個例子所示,工具改寫原件就跟改寫運算式一樣自由。結構式原件唯一值得用的地方,是時脈閘控元件以及其他你不想讓最佳化器碰的實體,而那些你應該用 dont_touch 屬性從元件庫明確實體化,而不是寫成泛用的 Verilog 原件。現代の RTL でプリミティブのレベルで書くのは、ほぼ常に悪手である。理由は三つ。冗長で、読めず、そして実際にコストを生むのは、それで何も防げないことである。プリミティブに手を伸ばす人は「ツールに何を作るかを正確に伝えている」と信じているが、この例が示すとおり、ツールはプリミティブも式と同じくらい自由に書き換える。構造的プリミティブが元を取れる場所は、クロックゲーティングセルや、最適化器に触らせたくないその他のインスタンスだけである。そしてそれらは汎用の Verilog プリミティブではなく、dont_touch 属性を付けてライブラリから明示的にインスタンス化すべきものである。
primitive boolean_eqs (y, a, b, c);
output y;
input a, b, c;
table
// a b c : y
0 1 ? : 1;
0 0 ? : 0;
1 ? 1 : 1;
1 ? 0 : 0;
endtable
endprimitive
A user-defined primitive declares one output and up to ten inputs and lists its behaviour as a table. The ? in an input column means "any of 0, 1 or x" — it is a don't care for matching purposes, not a value. Read the four rows: when $a=0$ the output follows $b$; when $a=1$ it follows $c$. That is $y = a'b + ac$, a 2:1 multiplexer with $a$ as the select. The slide's synthesis result is two AND gates and an OR, which is the textbook mux; a real library would use a transmission-gate MUX2 cell at about 2.3 GE.一個使用者自定原件宣告一個輸出與最多十個輸入,並以一張表格列出它的行為。輸入欄位裡的 ? 意思是「0、1 或 x 皆可」——它是比對用的不在意項,不是一個值。讀這四列:當 $a=0$ 時輸出跟著 $b$;當 $a=1$ 時跟著 $c$。那就是 $y = a'b + ac$,一個以 $a$ 為選擇線的 2:1 多工器。投影片的合成結果是兩個 AND 閘加一個 OR,也就是教科書上的多工器;真實的元件庫會用一個傳輸閘式的 MUX2 元件,約 2.3 GE。ユーザー定義プリミティブは出力を一つと最大 10 個の入力を宣言し、その振る舞いを表として列挙する。入力欄の ? は「0、1、x のいずれでもよい」を意味し、照合のためのドントケアであって値ではない。四つの行を読もう。$a=0$ のとき出力は $b$ に従い、$a=1$ のとき $c$ に従う。すなわち $y = a'b + ac$、$a$ を選択線とする 2:1 マルチプレクサである。スライドの合成結果は AND ゲート 2 個と OR 1 個、教科書どおりのマルチプレクサであるが、実際のライブラリなら約 2.3 GE のトランスミッションゲート型 MUX2 セルを使う。
Two rules make a UDP combinational rather than sequential, and both matter for the exam. First, the output must not be declared reg. Second, no row may contain an edge specifier such as (01) or r or f, and no row may have a three-field form with a current-state column. A table with either feature is a sequential UDP, which is explicitly on the un-synthesisable list, because it encodes arbitrary state-holding behaviour that no inference template matches.有兩條規則決定一個 UDP 是組合邏輯而不是序向的,而且兩條都會考。第一,輸出不得宣告為 reg。第二,任何一列都不得含有像 (01)、r 或 f 這類邊緣指定子,也不得出現帶有「現態」欄位的三欄形式。只要具備其中任一特徵,這張表就是序向 UDP,而它明確落在不可合成清單上,因為它編碼的是任何推論範本都比對不上的任意狀態保持行為。UDP が順序ではなく組合せになる条件は二つあり、どちらも試験に出る。第一に、出力を reg と宣言してはならない。第二に、どの行にも (01)、r、f のようなエッジ指定子があってはならず、現在状態の欄を持つ 3 欄形式の行があってもならない。どちらかの特徴を持つ表は順序 UDP であり、明確に合成不可の一覧に入る。どの推論テンプレートとも照合できない任意の状態保持の振る舞いを符号化しているからである。
The table is not fully specified in the Verilog sense: rows are given only for $a$ equal to 0 or 1 with the other input constrained, and the simulator returns x for any unlisted input combination. Here every combination of 0/1 inputs is covered, so there is no gap, but it is easy to write a UDP that quietly returns x for a combination you forgot. In simulation you see an x propagating; in synthesis the tool treats the gap as a don't care and picks whatever is cheapest. That is the same RTL-versus-netlist divergence that don't-care defaults cause in Section 10, arriving by a different door.就 Verilog 的意義而言,這張表並不是完全指定的:它只在 $a$ 等於 0 或 1、而另一個輸入被限定的情況下給出列,而模擬器對任何未列出的輸入組合都回傳 x。這裡所有 0/1 輸入的組合都被涵蓋了,所以沒有缺口,但要寫出一個對你忘掉的某個組合悄悄回傳 x 的 UDP 非常容易。在模擬中你會看到一個 x 在傳播;在合成中工具把那個缺口當成不在意項,挑最便宜的做法。這跟第 10 節不在意項預設值造成的 RTL 與網表分歧是同一回事,只是從另一扇門進來。Verilog の意味では、この表は完全には指定されていない。$a$ が 0 か 1 で、もう一方の入力が制約された場合の行しか与えられておらず、列挙されていない入力の組合せに対してシミュレータは x を返す。ここでは 0/1 入力のすべての組合せが網羅されているので穴はないが、忘れた組合せに対して黙って x を返す UDP を書いてしまうのはたやすい。シミュレーションでは x が伝播するのが見え、合成ではツールがその穴をドントケアとみなして最も安い実装を選ぶ。これは第 10 節でドントケアのデフォルトが引き起こす RTL とネットリストの乖離と同じ現象が、別の入口から入ってきたものである。
UDPs are a legacy construct. They are worth understanding because they appear on exams and in old libraries, and because the combinational-versus-sequential distinction is a clean illustration of the inference principle. They are not worth writing: a case statement expresses the same table, is readable, simulates faster, and does not risk the sequential-UDP cliff.UDP 是遺留下來的構件。值得理解它們,因為它們出現在考題裡、也出現在老的元件庫裡,而且組合與序向之分是推論原則的一個乾淨示範。但不值得寫它們:一個 case 敘述能表達同一張表,可讀性更好,模擬更快,而且不會有掉下序向 UDP 懸崖的風險。UDP は遺産的な構文である。試験や古いライブラリに登場し、組合せと順序の区別が推論の原則をきれいに例示するので、理解しておく価値はある。だが自分で書く価値はない。case 文で同じ表を表現でき、読みやすく、シミュレーションも速く、順序 UDP の崖から落ちる危険もない。
module or_nand_2 (enable, x1, x2, x3, x4, y); input enable, x1, x2, x3, x4; output y; assign y = ~(enable & (x1 | x2) & (x3 | x4)); endmodule
This is six lines instead of nine and it is impossible to misread. A continuous assignment drives a net from an expression, permanently; there is no sensitivity list to get wrong, no variable to leave unassigned on some path, and therefore no way to infer a latch. For any combinational function that fits comfortably in one expression, assign is the correct choice and the rest of this section is about when it stops fitting.這是六行而不是九行,而且不可能看錯。連續賦值用一個運算式永久地驅動一條網路;沒有敏感度列表可以寫錯,沒有變數會在某條路徑上沒被賦值,因此也就沒有辦法推論出鎖存器。任何一個組合邏輯函數只要能舒服地塞進一個運算式,assign 就是正確的選擇,而本節其餘部分談的是它什麼時候塞不下。こちらは 9 行ではなく 6 行で、読み違えようがない。連続代入は式でネットを恒久的に駆動する。間違えるセンシティビティリストはなく、どこかの経路で代入し忘れる変数もなく、したがってラッチが推論される余地もない。一つの式に無理なく収まる組合せ関数なら assign が正解であり、本節の残りは、それが収まらなくなるのはいつかという話である。
Two details are worth pinning down because they generate exam questions. First, the target of a continuous assignment must be a net (wire), never a reg. Second, the operators here are bitwise: ~, &, |. For the one-bit signals in this module, bitwise and logical operators coincide. For vectors they do not, and the difference is a classic source of silent bugs. If a is 4'b0010, then ~a is 4'b1101 but !a is 1'b0; and a & b is a four-bit AND while a && b is a single bit saying "both are non-zero". The slide's task version in Section 4.6 uses ! where the others use ~, which is harmless there and would be a bug on a vector.有兩個細節值得釘牢,因為它們會變成考題。第一,連續賦值的目標必須是一條 net(wire),絕不可以是 reg。第二,這裡的運算子是位元運算子:~、&、|。對這個模組裡的一位元訊號而言,位元運算子與邏輯運算子一致。對向量而言則不然,而這個差異是無聲錯誤的經典來源。如果 a 是 4'b0010,那麼 ~a 是 4'b1101,但 !a 是 1'b0;而 a & b 是四位元的 AND,a && b 卻是一個位元,說的是「兩者皆非零」。第 4.6 節投影片的 task 版本在其他版本用 ~ 的地方用了 !,在那裡無害,但對向量就會是錯誤。押さえておくべき細部が二つある。どちらも試験問題になる。第一に、連続代入の対象はネット(wire)でなければならず、reg であってはならない。第二に、ここで使われている演算子はビット単位の ~、&、| である。このモジュールの 1 ビット信号ではビット単位演算子と論理演算子は一致する。ベクタでは一致せず、その違いは静かなバグの古典的な原因である。a が 4'b0010 なら ~a は 4'b1101 だが !a は 1'b0 であり、a & b は 4 ビットの AND、a && b は「両方とも非ゼロ」を表す 1 ビットである。第 4.6 節のスライドの task 版は、他の版が ~ を使う箇所で ! を使っている。そこでは無害だが、ベクタならバグになる。
The always block is the general-purpose description style, and it is general-purpose precisely because it introduces sequencing, selection and variables — which is also why it is the only style in this section that can go wrong. The lecture states three rules. Here they are with their real justifications.always 區塊是通用的描述風格,而它之所以通用,正是因為它引入了順序、選擇與變數——這也正是為什麼它是本節唯一會出錯的風格。本講陳述了三條規則。以下是它們以及它們真正的理由。always ブロックは汎用の記述スタイルであり、汎用であるのはまさに順序・選択・変数を持ち込むからである。そしてそれこそが、本節で唯一おかしくなりうるスタイルである理由でもある。講義は三つの規則を述べる。以下にそれらと、その本当の根拠を示す。
"Inputs" means every operand on the right-hand side of any assignment in the block, plus every control signal whose transition can change which assignments execute. A signal read only inside an if condition is an input; so is a signal read only inside one arm of a case.「輸入」指的是區塊中任何賦值右手邊的每一個運算元,加上每一個其轉態會改變哪些賦值被執行的控制訊號。只在 if 條件裡被讀到的訊號是輸入;只在 case 某一分支裡被讀到的訊號也是。「入力」とは、ブロック内のいずれかの代入の右辺に現れるすべてのオペランドと、その遷移によってどの代入が実行されるかが変わるすべての制御信号を指す。if の条件の中だけで読まれる信号も入力であり、case の一つの枝の中だけで読まれる信号も入力である。
This is the lecture's wording and it needs qualifying; see the warning below.這是本講的措辭,而它需要補充條件;見下方的警告。これは講義の言い回しだが、但し書きが要る。下の注意を見よ。
Writing y = ... y ... in a level-sensitive block creates a combinational feedback path, which is Section 7's subject. The lecture calls the consequence an "infinite loop", which is literally what happens in a simulator: the block's own output is in its sensitivity list, so the assignment retriggers the block forever at the same simulation time.在一個準位敏感的區塊裡寫 y = ... y ... 會造出一條組合回授路徑,那是第 7 節的主題。本講把後果稱為「無窮迴圈」,而那在模擬器裡字面上就是會發生的事:區塊自己的輸出在它自己的敏感度列表裡,所以這個賦值會在同一個模擬時間點上永遠重新觸發這個區塊。レベルセンシティブなブロックで y = ... y ... と書くと組合せフィードバック経路ができる。これは第 7 節の主題である。講義はその帰結を「無限ループ」と呼ぶが、シミュレータの中では文字どおりそれが起きる。ブロック自身の出力がそのセンシティビティリストに入っているので、代入が同じシミュレーション時刻でブロックを永遠に再起動し続けるのだ。
This is the one place in Lecture 5 where the slide's wording will mislead you in practice, so it is worth being exact. There are two independent defects, with two different consequences.這是第 5 講中唯一一處投影片的措辭在實務上會誤導你的地方,所以值得講精確。這裡有兩個彼此獨立的缺陷,後果各不相同。第 5 講の中で、スライドの言い回しが実務で誤解を招く唯一の箇所がここなので、正確に述べておく価値がある。互いに独立な二つの欠陥があり、帰結は別物である。
Defect A — a variable is unassigned on some execution path. This is what creates a latch. The tool must preserve the variable's previous value on that path, and a value that must be preserved across an evaluation is, by definition, storage. This happens regardless of what the sensitivity list says.缺陷 A — 某個變數在某條執行路徑上沒有被賦值。這才是造出鎖存器的東西。工具必須在那條路徑上保住該變數的前一個值,而一個必須跨越一次求值被保住的值,依定義就是儲存。不管敏感度列表寫什麼,這件事都會發生。欠陥 A — ある変数がどこかの実行経路で代入されない。ラッチを作るのはこれである。ツールはその経路で変数の前の値を保存しなければならず、評価をまたいで保存されねばならない値は定義上、記憶である。センシティビティリストに何が書いてあろうと、これは起きる。
Defect B — a signal that is read is missing from the sensitivity list. This creates a simulation/synthesis mismatch. Every modern synthesiser builds the combinational function from the block's body and ignores your sensitivity list, warning that it did so. The RTL simulator, however, obeys the list exactly: the block does not re-execute when the missing signal changes, so the output appears to hold its old value — it looks like a latch in the waveform even though the netlist contains none.缺陷 B — 某個被讀取的訊號沒有出現在敏感度列表裡。這造出的是模擬與合成的不匹配。每一個現代合成器都從區塊的本體建立組合函數,並忽略你的敏感度列表,同時警告它這麼做了。然而 RTL 模擬器卻嚴格遵守那張列表:當那個缺席的訊號改變時區塊不會重新執行,於是輸出看起來抱著舊值不放——在波形上它看起來像鎖存器,即使網表裡一個都沒有。欠陥 B — 読まれている信号がセンシティビティリストに無い。これが作るのはシミュレーションと合成の不一致である。現代の合成器はどれもブロックの本体から組合せ関数を構築し、あなたのセンシティビティリストを無視したうえでその旨を警告する。一方 RTL シミュレータはリストに忠実である。欠けている信号が変化してもブロックは再実行されないので、出力は古い値を保持しているように見える。波形の上ではラッチのように見えるが、ネットリストには一つも無い。
The lecture conflates these because, in the tools of its era, Defect B could genuinely produce a latch in the netlist. Today it produces a mismatch instead, which is arguably worse because it is harder to see. The cure for both is the same and has been standard since Verilog-2001: write always @* (or always @(*)), which asks the compiler to build the sensitivity list for you from the block's reads, and assign every variable on every path.本講把這兩者混為一談,是因為在它那個年代的工具裡,缺陷 B 真的可能在網表裡產生一個鎖存器。今天它產生的是不匹配,而那可以說更糟,因為更難看見。兩者的解方相同,而且自 Verilog-2001 以來已是標準做法:寫 always @*(或 always @(*)),請編譯器根據區塊讀到的東西替你建立敏感度列表,並且在每一條路徑上為每一個變數賦值。講義がこの二つを混同しているのは、当時のツールでは欠陥 B が本当にネットリスト上のラッチを生みえたからである。今日それが生むのは不一致であり、見えにくいぶん、おそらくそちらのほうが悪い。どちらに対しても対処は同じで、Verilog-2001 以来の標準である。always @*(または always @(*))と書いてセンシティビティリストの作成をコンパイラに任せ、そのうえですべての経路ですべての変数に代入すること。
Here is the lecture's first always example, the same function once more:以下是本講第一個 always 例子,還是同一個函數:以下は講義の最初の always の例であり、やはり同じ関数である。
module or_nand_3 (enable, x1, x2, x3, x4, y);
input enable, x1, x2, x3, x4;
output y;
reg y; // assigned procedurally, so it must be reg
always @ (enable or x1 or x2 or x3 or x4)
begin
y = ~(enable & (x1 | x2) & (x3 | x4));
end
endmodule
Note reg y. A variable assigned inside a procedural block must be declared reg (or in later standards logic). The keyword is one of Verilog's worst naming decisions: reg does not mean "register". It means "a variable that holds its value between assignments inside a simulator", which is a statement about the simulation data model, not about hardware. This block has no state and synthesises to no state. The slide's own netlist is two gates. Students who believe reg implies a flip-flop go looking for storage that is not there, and — worse — students who believe the converse assume that because they declared something wire they are safe from inferring storage, which is true only because you cannot assign a wire procedurally in the first place.注意 reg y。在程序式區塊內被賦值的變數必須宣告為 reg(或在後來的標準中宣告為 logic)。這個關鍵字是 Verilog 最糟糕的命名決定之一:reg 不代表「暫存器」。它的意思是「一個在模擬器內部於兩次賦值之間保住自己的值的變數」,那是關於模擬資料模型的陳述,不是關於硬體的。這個區塊沒有狀態,也合成不出狀態。投影片自己的網表是兩個閘。相信 reg 蘊涵正反器的學生,會去找一個根本不存在的儲存元件;而更糟的是,相信反向命題的學生會以為自己既然宣告了 wire 就不會推論出儲存,但那之所以成立,只是因為你一開始就無法對 wire 做程序式賦值。reg y に注目してほしい。手続きブロック内で代入される変数は reg(後の規格では logic)と宣言しなければならない。このキーワードは Verilog 最悪の命名の一つである。reg は「レジスタ」を意味しない。意味するのは「シミュレータの中で代入と代入の間、自分の値を保持する変数」であり、これはシミュレーションのデータモデルについての記述であって、ハードウェアについての記述ではない。このブロックは状態を持たず、状態に合成されもしない。スライド自身のネットリストはゲート 2 個である。reg がフリップフロップを含意すると信じる学生は、存在しない記憶素子を探し回る。さらに悪いのは逆を信じる学生で、wire と宣言したから記憶の推論は起きないはずだと考える。それが成り立つのは、そもそも wire には手続き的な代入ができないからにすぎない。
The lecture's second and third always examples both use a for loop with a disable as an early exit. These are worth working through, because the way the tool handles them is genuinely non-obvious.本講第二與第三個 always 例子都用了一個 for 迴圈,並以 disable 作為提前離開的手段。它們值得逐步走過一遍,因為工具處理它們的方式真的不直觀。講義の二つ目と三つ目の always の例は、どちらも for ループと、早期脱出としての disable を使う。ツールの扱い方が本当に直観に反するので、順を追って見る価値がある。
module and4_behav (y, x_in);
parameter word_length = 4;
input [word_length - 1:0] x_in;
output y;
reg y;
integer k;
always @ x_in
begin: check_for_0
y = 1;
for (k = 0; k <= word_length - 1; k = k + 1)
if (x_in[k] == 0)
begin
y = 0;
disable check_for_0; // early exit from the named block
end
end
endmodule
Read it as software and it is a linear scan that stops at the first zero. Read it as hardware and there is no scanning, because there is no time inside a combinational block. The tool unrolls the loop into four copies of the body, then converts the disable into guards: statement $i$ executes only if no earlier iteration took the exit. The unrolled, guarded form is把它當軟體讀,那是一個線性掃描,碰到第一個零就停。把它當硬體讀,則根本沒有掃描,因為組合邏輯區塊內部沒有時間。工具把迴圈展開成本體的四份副本,再把 disable 轉換成守衛條件:第 $i$ 個敘述只在沒有任何更早的迭代走過離開路徑時才執行。展開並加上守衛之後的形式是ソフトウェアとして読めば、これは最初のゼロで止まる線形走査である。ハードウェアとして読めば、走査などない。組合せブロックの内部には時間が無いからだ。ツールはループを本体 4 個分のコピーに展開し、disable をガード条件に変換する。第 $i$ の文は、それより前の反復が脱出していない場合にのみ実行される。展開してガードを付けた形は次のようになる。
// what the tool actually optimises
y = 1;
if (x_in[0] == 0) y = 0;
else if (x_in[1] == 0) y = 0;
else if (x_in[2] == 0) y = 0;
else if (x_in[3] == 0) y = 0;
and since every branch assigns the same constant, the whole chain collapses to $y = x_3x_2x_1x_0$ — a single four-input AND gate, exactly as the slide's figure shows. The disable cost nothing and saved nothing in hardware; it only shortens the simulator's work.而既然每一個分支都賦同一個常數,整條鏈就塌成 $y = x_3x_2x_1x_0$——一個四輸入 AND 閘,正如投影片的圖所示。disable 在硬體上既沒花錢也沒省錢;它只縮短了模擬器的工作量。そしてどの枝も同じ定数を代入するので、チェーン全体は $y = x_3x_2x_1x_0$ に潰れる。スライドの図のとおり、4 入力 AND ゲート 1 個である。disable はハードウェア上で何のコストも生まず、何も節約しない。縮めたのはシミュレータの仕事量だけである。
Three structural facts make this legal. The loop bound is a parameter, so it is known at elaboration. The loop variable k is used only as a constant index after unrolling, so no variable bit select survives. And the disable targets the enclosing named block, which is the only form on the supported list; disabling some other block from here would be an "external disable" and is forbidden.有三個結構上的事實讓這件事合法。迴圈界限是一個參數,所以在展開時就已知。迴圈變數 k 在展開後只被當成常數索引使用,所以沒有可變位元選擇殘留下來。而那個 disable 的目標是包住它的具名區塊,那是支援清單上唯一允許的形式;從這裡去 disable 別的區塊就會是「外部 disable」,是被禁止的。これを合法にしている構造的な事実が三つある。ループの境界はパラメータなのでエラボレーション時に既知である。ループ変数 k は展開後は定数の添字としてしか使われないので、可変ビット選択は残らない。そして disable の対象はそれを囲む名前付きブロックであり、これがサポート一覧で唯一許される形である。ここから別のブロックを disable すれば「外部からの disable」になり、禁止される。
Notice also always @ x_in — a bare signal name with no parentheses, which is legal Verilog-1995 for a single-signal event control. It is complete here because x_in is the only input. It is also exactly the style that becomes a bug the moment someone adds a second input and forgets to extend the list, which is the practical argument for @*.也請注意 always @ x_in——一個沒有括號的裸訊號名稱,這在 Verilog-1995 裡對單一訊號的事件控制是合法的。這裡它是完整的,因為 x_in 是唯一的輸入。而它也正是那種一旦有人加了第二個輸入卻忘了擴充列表就變成錯誤的寫法,這就是支持 @* 的實務理由。always @ x_in にも注目したい。括弧のない裸の信号名で、単一信号のイベント制御としては Verilog-1995 で合法である。x_in が唯一の入力なので、ここでは完全である。しかしこれはまさに、誰かが二つ目の入力を加えてリストの拡張を忘れた瞬間にバグになる書き方であり、@* を支持する実務上の論拠になっている。
The comparator example is more interesting because its branches assign different values:比較器的例子更有趣,因為它的各分支賦的是不同的值:比較器の例はもっと面白い。枝ごとに異なる値を代入するからである。
module comparator (a, b, a_gt_b, a_lt_b, a_eq_b);
parameter size = 2;
input [size:1] a, b;
output a_gt_b, a_lt_b, a_eq_b;
reg a_gt_b, a_lt_b, a_eq_b;
integer k;
always @ (a or b)
begin: compare_loop
for (k = size; k > 0; k = k - 1) // MSB first
begin
if (a[k] != b[k])
begin
a_gt_b = a[k];
a_lt_b = ~a[k];
a_eq_b = 0;
disable compare_loop;
end
end
a_gt_b = 0; // reached only if no bit differed
a_lt_b = 0;
a_eq_b = 1;
end
endmodule
The algorithm is the standard one: scan from the most significant bit; the first position where the operands differ decides the comparison, because that bit outweighs everything below it. If a[k] is the one that is 1, then $a > b$.演算法是標準的那個:從最高有效位元往下掃描;運算元第一個不同的位置決定比較結果,因為那個位元的份量壓過它底下的一切。如果是 a[k] 為 1,那麼 $a > b$。アルゴリズムは標準的なものである。最上位ビットから走査し、オペランドが最初に異なる位置で比較結果が決まる。そのビットの重みが、それより下のすべてを上回るからだ。1 になっているのが a[k] のほうなら、$a > b$ である。
After unrolling with size = 2 the body becomes a two-level priority structure. Let $d_k = a_k \oplus b_k$ be "bit $k$ differs". Then在 size = 2 下展開之後,本體變成一個兩層的優先權結構。令 $d_k = a_k \oplus b_k$ 表示「第 $k$ 位元不同」。則size = 2 で展開すると、本体は 2 段の優先順位構造になる。$d_k = a_k \oplus b_k$ を「ビット $k$ が異なる」とおくと、次のようになる。
which matches the slide's synthesis figure exactly: XOR gates producing the $d_k$ terms, a NOR or AND producing a_eq_b from their complements, and two small muxes selecting between $a_2$ and $a_1$ under the control of $d_2$. The muxes are the priority: $d_2$ chooses whether bit 2 or bit 1 decides. This is the same mux-chain-equals-priority-structure idea that Section 8 formalises, arriving here as a by-product of the disable.這與投影片的合成圖完全相符:XOR 閘產生各個 $d_k$ 項,一個 NOR 或 AND 由它們的補數產生 a_eq_b,再加上兩個小多工器在 $d_2$ 的控制下於 $a_2$ 與 $a_1$ 之間做選擇。那些多工器就是優先權:$d_2$ 決定由第 2 位元還是第 1 位元來做主。這與第 8 節正式化的「多工器鏈等於優先權結構」是同一個想法,只是在這裡以 disable 的副產品形式出現。これはスライドの合成図と完全に一致する。XOR ゲートが各 $d_k$ の項を作り、NOR か AND がそれらの補数から a_eq_b を作り、$d_2$ の制御で $a_2$ と $a_1$ を選ぶ小さなマルチプレクサが二つ付く。このマルチプレクサこそが優先順位である。$d_2$ がビット 2 とビット 1 のどちらに決めさせるかを選ぶのだ。これは第 8 節で定式化される「マルチプレクサの連鎖は優先順位構造である」という考えと同じものが、ここでは disable の副産物として現れたにすぎない。
Crucially, every one of the three outputs is assigned on every path — either inside the if or in the three statements after the loop — so despite all the control flow, no latch appears. The final three assignments are not dead code; they are the fall-through that makes the description complete. Delete them and you get three latches.最關鍵的是,三個輸出每一個在每一條路徑上都被賦值了——不是在 if 裡面,就是在迴圈之後的那三個敘述裡——所以儘管控制流這麼複雜,並沒有鎖存器出現。最後那三個賦值不是死碼;它們是讓描述完整的「落下去」路徑。把它們刪掉,你就得到三個鎖存器。決定的に重要なのは、三つの出力のいずれもがすべての経路で代入されていることである。if の中か、ループの後ろの三つの文のどちらかで代入される。だから制御の流れがこれだけ入り組んでいてもラッチは現れない。末尾の三つの代入は死んだコードではない。記述を完全にするフォールスルーである。消せばラッチが三つできる。
Functions and tasks are the reuse mechanism inside a module. For synthesis they are inlined — expanded at each call site with the arguments substituted — so they produce no hierarchy and no extra hardware, only less typing and fewer copy-paste errors.function 與 task 是模組內部的重用機制。就合成而言它們會被內聯——在每個呼叫點展開並代入引數——所以它們不產生階層,也不產生額外硬體,只是讓你少打字、少犯複製貼上的錯。function と task はモジュール内部の再利用の仕組みである。合成の立場ではインライン展開され、各呼び出し地点で引数を代入して展開されるので、階層も余分なハードウェアも生まない。生むのはタイプ量の減少とコピー&ペーストのミスの減少だけである。
module or_nand_4 (enable, x1, x2, x3, x4, y);
input enable, x1, x2, x3, x4;
output y;
assign y = or_nand(enable, x1, x2, x3, x4);
function or_nand;
input enable, x1, x2, x3, x4;
begin
or_nand = ~(enable & (x1 | x2)
& (x3 | x4));
end
endfunction
endmodulemodule or_nand_5 (enable, x1, x2, x3, x4, y);
input enable, x1, x2, x3, x4;
output y;
reg y;
always @ (enable or x1 or x2 or x3 or x4)
or_nand(enable, x1, x2, x3, x4, y);
task or_nand;
input enable, x1, x2, x3, x4;
output y;
begin
y = !(enable & (x1 | x2) & (x3 | x4));
end
endtask
endmoduleThe structural differences between the two are what the exam asks about. A function has at least one input and no outputs; it returns a value by assignment to its own name, so it can be called from an expression, including from a continuous assignment. A task may have inputs, outputs and inouts, returns nothing, and must therefore be called as a statement — which means from inside a procedural block, which is why or_nand_5 needs an always block and a reg y that or_nand_4 does not. A function may not contain timing or event control at all; a task may contain them in simulation but must not if it is to be synthesised.兩者之間結構上的差異,正是考試會問的。function 至少有一個輸入,而且沒有輸出;它以對自己名字賦值的方式回傳一個值,所以可以在運算式中被呼叫,包括在連續賦值裡。task 可以有輸入、輸出與 inout,不回傳任何東西,因此必須以敘述的形式被呼叫——也就是說必須從程序式區塊內部呼叫,這就是為什麼 or_nand_5 需要一個 always 區塊與一個 reg y,而 or_nand_4 不需要。function 完全不得含有時序或事件控制;task 在模擬中可以含有它們,但若要被合成就不得含有。両者の構造上の違いこそ試験で問われる点である。function は少なくとも一つの入力を持ち、出力は持たない。自分自身の名前への代入によって値を返すので、連続代入を含め式の中から呼べる。task は入力・出力・inout を持ちうるが何も返さず、したがって文として呼ばねばならない。つまり手続きブロックの中から呼ぶことになり、だからこそ or_nand_5 には always ブロックと reg y が必要で、or_nand_4 には不要なのである。function はタイミングやイベント制御を一切含んではならない。task はシミュレーションでは含みうるが、合成するなら含んではならない。
The restriction the lecture emphasises — no incomplete case statements or conditionals inside a function — is the latch rule wearing a different hat. A function's return variable is a variable like any other; if some path through the function body leaves it unassigned, the inlined code leaves it unassigned, and you get a latch at the call site. Because the function may be called from a continuous assignment, where you would swear no storage could possibly appear, this is a particularly confusing way to meet your first inferred latch.本講強調的那條限制——function 內部不得有不完整的 case 敘述或條件式——其實就是鎖存器規則換了一頂帽子。function 的回傳變數跟其他變數沒兩樣;如果穿過 function 本體的某條路徑讓它未被賦值,那麼內聯之後的程式碼同樣讓它未被賦值,於是你在呼叫點得到一個鎖存器。由於 function 可能是從一個連續賦值裡被呼叫的——在那裡你會發誓不可能冒出任何儲存元件——這會是一種特別令人困惑的初次邂逅推論鎖存器的方式。講義が強調する制限、すなわち function の中に不完全な case 文や条件文を置いてはならない は、ラッチの規則が帽子をかぶり替えたものにすぎない。function の戻り値の変数も他の変数と変わらない。本体を通るどこかの経路でそれが未代入のままなら、インライン展開されたコードでも未代入のままであり、呼び出し地点にラッチができる。しかも function は連続代入の中から呼ばれることがあり、そこには記憶素子など現れようがないと誓いたくなる場所なので、推論ラッチとの初対面としてはとりわけ混乱させられる形になる。
One last detail: the task writes ! where the function writes ~. On the one-bit operand here they agree. The habit of using ! for "not" is a C reflex and it will eventually bite you on a vector, where !v is one bit and ~v is the full width. Use ~ for data, ! for conditions.最後一個細節:task 寫 ! 的地方,function 寫的是 ~。在這裡的一位元運算元上兩者一致。用 ! 當「not」是 C 的反射動作,而它終究會在某個向量上咬你一口,因為 !v 是一個位元,~v 是整個寬度。資料用 ~,條件用 !。最後に細かい点を一つ。task が ! と書く箇所で function は ~ と書いている。ここの 1 ビットのオペランドでは両者は一致する。「not」に ! を使う癖は C の反射であり、いずれベクタで痛い目を見る。!v は 1 ビット、~v は全幅だからだ。データには ~、条件には ! を使うこと。
Problem:題目:問題:
? aggressively?把貫串全節的例子 $y = \overline{e(x_1+x_2)(x_3+x_4)}$ 寫成一張組合邏輯 UDP 表。如果你積極使用 ?,需要幾列?通し例 $y = \overline{e(x_1+x_2)(x_3+x_4)}$ を組合せ論理 UDP の表として書け。? を積極的に使う場合、何行必要か。or_nand_3, the sensitivity list omits x4. Describe precisely what the RTL simulation does, what the netlist does, and what the synthesis log says.在 or_nand_3 裡,敏感度列表漏掉了 x4。精確描述 RTL 模擬會怎麼做、網表會怎麼做,以及合成 log 會說什麼。or_nand_3 でセンシティビティリストから x4 が抜けている。RTL シミュレーションの挙動、ネットリストの挙動、そして合成ログの内容を正確に述べよ。and4_behav without the loop and without the disable, in one line. Does the netlist change?把 and4_behav 改寫成不用迴圈也不用 disable,寫成一行。網表會變嗎?and4_behav をループも disable も使わずに 1 行で書き直せ。ネットリストは変わるか。comparator, delete the three assignments after the loop. What is inferred, how many bits of it, and what would the inferred-devices table show?在 comparator 裡,刪掉迴圈之後的那三個賦值。會推論出什麼、有幾位元,而推論元件表會顯示什麼?comparator でループの後の三つの代入を削除せよ。何が何ビット推論されるか。推論デバイス表には何が表示されるか。or_nand_4 use assign while or_nand_5 cannot?為什麼 or_nand_4 可以用 assign,而 or_nand_5 不行?なぜ or_nand_4 は assign を使えて、or_nand_5 は使えないのか。1. $y$ is 0 only when $e=1$ and at least one of $x_1,x_2$ is 1 and at least one of $x_3,x_4$ is 1. A compact table: 0 ? ? ? ? : 1; (enable low), ? 0 0 ? ? : 1; (first OR low), ? ? ? 0 0 : 1; (second OR low), then four rows of the form 1 1 ? 1 ? : 0;, 1 1 ? ? 1 : 0;, 1 ? 1 1 ? : 0;, 1 ? 1 ? 1 : 0;. Seven rows. Note the UDP limit of ten inputs — this function has five, so it fits, but a 32-bit function could never be a UDP.1. 只有在 $e=1$、且 $x_1,x_2$ 至少一個為 1、且 $x_3,x_4$ 至少一個為 1 時,$y$ 才是 0。一張精簡的表:0 ? ? ? ? : 1;(enable 為低)、? 0 0 ? ? : 1;(第一個 OR 為低)、? ? ? 0 0 : 1;(第二個 OR 為低),然後是四列形如 1 1 ? 1 ? : 0;、1 1 ? ? 1 : 0;、1 ? 1 1 ? : 0;、1 ? 1 ? 1 : 0; 的列。共七列。注意 UDP 最多十個輸入的限制——這個函數有五個,所以塞得下,但一個 32 位元的函數永遠不可能寫成 UDP。1. $y$ が 0 になるのは、$e=1$ かつ $x_1,x_2$ の少なくとも一方が 1 かつ $x_3,x_4$ の少なくとも一方が 1 のときだけである。簡潔な表はこうなる。0 ? ? ? ? : 1;(enable が低)、? 0 0 ? ? : 1;(一つ目の OR が低)、? ? ? 0 0 : 1;(二つ目の OR が低)、続いて 1 1 ? 1 ? : 0;、1 1 ? ? 1 : 0;、1 ? 1 1 ? : 0;、1 ? 1 ? 1 : 0; の四行。合計七行である。UDP の入力は最大 10 個という制限に注意したい。この関数は 5 個なので収まるが、32 ビットの関数は決して UDP にできない。
2. RTL simulation: the block does not re-execute when only x4 changes, so y keeps a stale value until some other listed signal moves. The waveform looks like a latch. Netlist: the synthesiser builds the full combinational function from the body and y tracks x4 immediately — there is no latch, because y is assigned unconditionally on the single path through the block. Log: a warning along the lines of "signal x4 is read in the block but missing from the sensitivity list; assuming complete sensitivity". The two simulations now disagree, and the gate-level one is right.2. RTL 模擬:只有 x4 變動時區塊不會重新執行,所以 y 會抱著舊值不放,直到列表上的其他訊號動了為止。波形看起來像鎖存器。網表:合成器從本體建出完整的組合函數,y 會立刻跟隨 x4——沒有鎖存器,因為在穿過區塊的那唯一一條路徑上 y 是無條件被賦值的。Log:一則類似「signal x4 is read in the block but missing from the sensitivity list; assuming complete sensitivity」的警告。此刻兩個模擬彼此不一致,而對的是閘級的那一個。2. RTL シミュレーション:x4 だけが変化してもブロックは再実行されないので、リストにある別の信号が動くまで y は古い値を保持する。波形はラッチのように見える。ネットリスト:合成器は本体から完全な組合せ関数を構築するので y はただちに x4 に追従する。ラッチはない。ブロックを通る唯一の経路で y が無条件に代入されているからである。ログ:「signal x4 is read in the block but missing from the sensitivity list; assuming complete sensitivity」といった趣旨の警告。二つのシミュレーションは食い違い、正しいのはゲートレベルのほうである。
3. assign y = &x_in; using the reduction AND, or assign y = (x_in == 4'b1111);. The netlist is identical — one four-input AND — because the optimiser reduces all three forms to the same Boolean function. This is the section's whole thesis in one line.3. 用歸約 AND 寫成 assign y = &x_in;,或寫成 assign y = (x_in == 4'b1111);。網表完全相同——一個四輸入 AND——因為最佳化器把三種形式都化成同一個布林函數。這一行就是本節的全部論點。3. リダクション AND を使って assign y = &x_in;、あるいは assign y = (x_in == 4'b1111); と書く。ネットリストは同一で、4 入力 AND 一つである。最適化器が三つの形をすべて同じブール関数に帰着させるからだ。この一行が本節の主張のすべてである。
4. All three outputs become latched, because when no bit differs the loop body never executes and nothing assigns them. With size = 2 that is three one-bit latches. The inferred-devices table would show three rows, each width 1, type "Latch", named a_gt_b_reg, a_lt_b_reg, a_eq_b_reg. It would also be functionally broken: equal operands would report whatever the previous comparison said.4. 三個輸出全都變成被鎖存,因為當沒有任何位元不同時,迴圈本體一次也不執行,沒有東西賦值給它們。在 size = 2 下那就是三個一位元鎖存器。推論元件表會顯示三列,每列寬度 1、型別「Latch」,名稱為 a_gt_b_reg、a_lt_b_reg、a_eq_b_reg。它在功能上也壞了:運算元相等時,回報的會是前一次比較所說的任何東西。4. 三つの出力すべてがラッチ化される。どのビットも異ならない場合、ループ本体は一度も実行されず、何も代入しないからである。size = 2 なら 1 ビットのラッチが三つである。推論デバイス表には幅 1、種別「Latch」の行が三つ、a_gt_b_reg、a_lt_b_reg、a_eq_b_reg という名で現れる。機能的にも壊れている。オペランドが等しいとき、前回の比較が言った内容がそのまま報告されてしまう。
5. A function returns a value and can therefore appear in an expression, which is what a continuous assignment needs. A task returns nothing and passes results through output arguments, so it must be called as a statement; statements live only inside procedural blocks. That forces the always block, which in turn forces y to be reg.5. function 回傳一個值,因此可以出現在運算式裡,而那正是連續賦值所需要的。task 不回傳任何東西,結果透過 output 引數傳出,所以它必須以敘述的形式被呼叫;而敘述只能住在程序式區塊裡。這就逼出了 always 區塊,而 always 區塊又逼得 y 必須是 reg。5. function は値を返すので式の中に書ける。連続代入が必要とするのはまさにそれである。task は何も返さず、結果は output 引数で渡すので、文として呼ぶしかない。そして文は手続きブロックの中にしか置けない。そのため always ブロックが必要になり、ひいては y が reg でなければならなくなる。
The multiplexer is the single most important structure in RTL, for a reason that is easy to miss: it is the hardware realisation of choice, and every if, every case, and every ?: in your design becomes one. Control logic for a multiplexed datapath — the slide's title — means exactly this: the datapath holds the adders and registers, and the control logic is the collection of muxes that decides which operands reach them this cycle. Get the mux coding right and most of RTL falls into place.多工器是 RTL 中最重要的單一結構,理由很容易被忽略:它是選擇這件事的硬體實現,而你設計裡的每一個 if、每一個 case、每一個 ?: 最後都會變成一個多工器。投影片標題說的「多工化資料路徑的控制邏輯」,講的正是這件事:資料路徑裡放著加法器與暫存器,而控制邏輯就是那一堆決定這個週期哪些運算元送進去的多工器。把多工器的寫法弄對,RTL 的大半就各就各位了。マルチプレクサは RTL で最も重要な単一の構造である。その理由は見落とされやすい。これは選択のハードウェア実現であり、設計中のあらゆる if、case、?: は最後にはこれになる。スライドの題にある「多重化されたデータパスの制御論理」が指すのはまさにこれである。データパスには加算器とレジスタが並び、制御論理とは、今サイクルどのオペランドがそこへ届くかを決めるマルチプレクサの集まりである。マルチプレクサの書き方を正せば、RTL の大半は所定の位置に収まる。
The lecture describes a 4-channel, 4-bit mux three ways. The three modules are syn1_mux_4bits (nested conditional operator in a continuous assignment), syn2_mux_4bits (case in an always block) and syn3_mux_4bits (if/else if chain in an always block). The slide notes after the third that the synthesis result is the same as the second.本講以三種方式描述一個四通道、四位元的多工器。三個模組是 syn1_mux_4bits(連續賦值裡的巢狀條件運算子)、syn2_mux_4bits(always 區塊裡的 case)與 syn3_mux_4bits(always 區塊裡的 if/else if 串)。投影片在第三個之後註明,合成結果與第二個相同。講義は 4 チャネル 4 ビットのマルチプレクサを三通りに記述する。三つのモジュールは syn1_mux_4bits(連続代入の中の入れ子条件演算子)、syn2_mux_4bits(always ブロック内の case)、syn3_mux_4bits(always ブロック内の if/else if の連鎖)である。スライドは三つ目のあとに、合成結果は二つ目と同じだと注記している。
module syn1_mux_4bits (y, sel, a, b, c, d);
input [3:0] a, b, c, d;
input [1:0] sel;
output [3:0] y;
assign y = (sel == 0) ? a :
(sel == 1) ? b :
(sel == 2) ? c :
(sel == 3) ? d : 4'bx;
endmodulemodule syn2_mux_4bits (y, sel, a, b, c, d);
input [3:0] a, b, c, d;
input [1:0] sel;
output [3:0] y;
reg [3:0] y;
always @ (sel or a or b or c or d)
case (sel)
0: y = a;
1: y = b;
2: y = c;
3: y = d;
default: y = 4'bx;
endcase
endmodulemodule syn3_mux_4bits (y, sel, a, b, c, d); // if/else if version
input [3:0] a, b, c, d;
input [1:0] sel;
output [3:0] y;
reg [3:0] y;
always @ (sel or a or b or c or d)
if (sel == 0) y = a;
else if (sel == 1) y = b;
else if (sel == 2) y = c;
else if (sel == 3) y = d;
else y = 4'bx;
endmodule
The conditional-operator version reads as a priority chain: "if sel is 0 take a, otherwise if sel is 1 take b, otherwise …". The if chain reads the same way. Yet both produce the same flat four-input multiplexer as the case, with no priority structure at all. This surprises people, and the reason is the rule the lecture states in Section 8: when the branch conditions are mutually exclusive, the tool detects it and builds a mux, not a chain.條件運算子版本讀起來像一條優先權鏈:「如果 sel 是 0 就取 a,否則如果 sel 是 1 就取 b,否則……」。if 串讀起來也一樣。然而兩者都產生與 case 相同的扁平四輸入多工器,完全沒有優先權結構。這會讓人驚訝,而理由就是本講在第 8 節陳述的那條規則:當各分支條件互斥時,工具會偵測到,並建出一個多工器而不是一條鏈。条件演算子版は優先順位チェーンのように読める。「sel が 0 なら a、そうでなく 1 なら b、そうでなければ…」。if の連鎖も同じように読める。それでも両者は case と同じ平坦な 4 入力マルチプレクサを生み、優先順位構造はまったく現れない。これは驚かれるところだが、理由は講義が第 8 節で述べる規則にある。分岐条件が互いに排他的であれば、ツールはそれを検出し、チェーンではなくマルチプレクサを作る。
Detecting mutual exclusivity here is easy, because the conditions are equality tests of one variable against distinct constants. Two of them cannot be true at once; this is provable by inspection and every tool does it. What the optimiser then builds is the canonical selection function在這裡偵測互斥性很容易,因為那些條件是把同一個變數拿去跟相異常數做相等比較。其中任兩個不可能同時成立;這用看的就能證明,而且每個工具都做得到。接著最佳化器建出來的,是標準的選擇函數ここで排他性を検出するのは容易である。条件は一つの変数を相異なる定数と等価比較しているからだ。そのうち二つが同時に真になることはありえず、これは一見して証明でき、どのツールもそれを行う。そのうえで最適化器が作るのは、正準的な選択関数である。
which is a two-level AND-OR per output bit, fed by a 2-to-4 decode of sel that is extracted once and shared by all four bits. That shared decode is visible in the slide's synthesis figure: two inverters and a handful of NOR gates at the bottom left, feeding four identical per-bit slices.這是每個輸出位元一個兩層的 AND-OR,由一個對 sel 的 2 對 4 解碼餵進去,而那個解碼只抽出一次、四個位元共用。那個共用的解碼在投影片的合成圖裡看得見:左下角的兩個反相器與一小把 NOR 閘,餵進四個一模一樣的每位元切片。これは出力ビットごとの 2 段 AND-OR であり、sel の 2 対 4 デコードから駆動される。そのデコードは一度だけ抽出され、4 ビットすべてで共有される。共有されたデコードはスライドの合成図に見てとれる。左下のインバータ 2 個といくつかの NOR ゲートが、同一のビットスライス 4 本に供給している。
The 4'bx in the final branch deserves attention. Since sel is two bits, all four values are already covered and the default is logically unreachable. Writing it anyway serves two purposes. It makes the statement syntactically complete, which removes any question of latch inference and silences lint. And by naming the unreachable output x rather than a concrete value, it tells the optimiser "I do not care what you produce here", which costs nothing when the branch is unreachable but matters a great deal when it is not — see Section 10.最後一個分支的 4'bx 值得注意。既然 sel 是兩位元,四個值早就都被涵蓋了,這個 default 在邏輯上不可到達。即使如此還是把它寫出來,有兩個用處。它讓敘述在語法上是完整的,這就排除了任何鎖存器推論的疑慮,也讓 lint 閉嘴。而且把那個不可到達的輸出命名為 x 而非一個具體值,等於告訴最佳化器「我不在意你在這裡產生什麼」,當分支不可到達時這不花任何代價,但當它可到達時關係重大——見第 10 節。最後の枝の 4'bx は注目に値する。sel は 2 ビットなので四つの値はすでにすべて網羅されており、この default は論理的に到達不能である。それでも書く理由は二つある。文を構文的に完全にし、ラッチ推論の疑いを消して lint を黙らせること。そして到達不能な出力を具体値ではなく x と名づけることで、最適化器に「ここで何を出そうと構わない」と伝えること。枝が到達不能ならこれは無料だが、到達可能なら大きな意味を持つ。第 10 節を参照のこと。
The slides present each mux twice, side by side, labelled "simulation efficient" and "simulation and synthesis friendly". The left-hand version looks like this:投影片把每個多工器並排呈現兩次,分別標示「simulation efficient」與「simulation and synthesis friendly」。左邊那個版本長這樣:スライドは各マルチプレクサを二つ並べ、「simulation efficient」と「simulation and synthesis friendly」と見出しを付けている。左側の版は次のようなものである。
always @ (sel) // sensitive to sel only case (sel) 0: assign y = a; // procedural continuous assignment 1: assign y = b; 2: assign y = c; 3: assign y = d; default: assign y = 4'bx; endcase
The trick is the procedural continuous assignment. An ordinary procedural assignment y = a samples a once, at the moment the statement executes. A PCA assign y = a establishes a continuous drive that persists after the block finishes, so y keeps tracking a until a later deassign or a later PCA overrides it. That is why the sensitivity list can contain only sel: the data inputs are followed continuously by the drive, not by re-execution of the block.訣竅在於程序式連續賦值。一個普通的程序式賦值 y = a 只在敘述執行的那一刻取樣 a 一次。而一個 PCA assign y = a 建立的是一個在區塊結束後仍然持續的連續驅動,所以 y 會一直跟著 a,直到後來的 deassign 或後來的 PCA 蓋過它為止。這就是為什麼敏感度列表裡可以只放 sel:那些資料輸入是被驅動持續跟隨的,不是靠區塊重新執行。仕掛けは手続き的連続代入にある。ふつうの手続き代入 y = a は、文が実行された瞬間に a を一度サンプルするだけである。一方 PCA の assign y = a はブロック終了後も続く連続的な駆動を確立するので、後の deassign か後の PCA が上書きするまで y は a に追従し続ける。だからこそセンシティビティリストには sel だけを書けばよい。データ入力はブロックの再実行ではなく駆動によって連続的に追われているからだ。
The efficiency claim is real for an event-driven simulator. With full sensitivity, every transition on any of a, b, c, d wakes the block, re-evaluates the case, and assigns — four times the event traffic for no change in result when the unselected inputs move. With the PCA version only sel transitions wake the block. On a large datapath with wide buses this was a measurable win in 1995.對事件驅動的模擬器而言,效率的宣稱是真的。若敏感度完整,a、b、c、d 任何一個的轉態都會喚醒區塊、重新求值 case、再賦值——當未被選中的輸入動的時候,結果毫無改變卻付出四倍的事件流量。用 PCA 版本,就只有 sel 的轉態會喚醒區塊。在 1995 年,對一條有寬匯流排的大型資料路徑而言,這是可測量的收益。イベント駆動のシミュレータにとって、効率の主張は本当である。センシティビティが完全なら、a・b・c・d のどれが遷移してもブロックが起き、case を再評価し、代入する。選ばれていない入力が動いたときも、結果は変わらないのにイベントの量は 4 倍になる。PCA 版なら sel の遷移だけがブロックを起こす。1995 年当時、バス幅の広い大きなデータパスではこれは計測できる利得だった。
Procedural continuous assignment is deprecated. Many current synthesisers reject it outright and the rest warn. Its interaction with ordinary procedural assignments to the same variable is a well-known source of confusion, and the simulation advantage it bought has been erased by compiled-code simulators that optimise sensitivity automatically. Recognise it in an exam question — the examinable fact is that the synthesised result is the same mux — and then write always @* like everyone else. The right-hand "simulation and synthesis friendly" column of the slide is the one to copy.程序式連續賦值已被廢止。許多現行的合成器直接拒收它,其餘的會警告。它與對同一變數的普通程序式賦值之間的交互作用,是眾所周知的混亂來源,而它當年買到的模擬優勢,已經被會自動最佳化敏感度的編譯式模擬器抹平了。在考題裡認得它就好——可考的事實是合成出來的結果是同一個多工器——然後像其他人一樣寫 always @*。投影片右邊那欄「simulation and synthesis friendly」才是該抄的。手続き的連続代入は廃止された。現行の合成器の多くはこれを拒否し、残りも警告する。同じ変数へのふつうの手続き代入との相互作用はよく知られた混乱の種であり、当時買えたシミュレーション上の利点も、センシティビティを自動で最適化するコンパイル型シミュレータによって消し飛んだ。試験問題では見分けられればよく、問われる事実は合成結果は同じマルチプレクサであるという点である。そのうえで、他の皆と同じく always @* と書けばよい。スライド右側の「simulation and synthesis friendly」の欄こそ写すべきものである。
The slide shows two different synthesis results for what is, functionally, the same mux. The conditional-operator version came out as four AND-OR-invert groups with output inverters and some small muxes; the case version came out as four explicit mux blocks with a shared NOR-based decoder. Both are correct and the difference is a mapping artefact: the tool was free to choose, and the two runs landed in different local optima. The slide's footnote about results depending on the tool is doing honest work here.投影片對功能上同一個多工器展示了兩個不同的合成結果。條件運算子版本出來的是四組 AND-OR-invert 加上輸出反相器與一些小多工器;case 版本出來的是四個明確的 mux 區塊,加上一個共用的 NOR 式解碼器。兩者都正確,差別是映射的產物:工具有選擇的自由,而兩次執行落進了不同的區域最佳解。投影片那句「結果取決於工具」的註腳,在這裡是在誠實地幹活。スライドは、機能的には同一のマルチプレクサについて二つの異なる合成結果を示す。条件演算子版は AND-OR-invert が四組、出力インバータといくつかの小さなマルチプレクサを伴って現れた。case 版は明示的な mux ブロックが四つと、共有された NOR ベースのデコーダになった。どちらも正しく、違いはマッピングの産物である。ツールには選ぶ自由があり、二度の実行が別々の局所最適に着地したのだ。「結果はツールに依存する」というスライドの脚注は、ここで正直に働いている。
What is stable across tools is the cost model, and it is worth having at your fingertips.跨工具穩定的是成本模型,值得隨手就能調出來。ツールをまたいで安定しているのはコストモデルであり、手元にすぐ出せるようにしておく価値がある。
| Structure結構構造 | Area, per output bit面積,每個輸出位元面積(出力 1 ビットあたり) | Delay from data in從資料輸入算起的延遲データ入力からの遅延 | Delay from select in從選擇線輸入算起的延遲選択入力からの遅延 |
|---|---|---|---|
| 2:1 mux2:1 多工器2:1 マルチプレクサ | ~2.3 GE約 2.3 GE約 2.3 GE | 1 level1 層1 段 | 1 level (+1 for the select inverter)1 層(選擇線反相器再加 1)1 段(選択信号のインバータで +1) |
| 4:1 mux, tree of three 2:14:1 多工器,三個 2:1 組成的樹4:1 マルチプレクサ(2:1 を 3 個のツリー) | ~7 GE約 7 GE約 7 GE | 2 levels2 層2 段 | 2 levels2 層2 段 |
| 4:1 mux, one-hot AND-OR4:1 多工器,one-hot AND-OR4:1 マルチプレクサ(ワンホット AND-OR) | ~8–9 GE + shared decode約 8–9 GE,外加共用的解碼約 8〜9 GE + 共有デコード | 2 levels2 層2 段 | 3 levels (decode + AND + OR)3 層(解碼 + AND + OR)3 段(デコード + AND + OR) |
| 4-deep priority chain of 2:1四層深、由 2:1 組成的優先權鏈2:1 を 4 段重ねた優先順位チェーン | ~7 GE約 7 GE約 7 GE | 1–3 levels depending on input1–3 層,視輸入而定入力により 1〜3 段 | 1–3 levels1–3 層1〜3 段 |
Two practical consequences follow. First, a 4:1 mux costs roughly three times a 2:1 mux, not twice: selection cost grows with the number of inputs, and an $N$:1 mux built as a balanced tree costs about $N-1$ two-input muxes with $\log_2 N$ levels. A 16:1 mux is fifteen 2:1 muxes and four levels — about 35 GE per bit, so a 32-bit 16:1 mux is over a thousand gate equivalents, which is why large crossbars are expensive and why designers go to some trouble to avoid them.有兩個實務後果。第一,一個 4:1 多工器的代價大約是 2:1 多工器的三倍,不是兩倍:選擇的代價隨輸入數成長,而一個以平衡樹建成的 $N$:1 多工器,大約要 $N-1$ 個二輸入多工器、$\log_2 N$ 層。一個 16:1 多工器是十五個 2:1 多工器、四層——每位元約 35 GE,所以一個 32 位元的 16:1 多工器超過一千個閘當量,這就是為什麼大型交叉開關很貴,也是為什麼設計者寧可費一番功夫避開它們。実務上の帰結が二つある。第一に、4:1 マルチプレクサのコストは 2:1 の 2 倍ではなく、およそ 3 倍である。選択のコストは入力数とともに増え、平衡木として作った $N$:1 マルチプレクサは 2 入力マルチプレクサ約 $N-1$ 個、$\log_2 N$ 段を要する。16:1 なら 2:1 が 15 個で 4 段、1 ビットあたり約 35 GE であり、32 ビットの 16:1 マルチプレクサは 1000 ゲート等価を超える。大きなクロスバーが高価である理由であり、設計者がそれを避けるために骨を折る理由でもある。
Second, the select path and the data path have different delays, and which one matters depends on your architecture. If sel comes straight from a register and the data comes from a long arithmetic chain, the data path dominates and a tree is right. If sel is itself computed by a slow comparison and the data is already stable, you want the select decode moved as early as possible — which is the same insight that makes resource sharing a delay question as well as an area question in Section 11.第二,選擇路徑與資料路徑的延遲不同,而哪一個重要取決於你的架構。如果 sel 直接來自一個暫存器,而資料來自一條長長的算術鏈,那麼資料路徑佔主導,用樹是對的。如果 sel 本身是由一個很慢的比較算出來的,而資料早就穩定了,那你會希望選擇線的解碼盡可能提早——這與第 11 節讓資源共用同時成為延遲問題與面積問題的,是同一個洞見。第二に、選択経路とデータ経路では遅延が異なり、どちらが効くかはアーキテクチャ次第である。sel がレジスタから直接来て、データが長い算術チェーンから来るならデータ経路が支配的で、ツリーが正解である。sel 自体が遅い比較で計算され、データはとうに安定しているなら、選択のデコードをできるだけ早い段階へ移したい。これは第 11 節でリソース共有を面積の問題であると同時に遅延の問題にしている洞察と同じものである。
Problem: For each block, say whether the synthesised structure is a flat mux or a priority chain, and give the number of 2:1-mux levels on the longest path. Assume all signals are one bit except where stated.題目:對每一個區塊,說出合成出來的結構是扁平多工器還是優先權鏈,並給出最長路徑上 2:1 多工器的層數。除非另有說明,假設所有訊號都是一位元。問題:各ブロックについて、合成される構造が平坦なマルチプレクサか優先順位チェーンかを述べ、最長経路上の 2:1 マルチプレクサの段数を示せ。特に断らない限り、すべての信号は 1 ビットとする。
// (a) always @* case (sel) 2'b00: y=a; 2'b01: y=b; 2'b10: y=c; 2'b11: y=d; endcase // (b) always @* begin if (req_a) y = a; else if (req_b) y = b; else if (req_c) y = c; else y = d; end // (c) always @* begin if (sel == 2'd0) y = a; else if (sel == 2'd1) y = b; else if (sel == 2'd2) y = c; else y = d; end // (d) always @* begin y = d; if (req_c) y = c; if (req_b) y = b; if (req_a) y = a; end
(a) Flat 4:1 mux. Conditions are equality tests against distinct constants, hence mutually exclusive. Two levels.(a) 扁平 4:1 多工器。條件是對相異常數的相等比較,因此互斥。兩層。(a) 平坦な 4:1 マルチプレクサ。条件は相異なる定数との等価比較なので排他的である。2 段。
(b) Priority chain. req_a, req_b, req_c are independent signals that may all be high at once, so the tool must honour the order. Three levels, req_a closest to the output.(b) 優先權鏈。req_a、req_b、req_c 是可能同時為高的獨立訊號,所以工具必須尊重順序。三層,req_a 離輸出最近。(b) 優先順位チェーン。req_a・req_b・req_c は同時に高になりうる独立の信号なので、ツールは順序を守らねばならない。3 段で、req_a が出力に最も近い。
(c) Flat 4:1 mux — same as (a), written as an if chain. This is the lecture's point about syn3_mux_4bits. Two levels. Note the trailing else rather than else if (sel == 2'd3): either works here, but the plain else also guarantees completeness.(c) 扁平 4:1 多工器——與 (a) 相同,只是寫成 if 串。這就是本講關於 syn3_mux_4bits 想說的。兩層。注意結尾是單純的 else 而不是 else if (sel == 2'd3):兩種在這裡都能用,但單純的 else 還額外保證了完整性。(c) 平坦な 4:1 マルチプレクサ。(a) と同じものを if の連鎖で書いただけである。講義が syn3_mux_4bits について言いたいのはこの点である。2 段。末尾が else if (sel == 2'd3) ではなく素の else であることに注意。ここではどちらでも動くが、素の else は完全性も保証する。
(d) Priority chain again, and the same structure as (b) — the later assignment wins, so req_a has the highest priority exactly as in (b). This "default then override" style is a legitimate and very readable way to write a priority encoder, and because the first statement assigns unconditionally it is latch-free by construction. Three levels.(d) 又是優先權鏈,而且結構與 (b) 相同——後面的賦值贏,所以 req_a 的優先權最高,恰與 (b) 相同。這種「先給預設再覆蓋」的寫法是一種正當而且非常好讀的優先權編碼器寫法,而且因為第一個敘述是無條件賦值,它在構造上就不會有鎖存器。三層。(d) これも優先順位チェーンであり、しかも(b) と同じ構造である。後の代入が勝つので、req_a の優先順位が最も高く、(b) とまったく同じになる。この「まずデフォルト、あとで上書き」のスタイルは、優先順位エンコーダの書き方として正当で非常に読みやすく、最初の文が無条件代入なので構成上ラッチが生じない。3 段。
A useful check: (b) and (d) describe identical functions and should produce identical netlists. If a tool gives you different areas for them, you are looking at optimisation noise, not a semantic difference.一個有用的檢查:(b) 與 (d) 描述的是相同的函數,應該產生相同的網表。如果某個工具給出不同的面積,你看到的是最佳化雜訊,不是語意差異。有用な確認を一つ。(b) と (d) は同じ関数を記述しており、同じネットリストになるはずである。ツールが両者に異なる面積を返したなら、それは意味の違いではなく最適化のノイズを見ているのである。
If you remember one thing from Lecture 5, make it this section. Inferred latches are the most common RTL defect in student projects and one of the more common in industry, they are trivially avoidable, and they cause damage out of all proportion to the typo that produces them. This section works through where the latch comes from, which exact code shapes produce one, what the resulting netlist contains, why a latch in a nominally combinational block is a disaster for static timing analysis rather than merely untidy, and the four standard cures with their respective caveats.如果你只從第 5 講記住一件事,就記住這一節。推論鎖存器是學生專題中最常見的 RTL 缺陷,在業界也名列前茅,它們極易避免,而它們造成的損害與產生它們的那個小筆誤完全不成比例。本節會一路走過:鎖存器從哪裡來、確切是哪些程式碼形狀會產生一個、結果的網表裡有什麼、為什麼一個名義上組合邏輯區塊裡的鎖存器對靜態時序分析是災難而不只是不整潔,以及四種標準解法與各自的但書。第 5 講から一つだけ覚えるなら、この節にしてほしい。推論ラッチは学生のプロジェクトで最も多い RTL の欠陥であり、産業界でも上位に入る。避けるのは造作もないのに、それを生む些細な打ち間違いとはまるで釣り合わない損害をもたらす。本節では、ラッチがどこから来るのか、正確にどのコードの形がそれを生むのか、できあがるネットリストに何が入るのか、名目上は組合せのブロックにあるラッチがなぜ単に不潔なだけでなく静的タイミング解析にとって災厄なのか、そして四つの標準的な対処とそれぞれの但し書きを順に見ていく。
The lecture's definition is: a case statement or conditional that does not include all possible cases or conditions is incompletely specified, and incompletely specified statements may lead to the synthesis of unwanted latches. That is accurate but it describes the symptom rather than the mechanism. The mechanism is a single requirement that falls out of Verilog's own semantics.本講的定義是:一個沒有涵蓋所有可能情況或條件的 case 敘述或條件式是不完整指定的,而不完整指定的敘述可能導致合成出非預期的鎖存器。這說法正確,但它描述的是症狀而不是機制。機制是一個從 Verilog 自身語意直接掉出來的單一要求。講義の定義はこうである。ありうるすべての場合や条件を含まない case 文や条件文は不完全に指定されているといい、不完全に指定された文は意図しないラッチの合成につながりうる。これは正確だが、述べているのは機構ではなく症状である。機構は Verilog 自身の意味論からそのまま落ちてくる一つの要請である。
A reg in Verilog holds its value until something assigns it; therefore, if there exists any assignment of values to the block's inputs for which the block executes without assigning variable v, then v must retain its previous value for that input combination — and a signal that must retain a previous value cannot be a wire, so the synthesiser inserts a level-sensitive storage element whose enable is the OR of all the conditions under which v is assigned.Verilog 的 reg 會保住它的值,直到有東西賦值給它;因此,如果存在任何一組輸入值,使得這個區塊執行完畢卻沒有賦值給變數 v,那麼對那組輸入組合而言 v 就必須保住它的前一個值——而一個必須保住前一個值的訊號不可能是一條 wire,於是合成器插入一個準位敏感的儲存元件,它的致能是「所有有賦值給 v 的條件」的 OR。Verilog の reg は何かが代入するまで値を保持する。したがって、ブロックが変数 v に代入せずに実行し終えるような入力値の割り当てが一つでも存在するなら、その入力の組合せに対して v は前の値を保持しなければならない。前の値を保持しなければならない信号は wire ではありえないので、合成器はレベルセンシティブな記憶素子を挿入する。そのイネーブルは、v に代入が行われるすべての条件の OR である。
Everything follows from that. The latch's data input is the multiplexed value that the assigned paths produce. The latch's enable is the Boolean condition "some path assigned v this time". The latch is transparent when the enable is high and holds when it is low. And the number of latch bits equals the width of v — a 32-bit variable with one uncovered path gives you 32 latch cells, not one.其餘一切都從這裡推出來。鎖存器的資料輸入是那些有賦值的路徑所產生的、經過多工的值。鎖存器的致能是布林條件「這次有某條路徑賦值給 v」。致能為高時鎖存器透通,為低時保持。而鎖存器的位元數等於 v 的寬度——一個 32 位元變數若有一條路徑沒涵蓋,給你的是 32 個鎖存器元件,不是一個。あとはすべてここから導かれる。ラッチのデータ入力は、代入が行われる経路が生む多重化された値である。ラッチのイネーブルは「今回どこかの経路が v に代入した」というブール条件である。イネーブルが高ければラッチは透過し、低ければ保持する。そしてラッチのビット数は v の幅に等しい。32 ビットの変数に未カバーの経路が一つあれば、得られるのはラッチセル 32 個であって 1 個ではない。
It is worth stressing the quantifier in that sentence: there exists any assignment of input values. The tool reasons over the Boolean space of inputs, not over the paths your testbench happens to exercise. A branch you believe is unreachable because of a protocol guarantee elsewhere in the chip is, to the synthesiser, perfectly reachable, and it will build the latch. This is why "but that opcode never happens" is not a defence, and why the correct response is to tell the tool it never happens — which is what a default: y = 'bx; does.那句話裡的量詞值得強調:存在任何一組輸入值。工具是在輸入的布林空間上推理,不是在你的測試平台碰巧走過的路徑上推理。某個你相信因為晶片別處的協定保證而不可到達的分支,在合成器眼裡完全可達,它就會把鎖存器造出來。這就是為什麼「可是那個 opcode 根本不會出現」不是辯護理由,也是為什麼正確的回應是去告訴工具它不會出現——那正是 default: y = 'bx; 所做的事。この文の量化子は強調しておきたい。入力値の割り当てが一つでも存在すればである。ツールは入力のブール空間上で推論するのであって、テストベンチがたまたま踏んだ経路の上で推論するのではない。チップの別の場所のプロトコル保証によって到達不能だとあなたが信じている分岐も、合成器にとっては完全に到達可能であり、ツールはラッチを作る。「でもその opcode は決して来ない」が弁明にならない理由であり、正しい応答がそれをツールに伝えることである理由でもある。default: y = 'bx; がやっているのはまさにそれだ。
There are five distinct shapes. Learning to recognise them by silhouette is faster than reasoning about each block from first principles.共有五種不同的形狀。學會用剪影認出它們,比每次都從頭推理一個區塊要快得多。異なる形が五つある。シルエットで見分けられるようになるほうが、毎回ブロックを原理から推論するより速い。
if with no else形狀 1 — 沒有 else 的 if形 1 — else のない ifalways @* if (en) q = d; // en == 0 leaves q unassigned
The canonical case. The enable is en; the data is d. This is also the standard way to write an intentional D latch, which is why the tool does not treat it as an error: the construct is legitimate, it is only usually unintended.最經典的一個。致能是 en;資料是 d。這也是寫一個刻意的 D 鎖存器的標準方式,這就是為什麼工具不把它當成錯誤:這個構件是正當的,只是通常不是你想要的。典型例である。イネーブルは en、データは d である。これは意図的な D ラッチを書く標準的な方法でもあり、だからこそツールはこれをエラー扱いしない。構文としては正当で、ただたいてい意図されていないだけである。
if/else if chain with no final else形狀 2 — 沒有最後 else 的 if/else if 串形 2 — 最後の else がない if/else if の連鎖always @ (a1 or a2)
begin
if ({a2, a1} == 2'b11) y = 1;
else if ({a2, a1} == 2'b01) y = 0;
else if ({a2, a1} == 2'b10) y = 0; // 2'b00 is not covered
end
This is the slide's incomplete_and module. Three of four possible values of the concatenation are listed; {a2,a1} == 2'b00 falls off the end. The intended function is an AND gate, so the correct result would be y = 0 for that input — but the tool has no way to know that, and builds a latch whose enable is $a_2 + a_1$. The slide's figure shows exactly that: two inverters, a NOR producing the data term, a NAND producing the enable, and a latrnb_a cell. The repair is one word: make the last branch a plain else.這是投影片的 incomplete_and 模組。那個串接的四個可能值裡列了三個;{a2,a1} == 2'b00 從末端掉了出去。原本想要的函數是一個 AND 閘,所以對那個輸入正確的結果應該是 y = 0——但工具沒有辦法知道這件事,於是造了一個致能為 $a_2 + a_1$ 的鎖存器。投影片的圖畫的就是這個:兩個反相器、一個產生資料項的 NOR、一個產生致能的 NAND,加上一個 latrnb_a 元件。修法只要一個字:把最後一個分支改成單純的 else。これはスライドの incomplete_and モジュールである。連接の取りうる四つの値のうち三つが列挙され、{a2,a1} == 2'b00 が末尾からこぼれ落ちる。意図された関数は AND ゲートなので、その入力に対する正しい結果は y = 0 のはずだが、ツールにそれを知る術はなく、イネーブルが $a_2 + a_1$ のラッチを作る。スライドの図はまさにそれを描いている。インバータ 2 個、データ項を作る NOR、イネーブルを作る NAND、そして latrnb_a セルである。修正は一語で済む。最後の分岐を素の else にすればよい。
case without default and without all items形狀 3 — 沒有 default、也沒有列出所有項目的 case形 3 — default もなく全項目も列挙しない casealways @ (sel_a or sel_b or data_a or data_b)
case ({sel_a, sel_b})
2'b10: y_out = data_a;
2'b01: y_out = data_b;
endcase // 2'b00 and 2'b11 are not covered
The slide's mux_latch. Two of four selector values are listed. The enable is $\text{sel\_a} \oplus \text{sel\_b}$ — true exactly when one and only one select is high — and the slide's own caption states the consequence: "When (sel_a, sel_b) = 00, 11, y_out keeps the old value."投影片的 mux_latch。四個選擇值裡列了兩個。致能是 $\text{sel\_a} \oplus \text{sel\_b}$——恰在有且僅有一個選擇線為高時為真——而投影片自己的圖說寫出了後果:「When (sel_a, sel_b) = 00, 11, y_out keeps the old value.」スライドの mux_latch である。四つのセレクタ値のうち二つが列挙されている。イネーブルは $\text{sel\_a} \oplus \text{sel\_b}$、すなわち選択線がちょうど一本だけ高のときに真となる。そしてスライド自身のキャプションが帰結を述べている。「When (sel_a, sel_b) = 00, 11, y_out keeps the old value.」
case covering all items of a wider expression than you think形狀 4 — case 涵蓋了一個比你以為的更寬的運算式的所有項目形 4 — 思っているより幅の広い式の全項目を case が覆っている場合reg [1:0] op;
always @* case (op)
2'b00: y = a; 2'b01: y = b; 2'b10: y = a & b;
endcase // 2'b11 missing: one value, still a latch
A near-miss that students make constantly: three of four values listed. It is a latch for one input combination out of four, and it will pass most testbenches.學生不斷犯的一種差一點點的錯:四個值裡列了三個。四種輸入組合中有一種會產生鎖存器,而且它會通過大部分的測試平台。学生が絶えずやる惜しい誤りである。四つの値のうち三つを列挙している。四通りの入力の組合せのうち一つでラッチになり、しかもたいていのテストベンチは通ってしまう。
always @* begin
case (state)
IDLE: begin start = 0; busy = 0; done = 0; end
RUN: begin start = 0; busy = 1; end // done not assigned here
FIN: begin busy = 0; done = 1; end // start not assigned here
default: begin start = 0; busy = 0; done = 0; end
endcase
end
This is by far the most common shape in real code and the hardest to spot by eye, because the case is complete. The incompleteness is per-variable. busy is assigned in all four arms and is fine. done is unassigned in RUN, so it latches with enable $\overline{\text{RUN}}$. start is unassigned in FIN, so it latches with enable $\overline{\text{FIN}}$. One complete case, two latches. Any output-decode block of a finite state machine with more than three outputs will do this to you sooner or later, which is why the "default assignment at the top" cure in Section 6.5 exists.這是真實程式碼裡遠遠最常見的形狀,也是最難用肉眼看出來的,因為那個 case 確實是完整的。不完整是逐變數發生的。busy 在四個分支裡都有賦值,沒問題。done 在 RUN 裡沒被賦值,所以它鎖存,致能是 $\overline{\text{RUN}}$。start 在 FIN 裡沒被賦值,所以它鎖存,致能是 $\overline{\text{FIN}}$。一個完整的 case,兩個鎖存器。任何有三個以上輸出的有限狀態機輸出解碼區塊,遲早都會這樣對你,這就是第 6.5 節「在頂端放預設賦值」那個解法存在的理由。これは実際のコードで断然最も多い形であり、目で見つけるのが最も難しい形でもある。case 自体は完全だからだ。不完全なのは変数ごとである。busy は四つの枝すべてで代入されており問題ない。done は RUN で代入されないので、イネーブル $\overline{\text{RUN}}$ のラッチになる。start は FIN で代入されないので、イネーブル $\overline{\text{FIN}}$ のラッチになる。完全な case 一つに、ラッチが二つである。出力が三つを超える有限状態機械の出力デコードブロックは、遅かれ早かれあなたにこれをやる。第 6.5 節の「冒頭でデフォルト代入」という対処が存在する理由である。
For each variable assigned anywhere in the block, ask: is there a path from the top of the block to the bottom that does not assign it? If yes, it latches. Do this per variable, not per block. A complete case guarantees nothing about a variable that only some arms touch.對區塊中任何地方被賦值的每一個變數,問:有沒有一條從區塊頂端走到底端、卻沒有賦值給它的路徑?如果有,它就會鎖存。請逐變數做這件事,不要逐區塊做。一個完整的 case 對一個只有部分分支碰到的變數什麼也保證不了。ブロック内のどこかで代入されるすべての変数について、こう問う。ブロックの先頭から末尾まで、その変数に代入せずに通れる経路はあるか。あればラッチになる。これはブロック単位ではなく変数単位で行うこと。完全な case は、一部の枝しか触らない変数について何も保証しない。
Take Shape 3, the slide's mux_latch, and derive the netlist by hand so that you can recognise it in a schematic viewer.拿形狀 3,也就是投影片的 mux_latch,親手推導出網表,這樣你就能在電路圖檢視器裡認出它。形 3、すなわちスライドの mux_latch を取り上げ、ネットリストを手で導いてみよう。回路図ビューアで見分けられるようになる。
module mux_latch (y_out, sel_a, sel_b, data_a, data_b);
input sel_a, sel_b, data_a, data_b;
output y_out;
reg y_out;
always @ (sel_a or sel_b or data_a or data_b)
case ({sel_a, sel_b})
2'b10: y_out = data_a;
2'b01: y_out = data_b; // incomplete case list
endcase
endmodule
Write $s_a$, $s_b$, $d_a$, $d_b$. The two assigning conditions are $C_a = s_a\overline{s_b}$ and $C_b = \overline{s_a}s_b$. Applying the mechanism:寫下 $s_a$、$s_b$、$d_a$、$d_b$。兩個有賦值的條件是 $C_a = s_a\overline{s_b}$ 與 $C_b = \overline{s_a}s_b$。套用機制:$s_a$、$s_b$、$d_a$、$d_b$ と書く。代入が行われる条件は $C_a = s_a\overline{s_b}$ と $C_b = \overline{s_a}s_b$ の二つである。機構を当てはめると次のようになる。
So the netlist is: an XOR (or an AND-NOR pair) producing the enable, a 2:1 mux producing the data, and one D-latch cell. Three cells where you expected one mux. The slide's figure draws it with inverters and AND gates rather than an XOR, which is the same function in a different cover.所以網表是:一個 XOR(或一對 AND-NOR)產生致能,一個 2:1 多工器產生資料,加上一個 D 鎖存器元件。你本來預期一個多工器,結果是三個元件。投影片的圖是用反相器與 AND 閘而不是 XOR 畫的,那是同一個函數的另一種覆蓋方式。したがってネットリストは、イネーブルを作る XOR(あるいは AND と NOR の対)、データを作る 2:1 マルチプレクサ、そして D ラッチセル 1 個である。マルチプレクサ 1 個のつもりが、セル 3 個になる。スライドの図は XOR ではなくインバータと AND ゲートで描かれているが、同じ関数を別の被覆で表したものである。
In a netlist text file you recognise the damage by the cell type. Latch cells are named with a recognisable stem in every library: LATQ, DLH, LHQ, latrnb, LN. Flip-flops carry DFF, SDFF, FD. If a module you believe is combinational contains any instance of the former, you have found an inferred latch. The instance name is usually the RTL variable with _reg appended and the bit index in brackets, which is how you trace it back to the offending always block.在網表文字檔裡,你靠元件型別認出災情。鎖存器元件在每個元件庫裡都有可辨識的字根:LATQ、DLH、LHQ、latrnb、LN。正反器帶的是 DFF、SDFF、FD。如果一個你認為是組合邏輯的模組裡出現前者的任何實體,你就找到一個推論鎖存器了。實體名稱通常是 RTL 變數加上 _reg 字尾,再用中括號帶位元索引,這就是你把它追回到那個惹禍的 always 區塊的方式。ネットリストのテキストファイルでは、セルの型で被害を見分ける。ラッチセルはどのライブラリでも見分けのつく語幹を持つ。LATQ、DLH、LHQ、latrnb、LN などである。フリップフロップは DFF、SDFF、FD を帯びる。組合せだと思っていたモジュールに前者のインスタンスが一つでもあれば、推論ラッチを見つけたことになる。インスタンス名はたいてい RTL の変数名に _reg を付け、角括弧でビット添字を付けたものであり、それを辿れば問題の always ブロックに戻れる。
The same information arrives much earlier and much more cheaply in the elaboration report. Design Compiler prints a block headed "Inferred memory devices in process" with a table listing the register name, type, width, and whether it has async set, async reset, sync set, sync reset. A latch row shows Latch in the type column and has blanks where a flip-flop would show a clock. Genus prints an equivalent table. Reading that table is the thirty-second habit that prevents everything in this section.同樣的資訊在展開報告裡會更早、也更便宜地送到你面前。Design Compiler 會印出一個標題為「Inferred memory devices in process」的區塊,表中列出暫存器名稱、型別、寬度,以及它有沒有非同步設定、非同步重置、同步設定、同步重置。鎖存器那一列在型別欄顯示 Latch,而且在正反器會顯示時脈的地方是空白。Genus 印的是一張等效的表。讀那張表,就是能防掉本節一切的那個三十秒習慣。同じ情報は、エラボレーションレポートならもっと早く、もっと安く手に入る。Design Compiler は「Inferred memory devices in process」という見出しのブロックを印字し、表にレジスタ名・種別・幅、そして非同期セット/非同期リセット/同期セット/同期リセットの有無を並べる。ラッチの行は種別の欄に Latch と出て、フリップフロップならクロックが表示される欄が空になる。Genus も同等の表を出す。この表を読むことこそ、本節のすべてを防ぐ 30 秒の習慣である。
Most students accept "latches are bad" as a rule of thumb without knowing why, and then quietly assume that since a latch has about the area of two NANDs, the cost is small. The area cost is small. The cost is in timing closure, and it is large. Here is the chain of reasoning.多數學生把「鎖存器不好」當成經驗法則接受下來,卻不知道為什麼,然後默默假設既然一個鎖存器大約只有兩個 NAND 的面積,代價應該很小。面積代價確實很小。代價在時序收斂上,而且很大。以下是推理的鏈條。多くの学生は「ラッチは良くない」を経験則として受け入れるが、理由は知らない。そして、ラッチの面積は NAND 二個分程度なのだからコストは小さいはずだと静かに思い込む。面積のコストは確かに小さい。コストはタイミングクロージャにあり、そちらは大きい。以下がその推論の鎖である。
Static timing analysis works by decomposing the design into timing paths, each running from a startpoint to an endpoint. Startpoints are primary inputs and clock pins of sequential cells; endpoints are primary outputs and data pins of sequential cells. For an edge-triggered design every path begins at one clock edge and ends before the next, so the analysis is a straightforward comparison of the longest path delay against the clock period minus setup time. This is why synchronous design is popular: timing verification is a shortest-and-longest-path problem on a DAG, and it is exact.靜態時序分析的做法,是把設計分解成一條條時序路徑,每一條從一個起點跑到一個終點。起點是主要輸入與序向元件的時脈腳;終點是主要輸出與序向元件的資料腳。對一個邊緣觸發的設計而言,每一條路徑都從一個時脈邊緣開始、在下一個邊緣之前結束,所以分析就是把最長路徑延遲拿去跟「時脈週期減去建立時間」比大小,很直接。這就是為什麼同步設計這麼受歡迎:時序驗證是一個有向無環圖上的最長與最短路徑問題,而且是精確的。静的タイミング解析は、設計をタイミングパスに分解して行う。各パスは始点から終点まで走る。始点は主入力と順序セルのクロックピン、終点は主出力と順序セルのデータピンである。エッジトリガの設計ではどのパスも一つのクロックエッジで始まり次のエッジまでに終わるので、解析は最長パス遅延とクロック周期からセットアップ時間を引いた値との単純な比較になる。同期設計が好まれる理由がこれである。タイミング検証は DAG 上の最長・最短経路問題であり、しかも厳密なのだ。
A level-sensitive latch breaks this decomposition in a specific way. While a latch is transparent, a signal arriving at its D pin propagates straight through to Q — the path does not end at the latch. The analyser must therefore consider time borrowing (also called cycle stealing): a path that arrives late at a transparent latch can continue into the next stage, borrowing time from the following cycle, provided the total still closes. Latch-based timing is legitimate and is used deliberately in some high-performance designs for exactly this reason. But it converts the timing problem from "check each path independently" into "solve a system of constraints over chains of latches", which is more expensive, less intuitive, and — the real killer — non-local: whether a path closes now depends on paths several stages away.一個準位敏感的鎖存器以一種特定的方式破壞這個分解。當鎖存器透通時,抵達它 D 腳的訊號會直接穿過去到 Q——路徑不會在鎖存器結束。因此分析器必須考慮借時(time borrowing,也叫 cycle stealing):一條較晚抵達透通鎖存器的路徑可以繼續進入下一級,向下一個週期借時間,只要總量仍然收斂就行。以鎖存器為基礎的時序是正當的,某些高效能設計正是為此刻意採用它。但它把時序問題從「獨立檢查每一條路徑」變成「在一串鎖存器鏈上解一組約束系統」,那更昂貴、更不直觀,而且——真正要命的是——非區域性:一條路徑現在能不能收斂,取決於好幾級之外的路徑。レベルセンシティブなラッチは、この分解を特定の形で壊す。ラッチが透過している間、D ピンに届いた信号はそのまま Q へ抜ける。パスはラッチで終わらないのだ。したがって解析器はタイムボローイング(サイクルスチールとも呼ぶ)を考慮しなければならない。透過中のラッチに遅れて到着したパスは次段へ進み、全体として収まる限り、次サイクルから時間を借りられる。ラッチベースのタイミングは正当な手法であり、一部の高性能設計はまさにこの理由で意図的に採用する。しかしそれはタイミングの問題を「各パスを独立に検査する」ものから「ラッチの鎖にまたがる制約系を解く」ものへ変える。高価で、直観に反し、そして本当に厄介なのは非局所的なことである。あるパスが収まるかどうかが、数段離れたパスに依存するようになる。
Set against all that, the functional damage is often the least of it: the output holds a stale value on the uncovered input combination. If that combination genuinely never occurs, the design works. It is the timing, test and verification consequences that make a latch expensive even when it is functionally harmless — and that is the part the one-line rule in the study guide does not convey.相對於上面這些,功能上的損害往往是最輕的:在沒涵蓋到的輸入組合上,輸出抱著一個舊值。如果那個組合真的從不出現,設計就會動。真正讓一個鎖存器變貴的,是時序、測試與驗證上的後果,即使它在功能上無害——而這正是學習指南裡那一行規則傳達不出來的部分。これらに比べれば、機能上の損害はしばしば最も軽い。未カバーの入力の組合せで出力が古い値を保持する、それだけである。その組合せが本当に決して現れないなら、設計は動く。ラッチを高価にするのはタイミング・テスト・検証上の帰結であり、機能的に無害な場合でもそれは変わらない。そしてそこが、スタディガイドの一行の規則では伝わらない部分である。
All four cures do the same thing — guarantee that every variable receives a value on every path — and they differ in style, in robustness, and in what they imply about your intent.這四種解法做的是同一件事——保證每一個變數在每一條路徑上都拿到一個值——它們的差別在風格、在穩健度,以及在它們對你的意圖暗示了什麼。四つの対処はどれも同じことをする。すべての変数がすべての経路で値を受け取ることを保証するのだ。違いはスタイル、頑健さ、そして自分の意図について何を暗に示すかにある。
always @* begin
case (state)
IDLE: begin start=0; busy=0; done=0; end
RUN: begin start=0; busy=1; end
FIN: begin busy=0; done=1; end
default: begin start=0; busy=0; done=0; end
endcase
end
// done latches on RUN, start latches on FINalways @* begin start = 1'b0; busy = 1'b0; done = 1'b0; case (state) IDLE: ; RUN: busy = 1'b1; FIN: done = 1'b1; default: ; endcase end // every variable assigned on every path
This is the strongest cure and the one to adopt as a default habit. It is immune to Shape 5 because the assignment happens before any branching; it survives someone adding a new case arm or a new output six months later; and it makes the code shorter and clearer, because each arm now states only what is different about it.這是最強的解法,也是應該當成預設習慣採用的那一個。它對形狀 5 免疫,因為賦值發生在任何分支之前;它在半年後有人加了一個新的 case 分支或一個新輸出時依然成立;而且它讓程式碼更短也更清楚,因為每個分支現在只陳述它有什麼不同。これが最も強い対処であり、既定の習慣として採用すべきものである。代入が分岐より前に起きるので形 5 に対して免疫があり、半年後に誰かが新しい case の枝や新しい出力を加えても生き残る。しかも各枝は何が違うかだけを述べればよくなるので、コードは短く明快になる。
The obvious objection — "does the first assignment not cost a mux?" — is answered by the dead-value elimination mentioned in Section 1.4. The tool observes that on a path where a later assignment occurs, the earlier value never reaches an output, and it simply does not build it. The structure synthesised is identical to the fully-explicit version. There is no area penalty whatsoever.一個顯而易見的反對意見——「第一個賦值不會多花一個多工器嗎?」——答案就是第 1.4 節提到的死值消去。工具觀察到:在一條後面還有賦值的路徑上,前面那個值永遠到不了輸出,於是它根本不把它造出來。合成出來的結構與完全明寫的版本一模一樣。完全沒有任何面積代價。当然の反論、「最初の代入でマルチプレクサが一つ増えるのでは」には、第 1.4 節で触れた死値除去が答えになる。後で代入がある経路では先の値が出力に届かないことをツールは見抜き、そもそもそれを作らない。合成される構造は完全に明示した版と同一であり、面積のペナルティは一切ない。
Defaulting all control outputs to their inactive value means that any state you forget to handle produces an inactive, safe output rather than an unpredictable one. That is a free safety property, and it is the reason this style is mandatory in most automotive and aerospace coding standards.把所有控制輸出預設為它們的非作用值,意味著任何你忘了處理的狀態都會產生一個非作用、安全的輸出,而不是一個不可預測的輸出。那是一個免費的安全性質,也是為什麼這種寫法在大部分車用與航太的寫碼規範裡是強制的。すべての制御出力を非アクティブ値にデフォルトしておけば、扱い忘れた状態はどれも予測不能な出力ではなく、非アクティブで安全な出力を生む。これは無料で得られる安全性であり、自動車や航空宇宙のコーディング規約の大半でこのスタイルが必須とされる理由でもある。
else解法 2 — 一個最後的、無條件的 else対処 2 — 最後に無条件の else を置くalways @ (a1 or a2) begin
if ({a2,a1} == 2'b11) y = 1;
else if ({a2,a1} == 2'b01) y = 0;
else if ({a2,a1} == 2'b10) y = 0;
end
// {a2,a1} == 2'b00 falls throughalways @ (a1 or a2) begin
if ({a2,a1} == 2'b11) y = 1;
else if ({a2,a1} == 2'b01) y = 0;
else y = 0;
end
// or simply: assign y = a1 & a2;The slide's annotation — "the last statement should be else condition" — is exactly right. Note how much better the one-line continuous assignment is than either version: four lines of latch-prone procedural code describing an AND gate. When the function fits in an expression, assign removes the entire class of bug.投影片的註解——「the last statement should be else condition」——完全正確。也請注意那一行連續賦值比兩個版本都好得多:四行容易長鎖存器的程序式程式碼,只為了描述一個 AND 閘。當函數塞得進一個運算式時,assign 把這一整類錯誤都消掉了。スライドの注記「the last statement should be else condition」はまったく正しい。そして、1 行の連続代入がどちらの版よりはるかに優れている点にも注目したい。AND ゲート一つを記述するために、ラッチを生みやすい手続きコードを 4 行も書いているのだ。関数が一つの式に収まるなら、assign はこの種のバグを丸ごと消し去る。
default: arm in every case解法 3 — 每個 case 都要有 default: 分支対処 3 — すべての case に default: の枝を置くThe slide's alu_complete is the model, and it is dissected in Section 6.7. The rule is absolute: every case statement gets a default, with no exceptions, even when you have enumerated all values of the selector. The reason for the no-exceptions form is maintenance. Today sel is two bits and you listed four arms; next month someone widens it to three bits and you have eight values, four of them unhandled, and a latch appears in code nobody edited. A default arm makes that impossible.投影片的 alu_complete 就是範本,第 6.7 節會逐行解剖它。規則是絕對的:每一個 case 敘述都要有 default,沒有例外,就算你已經把選擇線的所有值都列出來了也一樣。之所以要寫成「沒有例外」,理由是維護。今天 sel 是兩位元,你列了四個分支;下個月有人把它加寬成三位元,於是有八個值,其中四個沒被處理,而一個鎖存器出現在沒有人編輯過的程式碼裡。一個 default 分支讓這件事不可能發生。スライドの alu_complete が手本であり、第 6.7 節で逐行解剖する。規則は絶対である。すべての case 文に default を置く。例外なし。セレクタの値をすべて列挙済みの場合でも置く。例外なしとする理由は保守にある。今日の sel は 2 ビットで枝を四つ書いた。来月だれかがそれを 3 ビットに広げると値は八つになり、うち四つが未処理となって、誰も編集していないコードにラッチが現れる。default の枝はそれを不可能にする。
What value should the default produce? Three sensible answers, in order of preference:default 應該產生什麼值?三個合理的答案,依偏好排序:default は何を出すべきか。妥当な答えが三つあり、好ましい順に挙げる。
'bx when the uncovered inputs genuinely cannot occur. Costs nothing — it saves logic, as Section 10 shows — but introduces a controlled RTL-versus-netlist divergence, because the netlist will produce some definite value where the RTL produced x.'bx,當那些沒涵蓋的輸入真的不可能發生時。不花代價——如第 10 節所示,它反而省邏輯——但引入一個受控的 RTL 與網表分歧,因為網表會在 RTL 產生 x 的地方產生某個確定的值。'bx。未カバーの入力が本当に起こりえない場合に使う。コストはかからず、第 10 節が示すとおり論理を減らす。ただし制御された RTL とネットリストの乖離を持ち込む。RTL が x を出すところで、ネットリストは何らかの確定値を出すからである。What the default must never be is y = y;. That is Section 7's combinational loop, and it is strictly worse than the latch you were trying to avoid.default 絕對不可以是 y = y;。那是第 7 節的組合迴路,而且嚴格來說比你原本想避開的那個鎖存器更糟。default が決してなってはならないのは y = y; である。それは第 7 節の組合せループであり、避けようとしていたラッチより厳密に悪い。
casez / casex and the full_case directive解法 4 — casez / casex 與 full_case 指示詞対処 4 — casez / casex と full_case ディレクティブThe fourth approach is to tell the tool that the case is complete even though it does not look complete. Verilog gives you two mechanisms and they are not equivalent.第四種做法是告訴工具這個 case 其實是完整的,即使它看起來不完整。Verilog 給你兩種機制,而它們並不等價。四つ目の方法は、見た目は完全でなくとも case は完全だとツールに伝えることである。Verilog は二つの仕組みを用意しているが、両者は等価ではない。
The honest one is to use casez (or casex) with wildcard patterns that genuinely cover the whole space. If your selector is eight bits but only the top three matter, then casez with items like 8'b000????? covers thirty-two values per arm, and eight such arms cover all 256 — the statement really is complete, and no latch appears. This is the technique behind the casex priority encoder in Section 8.3, and it is entirely legitimate.誠實的那一種,是用 casez(或 casex)搭配真的覆蓋整個空間的萬用字元樣式。如果你的選擇線有八位元但只有最高三位有意義,那麼用 casez 搭配像 8'b000????? 這樣的項目,每個分支覆蓋三十二個值,八個這樣的分支就覆蓋全部 256 個——這個敘述真的是完整的,不會出現鎖存器。這就是第 8.3 節 casex 優先權編碼器背後的技巧,完全正當。誠実なほうは、空間を本当に覆うワイルドカードのパターンを casez(または casex)で使う方法である。セレクタが 8 ビットでも上位 3 ビットしか意味を持たないなら、8'b000????? のような項目を使う casez は 1 枝で 32 個の値を覆い、そうした枝が八つあれば 256 個すべてを覆う。文は本当に完全であり、ラッチは現れない。これが第 8.3 節の casex プライオリティエンコーダの背後にある技であり、まったく正当である。
The dangerous one is the synthesis pragma // synopsys full_case, which asserts to the tool — without proof — that the unlisted values never occur, so no latch is needed. It is attached as a comment and therefore invisible to the simulator.危險的那一種,是合成用的 pragma // synopsys full_case,它在沒有任何證明的情況下向工具宣稱:那些沒列出的值永遠不會出現,所以不需要鎖存器。它是以註解的形式附加的,因此對模擬器而言是隱形的。危険なほうは合成用のプラグマ // synopsys full_case である。これは証明なしに「列挙していない値は決して現れないのでラッチは不要だ」とツールに宣言する。コメントとして付けられるため、シミュレータからは見えない。
The pragma creates an asymmetry between simulation and synthesis that is the exact inverse of the one you usually worry about. The simulator has no idea the pragma exists: it executes the case, finds no match, assigns nothing, and holds the old value — simulating a latch. The synthesiser believes the pragma, treats the unlisted values as don't-cares, and builds pure combinational logic that produces some arbitrary value. If the "impossible" input ever does occur, RTL and gates disagree, and the disagreement is invisible to equivalence checking because the checker also honours the pragma.這個 pragma 製造出一種模擬與合成之間的不對稱,而且恰好是你平常擔心的那種的反面。模擬器根本不知道這個 pragma 存在:它執行 case、找不到相符的項、什麼也不賦值,於是保住舊值——模擬出一個鎖存器。合成器相信這個 pragma,把沒列出的值當成不在意項,建出會產生某個任意值的純組合邏輯。如果那個「不可能」的輸入真的出現了,RTL 與閘級就不一致,而這個不一致對等價驗證是隱形的,因為驗證器同樣尊重那個 pragma。このプラグマは、ふだん心配するのとちょうど逆向きの、シミュレーションと合成の非対称性を生む。シミュレータはプラグマの存在を知らない。case を実行し、一致する項がなく、何も代入せず、古い値を保持する——つまりラッチをシミュレートする。合成器はプラグマを信じ、列挙されていない値をドントケアとみなし、任意の値を出す純粋な組合せ論理を作る。その「ありえない」入力が実際に現れれば RTL とゲートは食い違い、しかも等価検証もプラグマを尊重するので、その食い違いは検証から見えない。
Its sibling // synopsys parallel_case asserts that the case items are mutually exclusive, suppressing priority logic. Same problem: if they are not in fact exclusive, the netlist implements a different function from the RTL. The modern replacements are SystemVerilog's unique case and priority case, which assert the same properties but are understood by the simulator too, so a violation produces a runtime error instead of a silent divergence. Within IEEE-1364 Verilog, the right answer is to write a real default and let the don't-care live in the assigned value.它的兄弟 // synopsys parallel_case 宣稱各 case 項目互斥,藉此抑制優先權邏輯。同樣的問題:如果它們其實不互斥,網表實現的就是一個與 RTL 不同的函數。現代的替代品是 SystemVerilog 的 unique case 與 priority case,它們宣稱同樣的性質,但模擬器也看得懂,所以違反時會產生執行期錯誤,而不是無聲的分歧。在 IEEE-1364 Verilog 的範圍內,正確答案是寫一個真的 default,讓不在意項活在被賦的值裡。兄弟分の // synopsys parallel_case は case 項目が互いに排他的だと宣言し、優先順位論理を抑制する。問題は同じで、実際には排他的でなければ、ネットリストは RTL とは別の関数を実装する。現代の置き換えは SystemVerilog の unique case と priority case である。同じ性質を宣言するがシミュレータも理解するので、違反は無言の乖離ではなく実行時エラーになる。IEEE-1364 Verilog の範囲では、正解は本物の default を書き、ドントケアを代入する値の側に置くことである。
| Cure解法対処 | Robust to later edits?對日後修改是否穩健?後の改変に強いか | Area cost面積代價面積コスト | Risk風險リスク |
|---|---|---|---|
| Default assignment at top在頂端放預設賦值冒頭のデフォルト代入 | Yes — covers new arms and new variables是——新分支與新變數都涵蓋強い——新しい枝も新しい変数も覆う | None (dead values eliminated)無(死值被消去)なし(死値は除去される) | None無なし |
Final else最後的 else最後の else | Partly — covers new conditions, not new variables部分——涵蓋新條件,不涵蓋新變數部分的——新しい条件は覆うが新しい変数は覆わない | None to small無到很小なし〜わずか | None無なし |
default: armdefault: 分支default: の枝 | Partly — covers new selector values, not new variables部分——涵蓋新的選擇線值,不涵蓋新變數部分的——新しいセレクタ値は覆うが新しい変数は覆わない | None to small; negative with 'bx無到很小;用 'bx 時為負なし〜わずか。'bx なら負(減る) | Choosing a misleading default value挑到一個會誤導人的預設值誤解を招くデフォルト値を選ぶこと |
casez with full coverage完整覆蓋的 casez完全に覆う casez | Yes, if the patterns really tile the space是,只要那些樣式真的鋪滿整個空間強い。ただしパターンが本当に空間を敷き詰めている場合に限る | None無なし | Patterns that silently leave a gap樣式悄悄留下一個缺口パターンが黙って穴を残すこと |
full_case pragmafull_case pragmafull_case プラグマ | No否弱い | Negative (removes logic)負(會移除邏輯)負(論理を減らす) | High — silent RTL/netlist divergence高——無聲的 RTL/網表分歧高——RTL とネットリストの無言の乖離 |
opcode is three bits. For 3'b001 output a|b; for 3'b010 output a^b; for 3'b110 output ~b. All other opcodes are reserved and are guaranteed by the instruction decoder never to reach this block. Operands are four bits.規格。opcode 是三位元。3'b001 輸出 a|b;3'b010 輸出 a^b;3'b110 輸出 ~b。所有其他 opcode 都是保留值,指令解碼器保證它們永遠不會到達這個區塊。運算元是四位元。仕様。opcode は 3 ビット。3'b001 では a|b、3'b010 では a^b、3'b110 では ~b を出力する。他の opcode はすべて予約値であり、命令デコーダがこのブロックに到達させないことを保証している。オペランドは 4 ビットである。case arms, no default. Five of eight opcodes are uncovered, so alu_reg latches. Enable $= \overline{o_2}\,\overline{o_1}o_0 + \overline{o_2}o_1\overline{o_0} + o_2o_1\overline{o_0}$. Four bits wide, so four latch cells, about 14 GE, plus the enable logic, about 4 GE. Total overhead roughly 18 GE on a block whose useful logic is four OR gates, four XORs and four inverters — about 16 GE. The bug more than doubles the module.天真版本。三個 case 分支,沒有 default。八個 opcode 中有五個沒涵蓋,所以 alu_reg 鎖存。致能 $= \overline{o_2}\,\overline{o_1}o_0 + \overline{o_2}o_1\overline{o_0} + o_2o_1\overline{o_0}$。寬度四位元,所以是四個鎖存器元件,約 14 GE,加上致能邏輯約 4 GE。在一個有用邏輯只有四個 OR、四個 XOR 與四個反相器(約 16 GE)的區塊上,總額外開銷大約 18 GE。這個錯誤讓模組大了一倍有餘。素朴な版。case の枝は三つで default なし。八つの opcode のうち五つが未カバーなので alu_reg はラッチになる。イネーブルは $= \overline{o_2}\,\overline{o_1}o_0 + \overline{o_2}o_1\overline{o_0} + o_2o_1\overline{o_0}$。幅は 4 ビットなのでラッチセル 4 個、約 14 GE、さらにイネーブル論理が約 4 GE。有用な論理が OR 4 個・XOR 4 個・インバータ 4 個の約 16 GE しかないブロックに対し、追加のオーバーヘッドは約 18 GE である。このバグはモジュールを倍以上に膨らませる。default: alu_reg = 4'b0; No latch. The tool must now drive zero for five opcodes, so it builds the full selection: two muxes plus the decode, roughly 22 GE total. Predictable, simulatable, scannable.用 default: alu_reg = 4'b0; 修。沒有鎖存器。工具現在必須對五個 opcode 驅動零,所以它建出完整的選擇結構:兩個多工器加上解碼,總共大約 22 GE。可預測、可模擬、可掃描。default: alu_reg = 4'b0; で直す。ラッチは消える。ツールは五つの opcode に対してゼロを駆動しなければならなくなるので、完全な選択構造を作る。マルチプレクサ 2 個とデコードで合計約 22 GE。予測可能で、シミュレートでき、スキャン可能である。default: alu_reg = 4'bx; No latch, and the five reserved opcodes become don't-cares. The optimiser now notices that it may output anything for those inputs, which collapses the decode dramatically — in the slide's own comparison (Section 10) this is the difference between a two-mux circuit and a one-mux circuit. Roughly 14 GE. Cheapest of all, at the cost of a deliberate RTL/netlist divergence on inputs you have promised cannot occur.用 default: alu_reg = 4'bx; 修。沒有鎖存器,而且那五個保留 opcode 變成不在意項。最佳化器現在注意到它對那些輸入可以輸出任何東西,於是解碼結構大幅塌陷——在投影片自己的對照裡(第 10 節),這就是兩個多工器的電路與一個多工器的電路之間的差別。大約 14 GE。最便宜的一種,代價是在你保證不會出現的輸入上,刻意製造 RTL 與網表的分歧。default: alu_reg = 4'bx; で直す。ラッチは消え、五つの予約 opcode はドントケアになる。それらの入力に対しては何を出してもよいと最適化器が気づくので、デコードは劇的に潰れる。スライド自身の比較(第 10 節)では、これがマルチプレクサ 2 個の回路と 1 個の回路の差である。約 14 GE。最も安いが、起こりえないと約束した入力の上で、RTL とネットリストの乖離を意図的に作る代償を払う。alu_reg = 4'b0; before the case, arms unchanged, no default: arm needed. Produces exactly the same netlist as the default: 4'b0 version — the tool cannot tell the two sources apart after translation. Preferred on style grounds because it also protects any variable you add later.用頂端的預設賦值修。在 case 之前寫 alu_reg = 4'b0;,分支不變,不需要 default: 分支。產生的網表與 default: 4'b0 版本完全相同——翻譯之後,工具根本分不出這兩份原始碼。從風格上更推薦它,因為它同時保護你日後新增的任何變數。冒頭のデフォルト代入で直す。case の前に alu_reg = 4'b0; を置き、枝はそのまま、default: の枝は不要である。できるネットリストは default: 4'b0 版とまったく同じで、翻訳後のツールには二つのソースを区別できない。後から追加する変数まで守ってくれるので、スタイルの面ではこちらが好ましい。full_case. Same netlist as the 4'bx version, but the RTL simulation still holds the old value on a reserved opcode. You have the cheapest hardware and the worst verification story. Do not.用 full_case 修。網表與 4'bx 版本相同,但 RTL 模擬在遇到保留 opcode 時仍然抱著舊值。你拿到最便宜的硬體與最糟的驗證故事。不要。full_case で直す。ネットリストは 4'bx 版と同じだが、RTL シミュレーションは予約 opcode で依然として古い値を保持する。最も安いハードウェアと最悪の検証の筋書きを同時に手にすることになる。やめること。The lecture gives three versions of the same ALU and the comparison between them makes a point that is easy to miss.本講給了同一個 ALU 的三個版本,而它們之間的對照帶出一個很容易被忽略的重點。講義は同じ ALU の三つの版を示す。その比較が、見落とされやすい論点を突きつける。
module alu_incomplete1a (alu_out, data_a,
data_b, enable, opcode);
input [2:0] opcode;
input [3:0] data_a, data_b;
input enable;
output alu_out; // scalar
reg [3:0] alu_reg;
assign alu_out = (enable == 1) ? alu_reg : 4'b0;
always @ (opcode) // defect B
case (opcode)
3'b001: alu_reg = data_a | data_b;
3'b010: alu_reg = data_a ^ data_b;
3'b110: alu_reg = ~data_b;
endcase // defect A
endmodulemodule alu_incomplete2a (alu_out, data_a,
data_b, enable, opcode);
input [2:0] opcode;
input [3:0] data_a, data_b;
input enable;
output alu_out;
reg [3:0] alu_reg;
assign alu_out = (enable == 1) ? alu_reg : 4'b0;
always @ (opcode or data_a or data_b)
case (opcode)
3'b001: alu_reg = data_a | data_b;
3'b010: alu_reg = data_a ^ data_b;
3'b110: alu_reg = ~data_b;
endcase // defect A remains
endmoduleVersion 1a has both defects from Section 4.4: the sensitivity list omits data_a and data_b (Defect B, a mismatch), and the case omits five opcodes (Defect A, a latch). Version 2a fixes the sensitivity list and nothing else. Both synthesise to exactly the same netlist, latch included. That is the pedagogical point of putting them side by side: fixing the sensitivity list does nothing for the latch, because the latch was never about sensitivity.版本 1a 同時有第 4.4 節的兩個缺陷:敏感度列表漏了 data_a 與 data_b(缺陷 B,不匹配),而 case 漏了五個 opcode(缺陷 A,鎖存器)。版本 2a 只修了敏感度列表,其餘不動。兩者合成出來的網表完全相同,鎖存器照樣在裡面。把它們並排放在一起的教學意義就在這裡:修敏感度列表對鎖存器毫無幫助,因為鎖存器從頭到尾就不是敏感度的問題。版 1a には第 4.4 節の二つの欠陥が揃っている。センシティビティリストから data_a と data_b が抜けており(欠陥 B、不一致)、case が五つの opcode を落としている(欠陥 A、ラッチ)。版 2a はセンシティビティリストだけを直し、他は変えていない。両者は完全に同じネットリストに合成され、ラッチもそのまま残る。この二つを並べる教育的な意味はそこにある。センシティビティリストを直してもラッチには何の効果もない。ラッチはそもそもセンシティビティの問題ではなかったからだ。
The slide's synthesis figure for 2a shows the structure predicted by Section 6.3: NAND and NOR cells computing the three data functions, two muxes selecting between them under the opcode decode, a latrnb_a cell holding the result, and a final NAND gating with enable. There is also an esdpupd block in the corner — an ESD pull-up device, an artefact of that particular library's I/O handling, not part of the logic.投影片為 2a 畫的合成圖,顯示的正是第 6.3 節預測的結構:NAND 與 NOR 元件算出三個資料函數,兩個多工器在 opcode 解碼的控制下在它們之間選擇,一個 latrnb_a 元件保住結果,最後一個 NAND 用 enable 做閘控。角落裡還有一個 esdpupd 區塊——那是一個 ESD 上拉元件,是那座特定元件庫 I/O 處理的產物,不是邏輯的一部分。スライドが 2a について描く合成図は、第 6.3 節が予測した構造そのものである。NAND と NOR のセルが三つのデータ関数を計算し、opcode のデコードに制御された二つのマルチプレクサがその中から選び、latrnb_a セルが結果を保持し、最後の NAND が enable でゲートする。隅には esdpupd のブロックもあるが、これは ESD プルアップ素子で、そのライブラリ特有の I/O 処理の産物であり論理の一部ではない。
module alu_complete (alu_out, data_a, data_b, enable, opcode);
input [2:0] opcode;
input [3:0] data_a, data_b;
input enable;
output alu_out;
reg [3:0] alu_reg;
assign alu_out = (enable == 1) ? alu_reg : 4'b0;
always @ (opcode or data_a or data_b)
case (opcode)
3'b001: alu_reg = data_a | data_b;
3'b010: alu_reg = data_a ^ data_b;
3'b110: alu_reg = ~data_b;
default: alu_reg = 4'b0; // completes the case list
endcase
endmodule
One line removes the latch. The slide's figure for alu_complete shows pure gates and one mux feeding the output NAND — no rectangular latch symbol anywhere.一行就能移除鎖存器。投影片為 alu_complete 畫的圖顯示的是純邏輯閘加上一個多工器餵進輸出的 NAND——任何地方都沒有那個長方形的鎖存器符號。一行でラッチは消える。スライドの alu_complete の図には、純粋なゲートと出力 NAND を駆動するマルチプレクサ 1 個が描かれ、長方形のラッチ記号はどこにもない。
alu_out is declared as a one-bit port while alu_reg is four bits; the slide notes "scalar for illustration". In Verilog, assigning a four-bit expression to a one-bit net truncates to the least significant bit, so the circuit that survives is only the bit-0 slice: one OR, one XOR, one inverter, one mux. That is why the published schematics look far too small for a four-bit ALU. In real code this width mismatch would be a bug, and a lint tool would flag it. Here it is a deliberate simplification to keep the figure on one page — worth knowing so that you do not waste time trying to find the other three bits.alu_out 被宣告成一位元的埠,而 alu_reg 是四位元;投影片註明「scalar for illustration」。在 Verilog 中,把一個四位元運算式賦給一條一位元的 net 會截斷成最低有效位元,所以活下來的電路只有第 0 位元那一片:一個 OR、一個 XOR、一個反相器、一個多工器。這就是為什麼印出來的電路圖對一個四位元 ALU 來說看起來小得離譜。在真實程式碼裡,這種寬度不符會是一個錯誤,lint 工具會標出來。在這裡它是為了讓圖塞進一頁而刻意做的簡化——值得知道,免得你浪費時間去找另外三個位元。alu_out は 1 ビットのポートとして宣言され、alu_reg は 4 ビットである。スライドは「scalar for illustration」と注記している。Verilog では 4 ビットの式を 1 ビットのネットに代入すると最下位ビットに切り詰められるので、生き残る回路はビット 0 のスライスだけ、すなわち OR 1 個、XOR 1 個、インバータ 1 個、マルチプレクサ 1 個である。掲載された回路図が 4 ビット ALU にしてはあまりに小さく見えるのはそのためである。実際のコードならこの幅の不一致はバグであり、lint ツールが指摘する。ここでは図を 1 ページに収めるための意図的な簡略化である。残り 3 ビットを探して時間を無駄にしないよう、知っておく価値がある。
Problem: For each block say whether a latch is inferred. If yes, give the latched variable, the Boolean enable expression, and the width in bits.題目:對每一個區塊,說出是否推論出鎖存器。若是,給出被鎖存的變數、致能的布林運算式,以及位元寬度。問題:各ブロックについて、ラッチが推論されるかどうかを述べよ。されるなら、ラッチされる変数、イネーブルのブール式、ビット幅を示せ。
// (a)
always @ (a or b or s) if (s) y = a; else y = b;
// (b)
always @ (en or d) if (en) q = d;
// (c)
always @ (s or a or b) begin y = b; if (s) y = a; end
// (d) op is 2 bits, y is 8 bits
always @ (op or a or b)
case (op) 2'b00: y = a; 2'b01: y = b; 2'b10: y = a & b; endcase
// (e) op is 2 bits
always @ (op or a or b)
case (op) 2'b00: y=a; 2'b01: y=b; 2'b10: y=a&b; 2'b11: y=a|b; endcase
// (f) g, h are 4 bits; mode is 2 bits
always @* case (mode)
2'b00: begin g = 4'd1; h = 4'd2; end
2'b01: begin g = 4'd3; end
2'b10: begin h = 4'd4; end
2'b11: begin g = 4'd5; h = 4'd6; end
endcase
// (g)
always @* begin
y = 1'b0;
casez (sel)
4'b1???: y = 1'b1;
4'b01??: y = 1'b1;
endcase
end
(a) No latch. Both branches assign y. A clean 2:1 mux.(a) 沒有鎖存器。兩個分支都賦值給 y。一個乾淨的 2:1 多工器。(a) ラッチなし。両方の枝が y に代入している。きれいな 2:1 マルチプレクサである。
(b) Latch on q, enable en, one bit. This is the intentional-D-latch template; whether it is a bug depends entirely on whether you meant it.(b) 有鎖存器,在 q 上,致能是 en,一位元。這是刻意寫 D 鎖存器的範本;它算不算錯誤,完全取決於你是不是真的想要它。(b) ラッチあり。q に対し、イネーブルは en、1 ビット。これは意図的な D ラッチのテンプレートであり、バグかどうかはそれを意図したかどうかだけで決まる。
(c) No latch. The unconditional y = b at the top covers the missing else — Cure 1 in miniature. Identical netlist to (a).(c) 沒有鎖存器。頂端那個無條件的 y = b 涵蓋了缺席的 else——這是縮小版的解法 1。網表與 (a) 相同。(c) ラッチなし。冒頭の無条件の y = b が欠けている else を覆っている。対処 1 のミニチュアである。ネットリストは (a) と同じ。
(d) Latch on y, enable $\overline{op_1} + \overline{op_0}$ — equivalently $\overline{op_1 \cdot op_0}$, i.e. "op is not 2'b11". Eight bits wide, so eight latch cells, roughly 28 GE of unintended storage.(d) 有鎖存器,在 y 上,致能是 $\overline{op_1} + \overline{op_0}$——等價於 $\overline{op_1 \cdot op_0}$,也就是「op 不是 2'b11」。寬八位元,所以是八個鎖存器元件,大約 28 GE 的非預期儲存。(d) ラッチあり。y に対し、イネーブルは $\overline{op_1} + \overline{op_0}$、すなわち $\overline{op_1 \cdot op_0}$、つまり「op が 2'b11 でない」である。幅 8 ビットなのでラッチセル 8 個、意図しない記憶が約 28 GE 分。
(e) No latch. All four values of a two-bit selector are listed, so the statement is complete over the synthesisable value space. (In simulation an x on op would match no arm and hold y, which is one more reason to add a default anyway.)(e) 沒有鎖存器。兩位元選擇線的四個值都列出來了,所以就可合成的值空間而言,這個敘述是完整的。(在模擬中,op 上的 x 會匹配不到任何分支而讓 y 保持原值,這又是一個無論如何都該加 default 的理由。)(e) ラッチなし。2 ビットのセレクタの四つの値がすべて列挙されているので、合成可能な値空間に対して文は完全である。(シミュレーションでは op の x はどの枝にも一致せず y を保持するので、やはり default を加えるべきもう一つの理由になる。)
(f) Two latches. The case is complete, but g is unassigned in arm 2'b10 and h is unassigned in arm 2'b01. So g latches with enable $\overline{(mode = 2'b10)}$ and h latches with enable $\overline{(mode = 2'b01)}$, four bits each — eight latch cells. This is Shape 5 and it is the one that catches experienced engineers.(f) 兩個鎖存器。case 是完整的,但 g 在 2'b10 分支裡沒被賦值,h 在 2'b01 分支裡沒被賦值。所以 g 以 $\overline{(mode = 2'b10)}$ 為致能鎖存,h 以 $\overline{(mode = 2'b01)}$ 為致能鎖存,各四位元——共八個鎖存器元件。這就是形狀 5,也是會抓到老練工程師的那一個。(f) ラッチが二つ。case は完全だが、g は枝 2'b10 で、h は枝 2'b01 で代入されていない。したがって g はイネーブル $\overline{(mode = 2'b10)}$ で、h はイネーブル $\overline{(mode = 2'b01)}$ でラッチされ、それぞれ 4 ビット、合わせてラッチセル 8 個となる。これが形 5 であり、熟練した技術者をも捕まえるものである。
(g) No latch. The unconditional y = 1'b0 before the casez covers every unmatched pattern. Note that the casez itself is not complete — 4'b00?? matches nothing — but completeness of the case is not required once the variable is pre-assigned. This combination (default assignment plus wildcard arms) is the cleanest way to write a priority encoder.(g) 沒有鎖存器。casez 之前那個無條件的 y = 1'b0 涵蓋了每一個沒匹配到的樣式。注意 casez 本身並不完整——4'b00?? 匹配不到任何東西——但一旦變數已經預先被賦值,case 就不需要完整。這個組合(預設賦值加萬用字元分支)是寫優先權編碼器最乾淨的方式。(g) ラッチなし。casez の前の無条件の y = 1'b0 が、一致しないすべてのパターンを覆っている。casez 自体は完全ではない(4'b00?? はどれにも一致しない)が、変数が事前に代入されていれば case の完全性は不要である。この組合せ(デフォルト代入+ワイルドカードの枝)が、プライオリティエンコーダを書く最もきれいな方法である。
Problem: A 16-bit datapath module contains an output-decode block for a 6-state FSM. There are five control outputs: load (1 bit), shift (1 bit), count_en (1 bit), sel (2 bits) and offset (16 bits). The case on the state is complete with all six states plus a default, but offset is assigned in only two of the seven arms and sel in only four.題目:一個 16 位元資料路徑模組含有一個 6 狀態 FSM 的輸出解碼區塊。有五個控制輸出:load(1 位元)、shift(1 位元)、count_en(1 位元)、sel(2 位元)與 offset(16 位元)。對狀態做的 case 完整列出全部六個狀態再加一個 default,但 offset 只在七個分支中的兩個被賦值,sel 只在四個被賦值。問題:16 ビットのデータパスモジュールが、6 状態 FSM の出力デコードブロックを含んでいる。制御出力は五つ、load(1 ビット)、shift(1 ビット)、count_en(1 ビット)、sel(2 ビット)、offset(16 ビット)である。状態に対する case は六つの状態すべてと default を備えて完全だが、offset は七つの枝のうち二つでしか代入されず、sel は四つでしか代入されない。
1. offset is unassigned in five of seven arms, so it latches: 16 bits. sel is unassigned in three arms, so it latches: 2 bits. load, shift and count_en are assigned everywhere and are fine. Total 18 latch cells, plus two enable-generating gate clusters of a few GE each.1. offset 在七個分支中有五個沒被賦值,所以它鎖存:16 位元。sel 在三個分支沒被賦值,所以它鎖存:2 位元。load、shift 與 count_en 到處都有賦值,沒問題。總共 18 個鎖存器元件,外加兩組各幾個 GE 的致能產生邏輯。1. offset は七つの枝のうち五つで代入されないのでラッチになる。16 ビット。sel は三つの枝で代入されないのでラッチになる。2 ビット。load・shift・count_en はどこでも代入されており問題ない。合計 ラッチセル 18 個、加えてイネーブル生成のゲート群が数 GE ずつ二組である。
2. 18 × 3.5 ≈ 63 GE of latches plus perhaps 6 GE of enable logic, call it 69 GE against 25 GE of useful logic. The defect is nearly three times the size of the block it infects. Scale that across a chip with forty such blocks and it is a visible fraction of the die.2. 18 × 3.5 ≈ 63 GE 的鎖存器,加上大概 6 GE 的致能邏輯,算 69 GE,對上 25 GE 的有用邏輯。這個缺陷幾乎是它所感染的區塊的三倍大。把它放大到一顆有四十個這種區塊的晶片上,那就是晶粒上看得見的一塊比例。2. 18 × 3.5 ≈ 63 GE のラッチに、イネーブル論理をおよそ 6 GE 加えて 69 GE、対して有用な論理は 25 GE である。欠陥は、それが取り憑いたブロックのほぼ 3 倍の大きさになる。このようなブロックが 40 個あるチップに広げれば、ダイの中で目に見える割合になる。
3. (i) A glitch on one of the data-derived enables opens a latch mid-transition and offset captures a half-formed value that then persists for many cycles — a functional failure that appears only at certain temperatures or voltages. (ii) The paths through the latches are unconstrained in STA, so they are never checked; the design passes timing sign-off and fails on silicon above some frequency. A third, cheaper failure: the DFT tool refuses to insert scan and the project loses a week.3. (i) 某個由資料導出的致能上出現毛刺,在轉態途中打開鎖存器,offset 捕捉到一個半成形的值,然後這個值持續好幾個週期——這是一個只在特定溫度或電壓下才出現的功能失效。(ii) 穿過鎖存器的路徑在 STA 裡未受約束,所以從來沒被檢查過;設計通過時序簽核,卻在矽片上超過某個頻率就失敗。還有第三種比較便宜的失敗:DFT 工具拒絕插入掃描,專案損失一個星期。3. (i) データ由来のイネーブルのいずれかにグリッチが乗り、遷移の途中でラッチを開け、offset が半端な値を取り込み、それが何サイクルも残り続ける。特定の温度や電圧でしか現れない機能不良である。(ii) ラッチを通る経路は STA で制約されておらず、まったく検査されない。設計はタイミングのサインオフを通り、シリコン上である周波数を超えると落ちる。三つ目のもっと安い失敗もある。DFT ツールがスキャン挿入を拒み、プロジェクトが一週間を失う。
4. Put load = 1'b0; shift = 1'b0; count_en = 1'b0; sel = 2'b00; offset = 16'd0; as the first statements of the block, before the case. One line (or five short ones), zero area cost, all eighteen latches gone.4. 把 load = 1'b0; shift = 1'b0; count_en = 1'b0; sel = 2'b00; offset = 16'd0; 放成這個區塊的第一批敘述,在 case 之前。一行(或五行短的),零面積代價,十八個鎖存器全部消失。4. load = 1'b0; shift = 1'b0; count_en = 1'b0; sel = 2'b00; offset = 16'd0; を case の前、ブロックの先頭の文として置く。1 行(あるいは短い 5 行)、面積コストはゼロ、18 個のラッチはすべて消える。
Section 6 established that an incompletely specified statement infers a latch. A natural but wrong reaction is: "fine, I will complete it — I will just write the variable back to itself on the leftover path." The lecture devotes four slides to showing why that is worse, and the slides are worth taking seriously because the reasoning is not obvious.第 6 節確立了「不完整指定的敘述會推論出鎖存器」。一個自然但錯誤的反應是:「好,那我把它補完——我就在剩下的那條路徑上把變數寫回它自己。」本講用四張投影片說明為什麼那樣更糟,而這些投影片值得認真看,因為推理並不顯而易見。第 6 節は、不完全に指定された文がラッチを推論させることを確立した。自然だが誤った反応はこうである。「よし、完全にしよう。残った経路で変数を自分自身に書き戻せばいい」。講義はそれがなぜ悪手なのかの説明に四枚のスライドを割いており、推論は自明ではないので真面目に読む価値がある。
module latch_case1 (latch_out, latch_in,
set, clear, enable);
input latch_in, set, clear, enable;
output latch_out;
reg latch_out;
always @ (enable or set or clear or latch_in)
case ({enable, set, clear})
3'b000: latch_out = latch_in; // transparent
3'b110: latch_out = 1'b1; // set
3'b010: latch_out = 1'b1; // set
3'b101: latch_out = 1'b0; // clear
3'b001: latch_out = 1'b0; // clear
default: latch_out = latch_out; // holds residual value
endcase
endmodulemodule latch_if1 (data_out, data_in,
latch_enable);
input [3:0] data_in;
output [3:0] data_out;
input latch_enable;
reg [3:0] data_out;
always @ (latch_enable or data_in)
if (latch_enable) data_out = data_in;
else data_out = data_out;
endmoduleSyntactically both statements are now complete — every path assigns the variable — so Section 6's latch-inference rule does not fire. What fires instead is Rule 3 from Section 4.4: the target of an assignment appears as an operand on the right-hand side. The tool builds precisely what you wrote: a multiplexer whose 0 input is the multiplexer's own output.語法上這兩個敘述現在都完整了——每一條路徑都賦值給該變數——所以第 6 節的鎖存器推論規則不會觸發。觸發的是第 4.4 節的規則 3:賦值的目標以運算元的身分出現在右手邊。工具精確地造出你寫的東西:一個多工器,它的 0 輸入是這個多工器自己的輸出。構文上、どちらの文もいまや完全である。すべての経路が変数に代入しているので、第 6 節のラッチ推論の規則は発火しない。発火するのは第 4.4 節の規則 3、すなわち代入の対象が右辺にオペランドとして現れていることである。ツールは書かれたとおりのものを正確に作る。0 入力が自分自身の出力であるマルチプレクサである。
data_out = data_out asks for; the right-hand latch is what you wanted, and you get it by simply omitting the else branch.同樣的意圖,兩種實現。左邊的迴路是 data_out = data_out 所要求的;右邊的鎖存器才是你想要的,而你只要省略 else 分支就能得到它。同じ意図、二つの実装。左のループは data_out = data_out が要求するものであり、右のラッチこそが望んだものである。それは else の枝を省くだけで手に入る。The slides annotate both synthesis results with the same blunt note: "Can't be synthesized to the gates." That is correct, and there are three independent reasons, any one of which is sufficient.投影片在兩個合成結果上都加了同一句直白的註解:「Can't be synthesized to the gates.」那是對的,而理由有三個彼此獨立,任何一個都足夠。スライドは二つの合成結果のどちらにも同じそっけない注記を付けている。「Can't be synthesized to the gates.」これは正しく、理由は互いに独立に三つあり、どれ一つでも十分である。
The optimiser's data structure is a DAG. Every algorithm in Section 3 — decomposition, factoring, substitution, elimination — assumes it can traverse the network from inputs to outputs in topological order. A cycle has no topological order. The tool must either refuse the structure or break the cycle arbitrarily, and most tools report a combinational feedback loop and stop optimising that cone.最佳化器的資料結構是一個有向無環圖。第 3 節的每一個演算法——分解、因式分解、代入、消去——都假設它可以依拓樸順序從輸入走到輸出。一個環沒有拓樸順序。工具要嘛拒絕這個結構,要嘛任意把環切開,而大多數工具會回報一個組合回授迴路,並停止最佳化那個邏輯錐。最適化器のデータ構造は DAG である。第 3 節のアルゴリズムはどれも——分解・因数分解・代入・消去——ネットワークを入力から出力へトポロジカル順に辿れることを前提とする。閉路にトポロジカル順序はない。ツールはその構造を拒むか、恣意的に閉路を切るかで、多くのツールは組合せフィードバックループを報告し、そのコーンの最適化をやめる。
Static timing analysis cannot close a cycle. A path through a feedback loop has no end; computing arrival times requires iterating around the loop, which does not converge. STA tools detect loops and break them by disabling one timing arc — the choice of arc is heuristic and is reported as a warning. The disabled arc means some real path is no longer being checked.靜態時序分析無法收斂一個環。穿過回授迴路的路徑沒有終點;計算到達時間需要繞著環迭代,而那不會收斂。STA 工具會偵測到環,並藉由停用某一條時序弧來切開它——選哪一條弧是啟發式的,並會以警告回報。被停用的那條弧意味著某條真實路徑不再被檢查。静的タイミング解析は閉路を収束させられない。フィードバックループを通る経路には終点がない。到着時刻の計算はループを回る反復を要し、それは収束しない。STA ツールはループを検出し、タイミングアークを一本無効にして断ち切る。どのアークを選ぶかはヒューリスティックで、警告として報告される。無効にされたアークは、実在するどこかの経路がもう検査されないことを意味する。
The physical behaviour is a race, not a function. A mux whose output feeds its own input is, electrically, a bistable element: when the enable is low, the loop sustains whatever value is on the wire. That is a latch built from a mux rather than from a characterised latch cell, with three serious differences. It has no guaranteed setup or hold window, because nobody characterised it. Its hold time against the enable's own transition depends on the mux's internal delays, so a slow enable edge can cause it to catch a new value. And it is vulnerable to any glitch on the loop, with no restoring feedback designed in. In marginal silicon such a structure can oscillate rather than hold.物理行為是一場競賽,不是一個函數。一個輸出餵回自己輸入的多工器,在電性上是一個雙穩態元件:當致能為低時,這個環維持住導線上原本是什麼值。那是一個用多工器而不是用特徵化鎖存器元件造出來的鎖存器,有三個嚴重的差別。它沒有保證的建立或保持視窗,因為沒有人特徵化過它。它相對於致能自身轉態的保持時間,取決於多工器的內部延遲,所以一個很慢的致能邊緣可能讓它抓到一個新值。而且它對環上的任何毛刺都毫無抵抗力,因為裡面沒有設計任何恢復性回授。在體質邊緣的矽片上,這種結構可能振盪而不是保持。物理的な振る舞いは関数ではなく競合である。出力が自分の入力に戻るマルチプレクサは、電気的には双安定素子である。イネーブルが低のとき、ループは配線上にあった値をそのまま維持する。それは特性化されたラッチセルではなくマルチプレクサで作ったラッチであり、三つの重大な違いがある。誰も特性化していないので、保証されたセットアップやホールドのウィンドウがない。イネーブル自身の遷移に対するホールド時間がマルチプレクサ内部の遅延に依存するので、イネーブルのエッジが遅いと新しい値を取り込んでしまうことがある。そしてループ上のどんなグリッチにも無防備で、復元のためのフィードバックが設計されていない。歩留まりぎりぎりのシリコンでは、この構造は保持する代わりに発振しうる。
The lecture's phrase "un-synthesised" is best read as: the tool will produce something — the slide's own figures show four muxes with their outputs looped back — but that something is not a mapped, characterisable circuit, and no downstream tool will treat it as one.本講那句「un-synthesised」最好讀成:工具會產生某個東西——投影片自己的圖就畫出四個輸出繞回去的多工器——但那個東西不是一個已映射、可特徵化的電路,而且下游不會有任何工具把它當成一個電路看待。講義の「un-synthesised」という語はこう読むのがよい。ツールは何かを出力する——スライド自身の図が出力をループバックさせたマルチプレクサを四つ描いている——が、その何かはマッピング済みで特性化できる回路ではなく、下流のどのツールもそれを回路として扱わない。
Here is the result worth remembering. The author of latch_if1 wanted a latch — it is in the module name. The self-assignment was an attempt to express "hold" explicitly, because leaving it implicit felt sloppy. The outcome is a structure that is functionally a latch, electrically a worse latch, and administratively a disaster.這裡有個值得記住的結果。latch_if1 的作者想要一個鎖存器——模組名字就這麼寫著。自我賦值是想把「保持」明白地表達出來的嘗試,因為把它留成隱含的感覺很邋遢。結果是一個在功能上是鎖存器、在電性上是更差的鎖存器、在行政上是一場災難的結構。ここに覚えておく価値のある結果がある。latch_if1 の作者はラッチが欲しかった。モジュール名にそう書いてある。自己代入は「保持」を明示的に表そうとした試みで、暗黙のままにしておくのがだらしなく感じられたのだ。その結果は、機能的にはラッチ、電気的にはより劣悪なラッチ、そして運用上は災厄である構造だった。
always @ (latch_enable or data_in) if (latch_enable) data_out = data_in; else data_out = data_out; // -> mux with feedback; not mappable
always @ (latch_enable or data_in)
if (latch_enable) data_out = data_in;
// -> four LATQ cells from the libraryOmitting the else matches the tool's latch-inference template. Templates are how synthesisers recognise storage: a fixed set of code shapes that the front end knows how to replace with a library cell. An if with no else in a level-sensitive block is the latch template; always @(posedge clk) is the flip-flop template, which is Lecture 6's subject. Writing the hold explicitly steps outside the template, and outside the template the tool has no replacement to offer — it falls back to building literally what you said.省略 else 剛好符合工具的鎖存器推論範本。範本就是合成器辨識儲存元件的方式:一組固定的程式碼形狀,前端知道該用哪個元件庫元件去替換它們。準位敏感區塊裡一個沒有 else 的 if,就是鎖存器範本;always @(posedge clk) 是正反器範本,那是第 6 講的主題。把保持明白地寫出來,就踏出了範本之外,而在範本之外工具沒有任何替換品可以提供——它只好退回去,照你說的字面意思造。else を省くことは、ツールのラッチ推論テンプレートに一致する。テンプレートこそ合成器が記憶素子を認識する仕組みであり、フロントエンドがライブラリセルに置き換える方法を知っている、固定されたコードの形の集合である。レベルセンシティブなブロックで else のない if がラッチのテンプレートであり、always @(posedge clk) がフリップフロップのテンプレートで、こちらは第 6 講の主題である。保持を明示的に書けばテンプレートの外へ出てしまい、外ではツールに差し出す置き換えがない。書かれた文字どおりのものを作る以外になくなるのだ。
Inference works by pattern matching against a small catalogue of templates. Express your intent in a template and you get the intended cell. Express the same intent more explicitly, more cleverly, or more "completely" than the template and you fall off the catalogue, and the result is whatever a literal reading of your code implies. This is why RTL coding style is not a matter of taste: the templates are the interface, and deviating from them is like calling an API with the wrong arguments.推論的運作方式是對一小本範本目錄做樣式比對。把你的意圖寫進一個範本,你就得到想要的元件。把同樣的意圖寫得比範本更明白、更聰明或更「完整」,你就掉出目錄之外,結果就是你程式碼的字面解讀所蘊涵的任何東西。這就是為什麼 RTL 寫碼風格不是品味問題:那些範本就是介面,偏離它們就像用錯誤的引數去呼叫一個 API。推論は小さなテンプレートのカタログに対するパターン照合として働く。意図をテンプレートの中で表現すれば、意図したセルが得られる。同じ意図をテンプレートより明示的に、より巧みに、より「完全」に表現すれば、カタログから外れ、結果はコードを字句どおりに読んだときに含意されるものになる。RTL のコーディングスタイルが好みの問題でない理由がこれである。テンプレートはインターフェースであり、そこから外れるのは API を誤った引数で呼ぶようなものだ。
Note also the second-order lesson about latch_case1. That module's case lists five of eight values of {enable, set, clear} and closes with the self-assigning default. Replace the default with default: latch_out = 1'b0; and you get clean combinational logic that forces zero on the three unlisted combinations. Delete the default entirely and you get a clean inferred latch whose enable is the OR of the five listed conditions. Both are mappable. Only the self-assignment is not. The construct that looks safest is the only unusable one.也請注意關於 latch_case1 的第二層教訓。那個模組的 case 列出 {enable, set, clear} 八個值裡的五個,並以自我賦值的 default 收尾。把那個 default 換成 default: latch_out = 1'b0;,你得到乾淨的組合邏輯,在三個沒列出的組合上強制為零。把 default 整個刪掉,你得到一個乾淨的推論鎖存器,致能是那五個列出條件的 OR。兩者都可映射。只有自我賦值不行。看起來最安全的那個構件,是唯一不能用的。latch_case1 についての二次的な教訓にも注目したい。このモジュールの case は {enable, set, clear} の八つの値のうち五つを列挙し、自己代入の default で締めくくっている。その default を default: latch_out = 1'b0; に置き換えれば、列挙されていない三つの組合せでゼロを強制する、きれいな組合せ論理が得られる。default を丸ごと削れば、列挙された五条件の OR をイネーブルとする、きれいな推論ラッチが得られる。どちらもマッピングできる。できないのは自己代入だけである。最も安全に見える構文が、唯一使えないものなのだ。
Problem:題目:問題:
latch_if1 three ways: as an intentional latch, as pure combinational logic that outputs zero when disabled, and as an edge-triggered register (forward reference to Lecture 6).用三種方式改寫 latch_if1:寫成一個刻意的鎖存器、寫成在停用時輸出零的純組合邏輯,以及寫成一個邊緣觸發的暫存器(前向參照第 6 講)。latch_if1 を三通りに書き直せ。意図的なラッチとして、無効時にゼロを出す純粋な組合せ論理として、そしてエッジトリガのレジスタとして(第 6 講への前方参照)。always @* y = y + 1; a combinational loop? What does the simulator do? What does the synthesiser do?always @* y = y + 1; 是組合迴路嗎?模擬器會做什麼?合成器會做什麼?always @* y = y + 1; は組合せループか。シミュレータは何をするか。合成器は何をするか。always @* begin t = a & b; y = t | c; end a loop? Explain why the intermediate variable t does not create one.always @* begin t = a & b; y = t | c; end 是迴路嗎?解釋為什麼中間變數 t 不會造出一個迴路。always @* begin t = a & b; y = t | c; end はループか。中間変数 t がループを作らない理由を説明せよ。assign bus = oe ? drv : bus; intending "hold the bus when not driving". Diagnose it and give the correct form.一位同事寫了 assign bus = oe ? drv : bus;,想表達「不驅動時保住匯流排」。診斷它,並給出正確的寫法。同僚が「駆動していないときはバスを保持する」つもりで assign bus = oe ? drv : bus; と書いた。これを診断し、正しい形を示せ。// (a-i) intentional latch: match the template, no else always @ (latch_enable or data_in) if (latch_enable) data_out = data_in; // (a-ii) pure combinational: every path gets a new value always @ (latch_enable or data_in) if (latch_enable) data_out = data_in; else data_out = 4'b0; // equivalently: assign data_out = latch_enable ? data_in : 4'b0; // (a-iii) edge-triggered register (Lecture 6) always @ (posedge clk) if (latch_enable) data_out <= data_in; // non-blocking
2. Yes, and a pathological one. y is both the target and an operand, so @* includes y in the sensitivity list, the assignment changes y, which retriggers the block at the same simulation time, forever: the simulator hangs at time 0 without advancing. The synthesiser reports a combinational feedback loop through an incrementer. This is the "infinite loops (avoided!!)" note on the lecture's always-block slide, in its purest form.2. 是,而且是病態的那種。y 同時是目標與運算元,所以 @* 把 y 放進敏感度列表,而賦值改變了 y,於是在同一個模擬時間點永遠地重新觸發這個區塊:模擬器會卡在時間 0 不前進。合成器會回報一條穿過遞增器的組合回授迴路。這就是本講 always 區塊那張投影片上「infinite loops (avoided!!)」那句註解最純粹的形式。2. はい、しかも病的な例である。y は対象であると同時にオペランドなので、@* は y をセンシティビティリストに含める。代入が y を変え、同じシミュレーション時刻でブロックが永遠に再起動する。シミュレータは時刻 0 で止まったまま進まない。合成器はインクリメンタを通る組合せフィードバックループを報告する。講義の always ブロックのスライドにある「infinite loops (avoided!!)」という注記の、最も純粋な形がこれである。
3. No loop. t is assigned before it is read, and it is read only after being assigned on every path, so the dependency graph is $a,b \to t \to y$ — acyclic. The tool either keeps t as an internal node or eliminates it into y = (a\&b)|c. The rule is not "never read a variable you also write"; it is "never read a variable whose value on this evaluation depends on this evaluation's own result".3. 不是迴路。t 在被讀之前已經被賦值,而且它只在每一條路徑上都被賦值之後才被讀,所以相依圖是 $a,b \to t \to y$——無環。工具要嘛把 t 留成一個內部節點,要嘛把它消去成 y = (a\&b)|c。規則不是「絕不讀一個你也寫的變數」;規則是「絕不讀一個其本次求值的值取決於本次求值自身結果的變數」。3. ループではない。t は読まれる前に代入されており、しかもすべての経路で代入されたあとにのみ読まれるので、依存グラフは $a,b \to t \to y$ で非巡回である。ツールは t を内部ノードとして残すか、y = (a\&b)|c へ消去する。規則は「書く変数を決して読むな」ではなく、「今回の評価における値が今回の評価の結果自体に依存するような変数を読むな」である。
4. bus appears on both sides of a continuous assignment, which is a combinational loop with no gating at all — the simulator will evaluate it repeatedly and may deadlock, and the synthesiser will report a loop. The intent is a tri-state driver, and the correct form is assign bus = oe ? drv : 32'bz; (Section 9.1). The high-impedance branch means "I am not driving", which is what "hold" means on a shared bus; writing bus there instead means "drive the bus with whatever is already on the bus", which is not a thing a circuit can do.4. bus 出現在一個連續賦值的兩邊,那是一個完全沒有任何閘控的組合迴路——模擬器會反覆求值而且可能鎖死,合成器會回報一個迴路。意圖是一個三態驅動器,正確的寫法是 assign bus = oe ? drv : 32'bz;(第 9.1 節)。那個高阻抗分支的意思是「我沒有在驅動」,而那正是在一條共用匯流排上「保持」的意思;在那裡寫 bus 的意思反而變成「用匯流排上已經有的東西去驅動匯流排」,那不是電路做得到的事。4. bus が連続代入の両辺に現れており、これはゲートが一切ない組合せループである。シミュレータは繰り返し評価してデッドロックしうるし、合成器はループを報告する。意図はトライステートドライバであり、正しい形は assign bus = oe ? drv : 32'bz; である(第 9.1 節)。高インピーダンスの枝は「私は駆動していない」を意味し、共有バスにおける「保持」とはまさにそれである。そこに bus と書けば「バスに既に載っているもので バスを駆動する」という意味になり、それは回路にできることではない。
The lecture states the rule in three bullets and it is the most useful single fact in the whole lecture, because it tells you how to control the shape of your logic without writing structural code.本講用三個要點陳述這條規則,而它是整堂課裡最有用的單一事實,因為它告訴你如何在不寫結構式程式碼的情況下控制你邏輯的形狀。講義はこの規則を三つの箇条書きで述べる。構造的なコードを書かずに論理の形を制御する方法を教えてくれるので、これは講義全体で最も有用な単一の事実である。
case are mutually exclusive, the tool treats them as having equal priority and synthesises a multiplexer, not a priority structure.如果一個 case 的各項目互斥,工具會視它們優先權相同,並合成出一個多工器,而不是優先權結構。case の項目が互いに排他的なら、ツールはそれらを同じ優先順位とみなし、優先順位構造ではなくマルチプレクサを合成する。if chain also synthesises to a multiplexer when its branch conditions are mutually exclusive.一個 if 串在它的分支條件互斥時,同樣會合成出一個多工器。if の連鎖も、分岐条件が互いに排他的ならマルチプレクサに合成される。The reason is pure Boolean algebra. An if/else if chain means, formally, $y = C_1 v_1 + \overline{C_1}C_2 v_2 + \overline{C_1}\,\overline{C_2}C_3 v_3 + \cdots$. If the conditions are pairwise exclusive — that is, $C_iC_j = 0$ for $i \neq j$ — then $\overline{C_1}C_2 = C_2$, $\overline{C_1}\,\overline{C_2}C_3 = C_3$, and so on. The guard terms vanish identically and the expression collapses to the flat one-hot selection $y = C_1v_1 + C_2v_2 + C_3v_3 + \cdots$, which is a multiplexer. No special analysis is needed; the ordinary optimiser discovers it while simplifying.理由是純粹的布林代數。一個 if/else if 串在形式上的意思是 $y = C_1 v_1 + \overline{C_1}C_2 v_2 + \overline{C_1}\,\overline{C_2}C_3 v_3 + \cdots$。如果這些條件兩兩互斥——也就是對 $i \neq j$ 有 $C_iC_j = 0$——那麼 $\overline{C_1}C_2 = C_2$、$\overline{C_1}\,\overline{C_2}C_3 = C_3$,依此類推。那些守衛項恆等地消失,運算式塌成扁平的 one-hot 選擇 $y = C_1v_1 + C_2v_2 + C_3v_3 + \cdots$,那就是一個多工器。不需要任何特殊分析;普通的最佳化器在化簡的過程中就會發現它。理由は純粋なブール代数である。if/else if の連鎖は形式的には $y = C_1 v_1 + \overline{C_1}C_2 v_2 + \overline{C_1}\,\overline{C_2}C_3 v_3 + \cdots$ を意味する。条件が対ごとに排他的、すなわち $i \neq j$ に対し $C_iC_j = 0$ なら、$\overline{C_1}C_2 = C_2$、$\overline{C_1}\,\overline{C_2}C_3 = C_3$ と続く。ガード項は恒等的に消え、式は平坦なワンホット選択 $y = C_1v_1 + C_2v_2 + C_3v_3 + \cdots$ に潰れる。これがマルチプレクサである。特別な解析は要らず、ふつうの最適化器が簡約の途中でこれを見つける。
When the conditions overlap, those guard terms are not redundant, and each one costs a gate on the path. That is what a priority chain is: the accumulated "none of the higher-priority conditions were true" terms. The slide's mux_4pri makes this concrete:當條件重疊時,那些守衛項就不是冗餘的,而且每一個都在路徑上花掉一個閘。優先權鏈就是這個東西:那些累積起來的「所有更高優先權的條件都不成立」的項。投影片的 mux_4pri 把這件事具體化:条件が重なるとき、ガード項は冗長ではなくなり、一つ一つが経路上でゲートを消費する。優先順位チェーンとはそれのことである。積み重なった「より優先度の高い条件はどれも成り立たなかった」という項の列だ。スライドの mux_4pri がそれを具体的に示す。
module mux_4pri (y, a, b, c, d, sel_a, sel_b, sel_c);
input a, b, c, d, sel_a, sel_b, sel_c;
output y;
reg y;
always @ (sel_a or sel_b or sel_c or a or b or c or d)
begin // conditions are NOT mutually exclusive
if (sel_a == 1) y = a;
else if (sel_b == 0) y = b;
else if (sel_c == 1) y = c;
else y = d;
end
endmodule
The slide's figure draws this as three cascaded 2:1 muxes with sel_c at the far left and sel_a at the far right, labelled "low" and "high". That ordering is the thing to internalise: the first condition tested is the last mux before the output, because it is the one that overrides everything else. Students routinely draw it backwards.投影片的圖把這畫成三個串接的 2:1 多工器,sel_c 在最左邊、sel_a 在最右邊,標示為「low」與「high」。那個順序是必須內化的事:第一個被判斷的條件,是輸出前的最後一個多工器,因為它是會蓋過其他一切的那一個。學生經常畫反。スライドの図はこれを縦続接続した 2:1 マルチプレクサ三つとして描き、sel_c を左端、sel_a を右端に置いて「low」「high」と注記している。この順序こそ体に入れておくべきことである。最初に判定される条件が、出力の直前の最後のマルチプレクサになる。それが他のすべてを上書きする条件だからだ。学生はたいてい逆に描く。
The timing consequence is direct. Each condition in the chain adds one mux level to the data inputs beneath it, so the worst-case path is from the lowest-priority data input through $N-1$ muxes. For a sixteen-way priority chain that is fifteen levels — likely to be your critical path. The standard remedies are to reduce the chain to a mux by making the conditions exclusive (for example, by pre-decoding the requests into a one-hot vector), or to build a balanced tree of smaller priority blocks, which gives $O(\log N)$ depth at the cost of extra "any request below me" terms. Both are architecture decisions, and both are expressed by changing the conditions, not by adding directives.時序後果很直接。鏈上的每一個條件都為它底下的資料輸入多加一層多工器,所以最差情況的路徑是從優先權最低的資料輸入穿過 $N-1$ 個多工器。對一個十六路的優先權鏈那就是十五層——很可能就是你的關鍵路徑。標準的補救方法,是讓條件互斥以把鏈化成多工器(例如把請求預先解碼成一個 one-hot 向量),或者建一棵由較小優先權區塊組成的平衡樹,以多出一些「我底下有任何請求」的項為代價,把深度降到 $O(\log N)$。兩者都是架構決策,而且兩者都是靠改變條件來表達,不是靠加指示詞。タイミング上の帰結は直接的である。チェーンの各条件は、その下のデータ入力に対しマルチプレクサを 1 段追加するので、最悪経路は最も優先度の低いデータ入力から $N-1$ 個のマルチプレクサを通る。16 路の優先順位チェーンなら 15 段であり、たいていそれがクリティカルパスになる。標準的な改善策は、条件を排他的にして(たとえば要求をワンホットのベクタに事前デコードして)チェーンをマルチプレクサに変えるか、より小さな優先順位ブロックの平衡木を組み、「自分より下に要求があるか」の項を追加する代償で深さを $O(\log N)$ にすることである。どちらもアーキテクチャの決定であり、どちらも条件を変えることで表現する。ディレクティブを足すことではない。
The lecture gives the plain encoder twice — as an if chain and as a case — and marks them "Equivalent!!".本講把一般編碼器寫了兩次——一次是 if 串,一次是 case——並標註「Equivalent!!」。講義は素のエンコーダを二度示す。一度は if の連鎖として、一度は case として。そして「Equivalent!!」と注記する。
module encoder (Data, Code);
input [7:0] Data;
output [2:0] Code;
reg [2:0] Code;
always @ (Data)
begin
if (Data == 8'b00000001) Code = 0;
else if (Data == 8'b00000010) Code = 1;
else if (Data == 8'b00000100) Code = 2;
else if (Data == 8'b00001000) Code = 3;
else if (Data == 8'b00010000) Code = 4;
else if (Data == 8'b00100000) Code = 5;
else if (Data == 8'b01000000) Code = 6;
else if (Data == 8'b10000000) Code = 7;
else Code = 3'bx;
end
endmodulemodule encoder (Data, Code);
input [7:0] Data;
output [2:0] Code;
reg [2:0] Code;
always @ (Data)
case (Data)
8'b00000001 : Code = 0;
8'b00000010 : Code = 1;
8'b00000100 : Code = 2;
8'b00001000 : Code = 3;
8'b00010000 : Code = 4;
8'b00100000 : Code = 5;
8'b01000000 : Code = 6;
8'b10000000 : Code = 7;
default : Code = 3'bx;
endcase
endmoduleThey are equivalent because the eight patterns are equality tests against distinct constants — mutually exclusive by construction — so Section 8.1's collapse applies and both become flat selection logic. What makes this example remarkable is how far the optimiser then goes.它們等價,是因為那八個樣式是對相異常數的相等比較——在構造上就互斥——所以第 8.1 節的塌陷適用,兩者都變成扁平的選擇邏輯。這個例子真正了不起的地方,是最佳化器接下來走得有多遠。等価であるのは、八つのパターンが相異なる定数との等価比較であり、構成上すでに排他的だからである。したがって第 8.1 節の潰れが適用され、どちらも平坦な選択論理になる。この例が注目に値するのは、そのあと最適化器がどこまで踏み込むかである。
The default: Code = 3'bx declares that all 248 non-one-hot values of Data are don't-cares. The optimiser is therefore free to produce anything for them, and it uses that freedom to discard the equality comparators entirely. Consider $Code[2]$: it must be 1 for the four one-hot patterns $D_4, D_5, D_6, D_7$ and 0 for $D_0 \ldots D_3$; everywhere else it does not matter. The cheapest function meeting that is simply $D_4 + D_5 + D_6 + D_7$.default: Code = 3'bx 宣告了 Data 的 248 個非 one-hot 值全都是不在意項。因此最佳化器可以自由地為它們產生任何東西,而它用這份自由把那些相等比較器整個丟掉。考慮 $Code[2]$:對四個 one-hot 樣式 $D_4, D_5, D_6, D_7$ 它必須是 1,對 $D_0 \ldots D_3$ 必須是 0;其他地方都無所謂。滿足這個條件最便宜的函數,就只是 $D_4 + D_5 + D_6 + D_7$。default: Code = 3'bx は、Data のワンホットでない 248 個の値すべてがドントケアであると宣言する。したがって最適化器はそれらに対して何を出してもよく、その自由を使って等価比較器をまるごと捨てる。$Code[2]$ を考えよう。四つのワンホットパターン $D_4, D_5, D_6, D_7$ では 1、$D_0 \ldots D_3$ では 0 でなければならず、それ以外はどうでもよい。これを満たす最も安い関数は、単に $D_4 + D_5 + D_6 + D_7$ である。
Three or4 cells — about 6 GE for an eight-input function, which matches the slide's synthesis figure exactly. Compare that with what you would get if the default had been Code = 3'b000 instead: the tool would then have to distinguish one-hot inputs from non-one-hot ones, which requires eight 8-bit equality comparators, perhaps 60 GE. A single choice of default value changes the area by a factor of ten. Section 10 returns to this.三個 or4 元件——一個八輸入函數約 6 GE,與投影片的合成圖完全相符。拿它跟 default 改成 Code = 3'b000 的情況比較:那時工具就必須分辨哪些輸入是 one-hot、哪些不是,而那需要八個 8 位元相等比較器,大概 60 GE。一個 default 值的選擇讓面積差了十倍。第 10 節會回到這件事。or4 セル 3 個、8 入力の関数で約 6 GE であり、スライドの合成図と完全に一致する。default が Code = 3'b000 だった場合と比べてみよう。その場合ツールはワンホットの入力とそうでない入力を区別しなければならず、8 ビットの等価比較器が八つ、おそらく 60 GE を要する。default の値の選択一つで面積が 10 倍違う。第 10 節でこの話に戻る。
The price of that bargain is a deliberate RTL-versus-netlist divergence, and it is the subject of Practice 9: feed the netlist a non-one-hot input and it produces a definite, plausible-looking, and completely meaningless code, where the RTL would have produced xxx and alerted you.這筆划算交易的代價,是刻意製造的 RTL 與網表分歧,而那正是練習 9 的主題:餵給網表一個非 one-hot 的輸入,它會產生一個確定的、看起來很合理、卻完全沒有意義的編碼,而 RTL 本來會產生 xxx 來警告你。この好取引の代価は、意図的に作る RTL とネットリストの乖離であり、それが演習 9 の主題である。ワンホットでない入力をネットリストに与えると、確定していてもっともらしく見え、まったく無意味なコードが出てくる。RTL なら xxx を出して警告してくれたはずのところである。
valid_data8.3 8:3 優先權編碼器,以及 valid_data 的角色8.3 8:3 プライオリティエンコーダと valid_data の役割module priority (Data, Code, valid_data);
input [7:0] Data;
output [2:0] Code;
output valid_data;
reg [2:0] Code;
assign valid_data = |Data; // reduction or
always @ (Data)
begin
if (Data[7]) Code = 7;
else if (Data[6]) Code = 6;
else if (Data[5]) Code = 5;
else if (Data[4]) Code = 4;
else if (Data[3]) Code = 3;
else if (Data[2]) Code = 2;
else if (Data[1]) Code = 1;
else if (Data[0]) Code = 0;
else Code = 3'bx;
end
endmodulemodule priority (Data, Code, valid_data);
input [7:0] Data;
output [2:0] Code;
output valid_data;
reg [2:0] Code;
assign valid_data = |Data;
always @ (Data)
casex (Data)
8'b1xxxxxxx : Code = 7;
8'b01xxxxxx : Code = 6;
8'b001xxxxx : Code = 5;
8'b0001xxxx : Code = 4;
8'b00001xxx : Code = 3;
8'b000001xx : Code = 2;
8'b0000001x : Code = 1;
8'b00000001 : Code = 0;
default : Code = 3'bx;
endcase
endmoduleNow the conditions overlap: several bits of Data can be high at once, and the specification is that the highest index wins. The if chain expresses that by ordering. The casex expresses it by patterns whose leading zeros encode "no higher bit is set" — note that the wildcard positions are below the leading one in each pattern, never above it. If you wrote 8'bxxxxxxx1 : Code = 0; as the first arm you would have a completely different (and wrong) priority order, because case arms are evaluated top to bottom and the first match wins.現在條件重疊了:Data 的好幾個位元可能同時為高,而規格是最高索引者勝。if 串用順序表達這件事。casex 則用樣式表達,每個樣式前導的零編碼了「沒有更高的位元被設起來」——注意萬用字元的位置在每個樣式中前導的那個 1 底下,絕不在它上面。如果你把 8'bxxxxxxx1 : Code = 0; 寫成第一個分支,你會得到一個完全不同(而且錯誤)的優先權順序,因為 case 的分支是由上往下求值,第一個匹配者勝。今度は条件が重なる。Data の複数ビットが同時に高になりうるのに、仕様は最上位の添字が勝つというものだ。if の連鎖はそれを順序で表現する。casex はパターンで表現し、各パターンの先頭の 0 が「より上のビットは立っていない」を符号化する。ワイルドカードの位置が各パターンの先頭の 1 の下にあり、決してその上にないことに注意したい。最初の枝に 8'bxxxxxxx1 : Code = 0; と書けば、まったく異なる(そして誤った)優先順位になる。case の枝は上から下へ評価され、最初に一致したものが勝つからである。
Unlike the plain encoder, these guard terms do not cancel, so real logic appears:與一般編碼器不同,這裡的守衛項不會被消掉,所以真的會出現邏輯:素のエンコーダと違い、ここではガード項が消えないので、実際に論理が現れる。
The structure is visible in the equations: each lower-priority term is gated by the complements of the higher-priority bits that would override it. That masking is why the slide's synthesis figure for the priority encoder is substantially larger than the one for the plain encoder — inverters and NOR gates appear where the plain version had only ORs.結構在方程式裡看得見:每一個較低優先權的項,都被那些會蓋過它的較高優先權位元的補數閘控。這層遮罩就是為什麼投影片給優先權編碼器的合成圖,比給一般編碼器的那張大得多——一般版本只有 OR 的地方,這裡出現了反相器與 NOR 閘。構造は方程式に見てとれる。優先度の低い項はどれも、それを上書きするより上位のビットの補数でゲートされている。このマスキングこそ、プライオリティエンコーダの合成図が素のエンコーダの図よりかなり大きい理由である。素の版では OR しかなかったところに、インバータと NOR ゲートが現れる。
The valid_data output is not decoration. A priority encoder with no inputs asserted has no meaningful code to report; the RTL says 3'bx and the netlist says something arbitrary. valid_data = |Data — the reduction OR, one bit saying "at least one request is present" — is the signal that lets the consumer distinguish "request from source 0" from "no request at all". This is the general pattern already mentioned in Section 2.2: when you are tempted to encode "unknown" in the data, add a valid bit instead. Every real arbiter, interrupt controller and leading-zero counter does this.valid_data 這個輸出不是裝飾品。一個沒有任何輸入被設起來的優先權編碼器,沒有任何有意義的編碼可回報;RTL 說 3'bx,網表說某個任意的東西。valid_data = |Data——歸約 OR,一個位元說「至少有一個請求存在」——就是讓消費端能分辨「來源 0 的請求」與「根本沒有請求」的那條訊號。這就是第 2.2 節已經提過的通用樣式:當你想把「未知」編碼進資料裡時,改成加一個 valid 位元。每一個真實的仲裁器、中斷控制器與前導零計數器都這麼做。valid_data という出力は飾りではない。入力が一つも立っていないプライオリティエンコーダには、報告すべき意味のあるコードがない。RTL は 3'bx と言い、ネットリストは何か任意のものを言う。valid_data = |Data、すなわち「要求が少なくとも一つある」ことを示す 1 ビットのリダクション OR こそ、受け手が「ソース 0 からの要求」と「要求がまったくない」を区別できるようにする信号である。これは第 2.2 節で既に触れた一般的なパターンである。「不明」をデータに符号化したくなったら、代わりに valid ビットを足すこと。現実のアービタ、割り込みコントローラ、先行ゼロカウンタはどれもそうしている。
casex, casez and the trouble with x in a pattern8.4 casex、casez,以及樣式裡出現 x 的麻煩8.4 casex・casez と、パターン中の x の厄介さVerilog gives three case flavours and the difference is in what counts as a match.Verilog 給了三種 case 口味,差別在於什麼算是匹配。Verilog は case を三種類用意しており、違いは何を一致とみなすかにある。
| Statement敘述文 | Wildcards in the case itemcase 項目中的萬用字元case 項目側のワイルドカード | Wildcards in the selector expression選擇運算式中的萬用字元セレクタ式側のワイルドカード | Verdict判決評定 |
|---|---|---|---|
case | None — exact bitwise match無——逐位元精確匹配なし——ビット単位の厳密一致 | An x or z in the selector matches nothing選擇運算式中的 x 或 z 什麼都匹配不到セレクタ中の x や z は何にも一致しない | Safe default choice安全的預設選擇安全な既定の選択 |
casez | z and ? are wildcardsz 與 ? 是萬用字元z と ? がワイルドカード | A z in the selector also matches any item bit選擇運算式中的 z 也會匹配任何項目位元セレクタ中の z も項目側の任意のビットに一致する | Preferred for wildcard matching做萬用匹配時的首選ワイルドカード照合にはこちらが望ましい |
casex | x, z and ? are wildcardsx、z 與 ? 都是萬用字元x・z・? がワイルドカード | An x in the selector matches any item選擇運算式中的 x 會匹配任何項目セレクタ中の x はどの項目にも一致する | Dangerous in simulation在模擬中很危險シミュレーションでは危険 |
The asymmetry in the third column is the trap. Wildcarding is bidirectional: casex treats an x on either side as "don't care". So if a bit of Data is unknown in simulation — because a register came up uninitialised, or because a bus is momentarily floating — then casex (Data) will happily match the very first arm, and the priority encoder will report Code = 7 with complete confidence. The x that should have propagated and exposed the uninitialised register is swallowed instead. This is the classic "casex hides x-propagation bugs" problem, and it is the reason most modern coding standards ban casex outright.第三欄的不對稱就是陷阱。萬用匹配是雙向的:casex 把任一邊的 x 都當成「不在意」。所以如果模擬中 Data 的某個位元是未知的——因為某個暫存器上電時沒初始化,或因為某條匯流排暫時浮接——那麼 casex (Data) 會高高興興地匹配到第一個分支,而優先權編碼器會滿懷信心地回報 Code = 7。那個本該傳播出去、揭露未初始化暫存器的 x,反而被吞掉了。這就是經典的「casex 會藏起 x 傳播錯誤」問題,也是大多數現代寫碼規範乾脆禁用 casex 的理由。三列目の非対称さこそが落とし穴である。ワイルドカード照合は双方向で、casex はどちら側の x も「ドントケア」として扱う。だからシミュレーション中に Data のあるビットが不定なら——レジスタが未初期化で立ち上がった、あるいはバスが一時的に浮いている——casex (Data) は嬉々として最初の枝に一致し、プライオリティエンコーダは自信満々に Code = 7 と報告する。伝播して未初期化のレジスタを暴くはずだった x は、代わりに飲み込まれてしまう。これが古典的な「casex は x 伝播のバグを隠す」問題であり、現代のコーディング規約の大半が casex を端的に禁ずる理由である。
casez with ?, not casex with x請用搭配 ? 的 casez,不要用搭配 x 的 casex? を使う casez を用い、x を使う casex は使わないことRewriting the slide's priority encoder as casez with ? in place of x — 8'b1??????? : Code = 7; and so on — gives an identical netlist and a strictly better simulation. z is far rarer than x on an internal signal, so the accidental-match hazard nearly vanishes, and the ? character reads unambiguously as "don't care" rather than "unknown". There is no case in which casex is preferable to casez.把投影片的優先權編碼器改寫成 casez,並把 x 換成 ?——例如 8'b1??????? : Code = 7;——會得到完全相同的網表,以及嚴格更好的模擬。在一條內部訊號上 z 遠比 x 罕見,所以意外匹配的風險幾乎消失,而 ? 這個字元讀起來明確就是「不在意」,而不是「未知」。沒有任何情況下 casex 會優於 casez。スライドのプライオリティエンコーダを casez に書き換え、x を ? に置き換える——たとえば 8'b1??????? : Code = 7;——と、ネットリストは同一で、シミュレーションは厳密により良くなる。内部信号で z は x よりはるかにまれなので偶然一致の危険はほぼ消え、? という文字は「不明」ではなく「ドントケア」と明確に読める。casex が casez より望ましい場面は存在しない。
One more detail that catches people: wildcards in a case item are a synthesis-visible construct and are fine, but an x or z appearing in the selector is a simulation-only phenomenon. Synthesis reasons only over 0 and 1. So all of the behaviour described in this subsection is behaviour of your RTL simulation; the netlist is the same either way. That is precisely what makes it dangerous — the mismatch is in the direction that makes the RTL look healthier than the hardware.還有一個細節會絆倒人:case 項目裡的萬用字元是合成看得見的構件,沒有問題;但出現在選擇運算式裡的 x 或 z 純粹是模擬現象。合成只在 0 與 1 上推理。所以本小節描述的所有行為,都是你 RTL 模擬的行為;兩種寫法的網表都一樣。而這正是它危險的原因——這個不匹配的方向,剛好讓 RTL 看起來比硬體更健康。もう一つ、人をつまずかせる細部がある。case の項目側のワイルドカードは合成から見える構文であり問題ない。しかしセレクタ式に現れる x や z は純粋にシミュレーション上の現象である。合成は 0 と 1 の上でしか推論しない。したがって本小節で述べた振る舞いはすべて RTL シミュレーションの振る舞いであり、ネットリストはどちらでも同じである。そしてそれこそが危険な理由である。この不一致は、RTL をハードウェアより健康そうに見せる向きに働く。
module decoder (Code, Data);
output [7:0] Data;
input [2:0] Code;
reg [7:0] Data;
always @ (Code)
begin
if (Code == 0) Data = 8'b00000001;
else if (Code == 1) Data = 8'b00000010;
else if (Code == 2) Data = 8'b00000100;
else if (Code == 3) Data = 8'b00001000;
else if (Code == 4) Data = 8'b00010000;
else if (Code == 5) Data = 8'b00100000;
else if (Code == 6) Data = 8'b01000000;
else if (Code == 7) Data = 8'b10000000;
else Data = 8'bx;
end
endmodule always @ (Code)
case (Code)
0 : Data = 8'b00000001;
1 : Data = 8'b00000010;
2 : Data = 8'b00000100;
3 : Data = 8'b00001000;
4 : Data = 8'b00010000;
5 : Data = 8'b00100000;
6 : Data = 8'b01000000;
7 : Data = 8'b10000000;
default: Data = 8'bx;
endcase
The default is unreachable — a three-bit selector has exactly eight values and all eight are listed — but writing it costs nothing and immunises the code against someone widening Code later. The slide's homework note asks you to write a multiplexer in the same two styles.那個 default 不可到達——一個三位元的選擇線恰有八個值,而八個全列出來了——但寫它不花任何代價,而且讓程式碼對日後有人把 Code 加寬免疫。投影片的作業註記要你用同樣的兩種風格寫一個多工器。この default は到達不能である。3 ビットのセレクタの値はちょうど八つで、その八つがすべて列挙されているからだ。だが書いてもコストはかからず、後日誰かが Code を広げてもコードは無事である。スライドの宿題の注記は、同じ二つのスタイルでマルチプレクサを書くよう求めている。
Each output bit is a single minterm of the three code bits: $Data[i] = m_i(Code)$. Written out, $Data[0] = \overline{c_2}\,\overline{c_1}\,\overline{c_0}$, $Data[1] = \overline{c_2}\,\overline{c_1}c_0$, through to $Data[7] = c_2c_1c_0$. A naive implementation is eight three-input AND gates plus three inverters, about 15 GE.每一個輸出位元都是三個編碼位元的一個最小項:$Data[i] = m_i(Code)$。寫開來就是 $Data[0] = \overline{c_2}\,\overline{c_1}\,\overline{c_0}$、$Data[1] = \overline{c_2}\,\overline{c_1}c_0$,一直到 $Data[7] = c_2c_1c_0$。天真的實作是八個三輸入 AND 閘加三個反相器,約 15 GE。各出力ビットは 3 本のコードビットの一つの最小項である。$Data[i] = m_i(Code)$。書き下せば $Data[0] = \overline{c_2}\,\overline{c_1}\,\overline{c_0}$、$Data[1] = \overline{c_2}\,\overline{c_1}c_0$、…、$Data[7] = c_2c_1c_0$ となる。素朴な実装は 3 入力 AND ゲート 8 個とインバータ 3 個、約 15 GE である。
The slide's synthesis figure shows something smarter, and it is worth understanding because it is a pattern you will meet in every memory address decoder. The tool builds a two-stage pre-decode: first it forms the four combinations of the two low bits using two inverters and a pair of NAND/AND cells, then it combines each of those with $c_2$ or $\overline{c_2}$. Because static CMOS implements inverting functions natively, NOR and NAND cells appear where you expected AND and OR. The result in the figure is two inverters, two nand2, one and2, one more inverter, then a mixture of nor2, nor3 and and3 cells driving the eight outputs — fewer transistors and a shallower worst-case path than the flat version, and the four shared pre-decode terms are extracted exactly once. This is Section 3's extraction doing its job on a structure you would not have thought to factor by hand.投影片的合成圖顯示了更聰明的東西,值得理解,因為這是你在每一個記憶體位址解碼器裡都會遇到的樣式。工具建了一個兩階段預解碼:先用兩個反相器與一對 NAND/AND 元件形成低兩位的四個組合,再把其中每一個與 $c_2$ 或 $\overline{c_2}$ 結合。因為靜態 CMOS 原生實現的是反相函數,所以你預期是 AND 與 OR 的地方出現的是 NOR 與 NAND 元件。圖中的結果是兩個反相器、兩個 nand2、一個 and2、再一個反相器,然後是一組混用的 nor2、nor3 與 and3 元件驅動那八個輸出——電晶體比扁平版本少,最差路徑也更淺,而那四個共用的預解碼項恰好只被抽出一次。這就是第 3 節的抽出,在一個你不會想到要親手因式分解的結構上發揮作用。スライドの合成図はもっと賢いものを示しており、理解しておく価値がある。あらゆるメモリアドレスデコーダで出会うパターンだからだ。ツールは二段のプリデコードを組む。まずインバータ 2 個と NAND/AND のセル対で下位 2 ビットの四つの組合せを作り、次にそれぞれを $c_2$ または $\overline{c_2}$ と結合する。静的 CMOS は反転関数をそのまま実装するので、AND と OR を予想した場所に NOR と NAND のセルが現れる。図の結果はインバータ 2 個、nand2 2 個、and2 1 個、さらにインバータ 1 個、そして nor2・nor3・and3 を混ぜたセル群が八つの出力を駆動するというものである。平坦版よりトランジスタが少なく、最悪経路も浅く、共有される四つのプリデコード項はちょうど一度だけ抽出されている。これは第 3 節の抽出が、手では因数分解しようと思いつかない構造の上で働いている姿である。
The general lesson is that an $n$-to-$2^n$ decoder built as a tree of pre-decodes costs about $2^n$ small gates with $\log n$ levels, rather than $2^n$ wide gates with one level. For a 7-to-128 address decoder the difference is the whole design.一般性的教訓是:一個以預解碼樹建成的 $n$ 對 $2^n$ 解碼器,代價大約是 $2^n$ 個小閘、$\log n$ 層,而不是 $2^n$ 個寬閘、一層。對一個 7 對 128 的位址解碼器而言,這個差別就是整個設計。一般的な教訓はこうである。プリデコードのツリーとして組んだ $n$ 対 $2^n$ のデコーダは、1 段の幅広ゲート $2^n$ 個ではなく、$\log n$ 段の小さなゲート約 $2^n$ 個で済む。7 対 128 のアドレスデコーダでは、この違いが設計そのものを決める。
Problem: Take Data = 8'b0010_0100 — bits 5 and 2 are high.題目:取 Data = 8'b0010_0100——第 5 與第 2 位元為高。問題:Data = 8'b0010_0100 とする。ビット 5 と 2 が高である。
encoder give in RTL simulation?一般 encoder 在 RTL 模擬中給出什麼?素の encoder は RTL シミュレーションで何を出すか。priority encoder give, and what is valid_data?priority 編碼器給出什麼,而 valid_data 是什麼?priority エンコーダは何を出すか。valid_data はどうなるか。Data = 8'b0000_0000, give Code and valid_data from the priority encoder in both RTL and netlist, and explain why the design is still usable.對 Data = 8'b0000_0000,給出優先權編碼器在 RTL 與網表兩邊的 Code 與 valid_data,並解釋為什麼這個設計仍然可用。Data = 8'b0000_0000 について、プライオリティエンコーダの Code と valid_data を RTL とネットリストの両方で示し、それでもこの設計が使える理由を説明せよ。default : Code = 3'bx; with default : Code = 3'b000; in the plain encoder "to be safe". What happens to the area, and is it actually safer?有人提議把一般編碼器裡的 default : Code = 3'bx; 換成 default : Code = 3'b000;「比較保險」。面積會怎麼樣,而這樣真的比較安全嗎?ある人が「安全のため」に素のエンコーダの default : Code = 3'bx; を default : Code = 3'b000; に換えようと提案した。面積はどうなるか。それは本当により安全か。1. 8'b00100100 is not one-hot, so no arm matches and the default fires: Code = 3'bxxx. The RTL is telling you, correctly, that the input violates the encoder's contract.1. 8'b00100100 不是 one-hot,所以沒有分支匹配,default 觸發:Code = 3'bxxx。RTL 正確地告訴你,這個輸入違反了編碼器的約定。1. 8'b00100100 はワンホットではないのでどの枝にも一致せず、default が発火して Code = 3'bxxx になる。RTL は、この入力がエンコーダの契約に違反していることを正しく伝えている。
2. The netlist computes the three ORs. $D_5 = 1$ and $D_2 = 1$. $Code[2] = D_4+D_5+D_6+D_7 = 1$. $Code[1] = D_2+D_3+D_6+D_7 = 1$. $Code[0] = D_1+D_3+D_5+D_7 = 1$. So Code = 3'b111 — a confident, specific, entirely wrong answer. This is the don't-care bargain made visible: you traded 54 GE for the loss of the x warning.2. 網表算的是那三個 OR。$D_5 = 1$ 且 $D_2 = 1$。$Code[2] = D_4+D_5+D_6+D_7 = 1$。$Code[1] = D_2+D_3+D_6+D_7 = 1$。$Code[0] = D_1+D_3+D_5+D_7 = 1$。所以 Code = 3'b111——一個很有自信、很具體、而且完全錯誤的答案。這就是不在意項這筆交易現形的樣子:你用失去那個 x 警告,換來 54 GE。2. ネットリストは三つの OR を計算する。$D_5 = 1$ かつ $D_2 = 1$ である。$Code[2] = D_4+D_5+D_6+D_7 = 1$、$Code[1] = D_2+D_3+D_6+D_7 = 1$、$Code[0] = D_1+D_3+D_5+D_7 = 1$。よって Code = 3'b111、自信に満ちて具体的で、まったく誤った答えである。これがドントケアの取引が姿を現したところである。x の警告を失う代わりに 54 GE を手に入れたのだ。
3. The highest set bit is 5, so Code = 3'b101, and valid_data = |Data = 1. The priority encoder has no contract to violate, which is exactly why it is the safer component when inputs may overlap.3. 被設起來的最高位元是 5,所以 Code = 3'b101,而 valid_data = |Data = 1。優先權編碼器沒有可違反的約定,這正是當輸入可能重疊時它是比較安全的元件的理由。3. 立っている最上位ビットは 5 なので Code = 3'b101、valid_data = |Data = 1 である。プライオリティエンコーダには破るべき契約がなく、入力が重なりうる場面でこちらが安全な部品である理由はまさにそこにある。
4. RTL: no arm matches, default gives Code = 3'bxxx; valid_data = 0. Netlist: Code is whatever the optimised equations give for all-zero inputs, which from the equations above is 3'b000; valid_data = 0. The design is usable because valid_data tells the consumer to ignore Code entirely. The divergence is confined to a signal that is defined as meaningless.4. RTL:沒有分支匹配,default 給出 Code = 3'bxxx;valid_data = 0。網表:Code 是那些最佳化後的方程式對全零輸入所給出的值,依上面的方程式就是 3'b000;valid_data = 0。這個設計仍然可用,因為 valid_data 告訴消費端完全忽略 Code。分歧被侷限在一條依定義就沒有意義的訊號上。4. RTL:どの枝にも一致せず、default が Code = 3'bxxx を与える。valid_data = 0。ネットリスト:Code は最適化された方程式が全ゼロ入力に対して返す値で、上の式からは 3'b000 になる。valid_data = 0。この設計が使えるのは、valid_data が受け手に Code を完全に無視せよと伝えるからである。乖離は、定義上無意味とされた信号の中に封じ込められている。
5. Area goes up by roughly a factor of ten, from three OR cells to eight full 8-bit equality comparators plus the selection logic, because the tool must now distinguish one-hot patterns from everything else. It is "safer" only in the narrow sense that the netlist now agrees with a modified RTL. It is not safer in the sense that matters: a non-one-hot input still produces 3'b000, which is indistinguishable from a legitimate D_0 request. The genuinely safer change is to add a one_hot_valid output, which costs a few gates and makes the error detectable.5. 面積大約漲十倍,從三個 OR 元件變成八個完整的 8 位元相等比較器加上選擇邏輯,因為工具現在必須把 one-hot 樣式與其他一切區分開來。它「比較安全」只在一個很窄的意義上成立:網表現在與一份改過的 RTL 一致。在真正重要的意義上它並不更安全:一個非 one-hot 的輸入依然產生 3'b000,而那與一個正當的 D_0 請求無法區分。真正更安全的改法,是加一個 one_hot_valid 輸出,只花幾個閘,卻讓錯誤變得可偵測。5. 面積はおよそ 10 倍になる。OR セル 3 個から、8 ビットの等価比較器 8 個と選択論理へ膨らむ。ツールがワンホットのパターンとそれ以外を区別しなければならなくなるからだ。「より安全」なのは、ネットリストが修正後の RTL と一致するという狭い意味においてだけである。肝心の意味では安全になっていない。ワンホットでない入力は依然として 3'b000 を生み、それは正当な D_0 の要求と区別がつかない。本当に安全にする変更は one_hot_valid 出力を足すことで、数ゲートの費用で誤りを検出可能にできる。
Problem: A design uses the slide's casex priority encoder. During simulation, Data[7] is driven by a register that has not yet been reset, so it reads 1'bx; the other seven bits are all 0.題目:某個設計使用投影片的 casex 優先權編碼器。模擬期間,Data[7] 由一個還沒被重置的暫存器驅動,所以它讀起來是 1'bx;其他七個位元全是 0。問題:ある設計がスライドの casex プライオリティエンコーダを使っている。シミュレーション中、Data[7] はまだリセットされていないレジスタに駆動されており 1'bx と読める。残る 7 ビットはすべて 0 である。
Code evaluate to in RTL simulation? What should it have evaluated to?在 RTL 模擬中 Code 求值為何?它本該求值為何?RTL シミュレーションで Code は何に評価されるか。本来は何に評価されるべきだったか。valid_data evaluate to?valid_data 求值為何?valid_data は何に評価されるか。casez and explain what changes in each of (1) and (2).用 casez 改寫那些分支,並說明 (1) 與 (2) 各自有什麼改變。枝を casez で書き直し、(1) と (2) それぞれで何が変わるかを説明せよ。casex and casez versions?casex 版與 casez 版的網表有差別嗎?casex 版と casez 版でネットリストは異なるか。1. casex treats an x in the selector as a wildcard that matches anything, so Data = 8'bx0000000 matches the very first arm 8'b1xxxxxxx and Code = 3'b111. It should have been 3'bxxx, which would have exposed the uninitialised register immediately. The casex hid the bug.1. casex 把選擇運算式裡的 x 當成匹配任何東西的萬用字元,所以 Data = 8'bx0000000 會匹配到第一個分支 8'b1xxxxxxx,於是 Code = 3'b111。它本該是 3'bxxx,那會立刻讓那個未初始化的暫存器現形。casex 把錯誤藏起來了。1. casex はセレクタ中の x を何にでも一致するワイルドカードとして扱うので、Data = 8'bx0000000 は最初の枝 8'b1xxxxxxx に一致し、Code = 3'b111 となる。本来は 3'bxxx であるべきで、そうなっていれば未初期化のレジスタがただちに露見した。casex がバグを隠したのである。
2. valid_data = |Data. The reduction OR of x with seven zeros is 1'bx, because the simulator cannot know whether any bit is set. So valid_data correctly goes unknown — the one signal that was not written with casex is the one that still tells the truth. That is a neat illustration of why the valid bit is worth having.2. valid_data = |Data。一個 x 與七個零做歸約 OR 的結果是 1'bx,因為模擬器無法知道是否有任何位元被設起來。所以 valid_data 正確地變成未知——那條沒有用 casex 寫的訊號,是唯一還在說實話的。這漂亮地說明了為什麼 valid 位元值得擁有。2. valid_data = |Data である。x とゼロ 7 個のリダクション OR は 1'bx になる。どのビットも立っていないかどうかをシミュレータは知りえないからだ。つまり valid_data は正しく不定になる。casex で書かれなかった唯一の信号が、いまだに真実を語っているわけである。valid ビットを持つ価値がよく分かる例である。
3. In silicon there is no x; the register powers up at some definite level. If it powers up low, Data is all zeros, the netlist's equations give Code = 3'b000 and valid_data = 0, and the consumer ignores Code. The system works. The failure here is purely one of verification: simulation told you everything was fine when it had no basis for saying so.3. 在矽片上沒有 x;那個暫存器會以某個確定的準位上電。如果它上電為低,Data 全是零,網表的方程式給出 Code = 3'b000 與 valid_data = 0,而消費端會忽略 Code。系統能動。這裡的失敗純粹是驗證上的失敗:模擬告訴你一切都好,但它根本沒有任何根據這麼說。3. シリコンに x はなく、レジスタは何らかの確定したレベルで立ち上がる。低レベルで立ち上がれば Data は全ゼロとなり、ネットリストの式は Code = 3'b000、valid_data = 0 を与え、受け手は Code を無視する。システムは動く。ここでの失敗は純粋に検証の失敗である。シミュレーションはそう言える根拠をまったく持たないまま、すべて問題ないと告げたのだ。
4. Replace each arm's x with ? and casex with casez: 8'b1??????? : Code = 7; and so on. Now an x in the selector matches no arm (only z would wildcard, and z does not occur here), so the default fires and Code = 3'bxxx — the correct, alarming answer. valid_data is unchanged at 1'bx.4. 把每個分支的 x 換成 ?,把 casex 換成 casez:8'b1??????? : Code = 7; 依此類推。現在選擇運算式裡的 x 匹配不到任何分支(只有 z 會做萬用匹配,而這裡不會出現 z),所以 default 觸發,Code = 3'bxxx——那個正確而且令人警醒的答案。valid_data 不變,仍是 1'bx。4. 各枝の x を ? に、casex を casez に置き換える。8'b1??????? : Code = 7; という具合である。こうするとセレクタ中の x はどの枝にも一致しない(ワイルドカードになるのは z だけで、ここに z は現れない)。したがって default が発火し、Code = 3'bxxx という正しく警戒を促す答えが得られる。valid_data は 1'bx のまま変わらない。
5. No. Synthesis reasons over 0 and 1 only, where casez with ? and casex with x describe the same set of patterns. You get identical gates and strictly better simulation — the definition of a free improvement.5. 沒有差別。合成只在 0 與 1 上推理,而在那裡,搭配 ? 的 casez 與搭配 x 的 casex 描述的是同一組樣式。你得到一模一樣的閘,以及嚴格更好的模擬——這就是「免費的改善」的定義。5. 異ならない。合成は 0 と 1 の上でしか推論せず、そこでは ? を使う casez と x を使う casex は同じパターンの集合を記述する。まったく同じゲートと、厳密により良いシミュレーションが得られる。無料の改善の定義そのものである。
The lecture defines a bus as a structure used to transport multi-bit data and a buffer as a structure used to store or drive data with or without control signals, then lists four implementation concerns: tri-state buffers, bidirectional buses, bus loading, and tri-state outputs with don't cares. The first three are this section; the fourth is Section 10.本講把匯流排定義為用來傳輸多位元資料的結構,把緩衝器定義為用來儲存或驅動資料(有或沒有控制訊號)的結構,接著列出四個實作上的考量:三態緩衝器、雙向匯流排、匯流排負載,以及帶不在意項的三態輸出。前三個是本節的內容;第四個是第 10 節。講義はバスを多ビットのデータを運ぶための構造、バッファを制御信号の有無にかかわらずデータを蓄えたり駆動したりするための構造と定義し、続いて実装上の論点を四つ挙げる。トライステートバッファ、双方向バス、バスの負荷、そしてドントケアを伴うトライステート出力である。最初の三つが本節の内容で、四つ目は第 10 節である。
A tri-state driver is the one place in combinational design where the value z is not merely a simulation artefact but a request for specific hardware. A normal CMOS output stage always drives: either the pull-up network or the pull-down network conducts. A tri-state output stage adds an enable that turns both off, leaving the output node floating at high impedance so that some other driver may take the wire. This is a real cell in every library, usually named with a BUFT, TBUF or TRI stem, and it has its own timing arcs: a data-to-output delay as usual, plus enable-to-output ("turn on") and output-to-Z ("turn off") arcs.三態驅動器是組合邏輯設計中唯一一個地方,z 這個值不只是模擬的產物,而是對特定硬體的要求。一個正常的 CMOS 輸出級永遠在驅動:不是上拉網路導通,就是下拉網路導通。三態輸出級加上一個致能,把兩者都關掉,讓輸出節點浮接在高阻抗狀態,好讓別的驅動器接管這條導線。這在每一座元件庫裡都是一個真實存在的元件,通常以 BUFT、TBUF 或 TRI 為字根命名,而且它有自己的時序弧:照例有一條資料到輸出的延遲,再加上致能到輸出(「打開」)與輸出到 Z(「關閉」)兩條弧。トライステートドライバは、組合せ回路設計において z という値が単なるシミュレーションの産物ではなく、特定のハードウェアへの要求になる唯一の場所である。通常の CMOS 出力段は常に駆動している。プルアップ網かプルダウン網のどちらかが導通する。トライステート出力段はイネーブルを加えて両方を切り、出力ノードを高インピーダンスで浮かせ、別のドライバがその配線を取れるようにする。これはどのライブラリにも実在するセルで、ふつう BUFT、TBUF、TRI を語幹として名づけられ、独自のタイミングアークを持つ。通常のデータから出力への遅延に加え、イネーブルから出力への「オン」と、出力から Z への「オフ」のアークである。
module stuff_to_bus1 (data_to_bus, bus_enabled, clk); input bus_enabled, clk; output [31:0] data_to_bus; reg [31:0] ckt_output_to_bus; assign data_to_bus = (bus_enabled) ? ckt_output_to_bus : 32'bz; // Description of the core circuit that drives ckt_output_to_bus goes here endmodule
The template is exact and worth memorising: a conditional expression, driving a net, whose "false" branch is a vector of z of the right width. The front end recognises this shape and instantiates one tri-state driver per bit — thirty-two of them here — all sharing the enable.這個範本很精確,值得背下來:一個條件運算式,驅動一條 net,而它的「假」分支是一個寬度正確的 z 向量。前端辨識這個形狀,並為每一個位元實體化一個三態驅動器——這裡是三十二個——全都共用那個致能。テンプレートは厳密で、暗記する価値がある。条件式がネットを駆動し、その「偽」の枝が正しい幅の z のベクタであること。フロントエンドはこの形を認識し、ビットごとにトライステートドライバを一つ、ここでは 32 個、すべて同じイネーブルを共有する形でインスタンス化する。
Three details decide whether you get the cell you wanted:有三個細節決定你拿不拿得到想要的那個元件:望んだセルが得られるかどうかを決める細部が三つある。
always block with a reg target and most tools will still infer the driver, but the continuous-assignment form is universally supported and unambiguous.目標必須是一條 net,而且由連續賦值驅動。你也可以在一個 always 區塊裡對一個 reg 目標寫同樣的條件式,大多數工具仍然會推論出那個驅動器,但連續賦值的形式是普遍被支援而且沒有歧義的。対象はネットでなければならず、連続代入で駆動すること。同じ条件式を always ブロック内で reg を対象に書いても、たいていのツールはドライバを推論する。しかし連続代入の形は普遍的にサポートされ、曖昧さがない。z branch must be the whole value, not part of one. 32'bz is thirty-two z bits. Writing 1'bz and relying on extension is a width bug waiting to happen; writing {16'bz, data[15:0]} asks for sixteen tri-state drivers and sixteen ordinary ones, which is legal but almost certainly not what you meant.z 分支必須是整個值,不能只是其中一部分。32'bz 是三十二個 z 位元。寫 1'bz 然後倚賴位元擴展,是一個等著發生的寬度錯誤;寫 {16'bz, data[15:0]} 則是要求十六個三態驅動器加十六個普通驅動器,合法,但幾乎可以確定不是你的本意。z の枝は値全体でなければならず、その一部であってはならない。32'bz は z が 32 ビットである。1'bz と書いて拡張に頼るのは、起きるのを待っている幅のバグである。{16'bz, data[15:0]} と書けば、トライステートドライバ 16 個とふつうのドライバ 16 個を要求することになる。合法だが、まず意図とは違う。z. A conditional with z on both sides is a permanently disabled driver; the tool will optimise the entire cone away and tell you about it.只能有恰好一個分支是 z。兩邊都是 z 的條件式是一個永遠停用的驅動器;工具會把整個邏輯錐最佳化掉,並且告訴你。z の枝はちょうど一つだけ。両辺が z の条件式は恒久的に無効なドライバであり、ツールはコーン全体を最適化で消し去り、そのことを報告する。Within a chip, tri-state buses have fallen out of favour and you should know why, since the lecture presents them neutrally. Internal tri-state nets have no defined value when no driver is enabled — the node floats, and a floating CMOS input dissipates crowbar current and may oscillate, so real designs need bus keepers (weak cross-coupled inverters) to hold the last value. They are hard to test, because ATPG must prove that exactly one driver is on for every pattern. They are hard to time, because the "turn off" arc of one driver and the "turn on" arc of the next must not overlap, which requires a non-overlap margin that eats into the cycle. And they are hard to debug, because a contention produces x in simulation and heat in silicon. The modern practice is to use tri-states only at chip I/O pads, where they are unavoidable, and to replace every internal bus with the multiplexer of Section 9.3.在晶片內部,三態匯流排已經不受青睞,而你應該知道為什麼,因為本講是中性地呈現它們的。內部三態網路在沒有任何驅動器被致能時沒有定義值——節點浮接,而一個浮接的 CMOS 輸入會消耗貫穿電流而且可能振盪,所以真實設計需要 bus keeper(弱交叉耦合反相器)來保住上一個值。它們難以測試,因為 ATPG 必須對每一個樣式證明恰好只有一個驅動器開著。它們難以做時序,因為前一個驅動器的「關閉」弧與下一個驅動器的「打開」弧不得重疊,那需要一段不重疊的餘裕,而那會吃掉週期。而且它們難以除錯,因為競爭在模擬中產生 x、在矽片上產生熱。現代的做法是只在晶片 I/O 焊墊上用三態(那裡避不掉),並把每一條內部匯流排換成第 9.3 節的多工器。チップ内部では、トライステートバスはすでに好まれない。講義は中立に紹介しているので、理由は知っておくべきである。内部のトライステートネットは、どのドライバも有効でないとき値が定義されない。ノードは浮き、浮いた CMOS 入力は貫通電流を消費し発振しうるので、実際の設計は直前の値を保持するバスキーパ(弱い交差結合インバータ)を必要とする。テストも難しい。ATPG はすべてのパターンについて、ちょうど一つのドライバだけがオンであることを証明せねばならない。タイミングも難しい。あるドライバの「オフ」のアークと次のドライバの「オン」のアークは重なってはならず、そのための非重複マージンがサイクルを食う。デバッグも難しい。競合はシミュレーションでは x を、シリコンでは熱を生む。現代の実務は、避けようのないチップ I/O パッドでのみトライステートを使い、内部バスはすべて第 9.3 節のマルチプレクサに置き換えることである。
inout port9.2 雙向匯流排與 inout 埠9.2 双方向バスと inout ポートmodule stuff_to_bus2 (data_to_from_bus, clk, send_data, rcv_data); input clk, send_data, rcv_data; inout [31:0] data_to_from_bus; reg [31:0] reg_to_bus; wire [31:0] data_to_from_bus, inbound_data; assign inbound_data = (rcv_data) ? data_to_from_bus : 32'bz; assign data_to_from_bus = (send_data) ? reg_to_bus : 32'bz; // Behaviour using inbound_data goes here endmodule
An inout port is the Verilog model of a bidirectional pad. It is always a net, never a reg — note the explicit wire [31:0] data_to_from_bus; in the declaration list, which is redundant but harmless and makes the point. The module contains two drivers that share the pad: an outbound one gated by send_data, and an inbound path gated by rcv_data.inout 埠是雙向焊墊在 Verilog 裡的模型。它永遠是一條 net,絕不是 reg——注意宣告清單裡那句明寫的 wire [31:0] data_to_from_bus;,它是多餘的但無害,而且把重點講清楚了。這個模組裡有兩個共用這個焊墊的驅動器:一個由 send_data 閘控的對外驅動器,與一條由 rcv_data 閘控的對內路徑。inout ポートは双方向パッドの Verilog 上のモデルである。常にネットであり、決して reg ではない。宣言リストにある明示的な wire [31:0] data_to_from_bus; に注意したい。冗長だが無害で、要点をはっきりさせている。このモジュールにはパッドを共有するドライバが二つある。send_data でゲートされた外向きのものと、rcv_data でゲートされた内向きの経路である。
The inbound assignment deserves a second look, because at first sight it is odd: why gate the input with a tri-state rather than just reading the pad? The reason is modelling discipline. Making inbound_data go to z when rcv_data is low means that any logic which consumes it while the receiver is disabled will see z rather than stale bus traffic, and z propagating into arithmetic produces x, which shows up loudly in simulation. It is a deliberate "fail noisily" choice. In synthesis the inbound gate usually optimises away to a plain wire or a simple AND, because nothing downstream can use high impedance as an input.對內那個賦值值得再看一眼,因為乍看很奇怪:為什麼要用三態去閘控輸入,而不是直接讀那個焊墊?理由是建模紀律。讓 inbound_data 在 rcv_data 為低時變成 z,意味著任何在接收器停用期間消費它的邏輯會看到 z 而不是陳舊的匯流排資料,而 z 傳進算術運算會產生 x,那在模擬中會很大聲地現形。這是刻意的「吵著失敗」選擇。在合成中,對內的那個閘通常會被最佳化成一條單純的 wire 或一個簡單的 AND,因為下游沒有東西能把高阻抗當成輸入來用。内向きの代入はもう一度見る価値がある。一見すると奇妙だからだ。なぜパッドをそのまま読まず、入力をトライステートでゲートするのか。理由はモデリングの規律である。rcv_data が低のとき inbound_data を z にしておけば、受信器が無効な間にそれを消費する論理は、古いバスの内容ではなく z を見る。そして z が算術に伝播すれば x になり、シミュレーションで大きな音を立てて現れる。これは意図的な「派手に失敗する」選択である。合成では、内向きのゲートはたいてい単なる配線か単純な AND に最適化される。下流に高インピーダンスを入力として使えるものがないからだ。
The structural risk of inout is contention, and the Verilog gives you no protection against it. If send_data is high while a device at the other end of the board is also driving, the two drivers fight: in simulation the net resolver returns x on every bit where they disagree and the agreeing bits keep their value, while in silicon you have a direct path from supply to ground through two output stages, which is a current spike and, sustained, a reliability failure. The discipline is to make the enable conditions provably exclusive at the protocol level, and — in a real project — to add an assertion that fires if more than one driver is ever enabled.inout 的結構性風險是競爭,而 Verilog 完全不保護你。如果 send_data 為高,而板子另一端的某個裝置同時也在驅動,兩個驅動器就會打架:在模擬中,網路仲裁器會對每一個它們不一致的位元回傳 x,一致的位元則保住原值;而在矽片上,你會得到一條從電源直通地、穿過兩個輸出級的路徑,那是一道電流尖峰,若持續下去就是可靠度失效。紀律是:在協定層級讓致能條件可證明地互斥,而且——在真實專案裡——加上一條斷言,只要有超過一個驅動器被致能就觸發。inout の構造的リスクは競合であり、Verilog は何も守ってくれない。send_data が高のときにボードの反対側のデバイスも駆動していれば、二つのドライバは衝突する。シミュレーションではネットの解決器が、両者が食い違うビットごとに x を返し、一致するビットは値を保つ。シリコンでは電源から接地まで二つの出力段を貫く経路ができ、電流スパイクとなり、持続すれば信頼性の故障になる。規律は、イネーブル条件をプロトコルのレベルで証明可能に排他的にすること、そして実プロジェクトでは、二つ以上のドライバが有効になったら発火するアサーションを加えることである。
module stuff_to_bus3 (data_to_from_bus, enab_a, enab_b, clk, rcv_data);
input enab_a, enab_b, rcv_data, clk;
inout [31:0] data_to_from_bus;
reg [31:0] reg_a_to_bus, reg_b_to_bus;
assign data_to_from_bus = (enab_a) ? reg_a_to_bus :
(enab_b) ? reg_b_to_bus : 32'bz;
endmodule // not showing other logic
This is the single most useful idea in the section and the slide passes over it in one line. Compare it with the obvious alternative, two separate continuous assignments to the same net:這是本節最有用的單一想法,而投影片用一行就帶過去了。拿它跟顯而易見的替代方案比較:對同一條 net 做兩個分開的連續賦值:これは本節で最も有用な単一の着想だが、スライドは一行で通り過ぎる。明白な代替案、すなわち同じネットへの二つの別個の連続代入と比べてみよう。
assign data_to_from_bus =
enab_a ? reg_a_to_bus : 32'bz;
assign data_to_from_bus =
enab_b ? reg_b_to_bus : 32'bz;
// 64 tri-state cells, 2 loads on the net,
// and nothing stops both enables going highassign data_to_from_bus =
(enab_a) ? reg_a_to_bus :
(enab_b) ? reg_b_to_bus : 32'bz;
// 32 mux cells + 32 tri-state cells,
// 1 load, contention impossible by constructionThe nested form synthesises to a 2:1 multiplexer selected by enab_a, followed by a single tri-state driver whose enable is enab_a | enab_b — exactly the structure in the slide's figure, which shows a Mux block with a sel input tied to enab_a and an OR gate feeding the driver's control. Four consequences follow, and all of them are improvements:巢狀形式合成出一個由 enab_a 選擇的 2:1 多工器,後面接單一一個三態驅動器,其致能是 enab_a | enab_b——正是投影片圖中的結構,那張圖畫了一個 sel 輸入接到 enab_a 的 Mux 區塊,以及一個 OR 閘餵進驅動器的控制端。接著有四個後果,而且全都是改善:入れ子の形は、enab_a で選択される 2:1 マルチプレクサと、その後ろに置かれた単一のトライステートドライバ(イネーブルは enab_a | enab_b)に合成される。スライドの図の構造そのもので、そこには sel 入力を enab_a につないだ Mux ブロックと、ドライバの制御端子へ入る OR ゲートが描かれている。帰結が四つあり、どれも改善である。
enab_a priority; if both are high, source A wins and source B is simply not selected. There is no combination of enables that produces two active drivers.競爭在結構上不可能發生。巢狀條件式給 enab_a 優先權;如果兩者都為高,來源 A 勝,來源 B 單純就是沒被選中。沒有任何致能組合能產生兩個作用中的驅動器。競合が構造的に起こりえない。入れ子の条件式は enab_a に優先権を与える。両方が高ならソース A が勝ち、ソース B は単に選ばれない。二つのドライバが同時に有効になるイネーブルの組合せは存在しない。Generalise this and you have the reason internal tri-state buses have disappeared from modern design: a multiplexer tree does everything a bus does, with defined values at all times, no floating nodes, no contention, full testability, and conventional timing arcs. The only place you genuinely need tri-states is at the chip boundary, where the wire is shared with devices you do not control.把這件事推廣,你就得到內部三態匯流排從現代設計中消失的理由:一棵多工器樹能做到匯流排能做的一切,而且在任何時刻都有定義值、沒有浮接節點、沒有競爭、完全可測試,而且有常規的時序弧。真正需要三態的地方只有晶片邊界,那裡的導線是與你控制不到的裝置共用的。これを一般化すれば、内部のトライステートバスが現代の設計から消えた理由になる。マルチプレクサのツリーはバスにできることをすべてこなし、しかも常に値が定義され、浮くノードがなく、競合がなく、完全にテスト可能で、ふつうのタイミングアークを持つ。本当にトライステートが要るのはチップの境界だけで、そこでは配線を自分の制御外のデバイスと共有しているからである。
Problem: A designer replaces stuff_to_bus3's nested conditional with two separate assignments:題目:某位設計者把 stuff_to_bus3 的巢狀條件式換成兩個分開的賦值:問題:ある設計者が stuff_to_bus3 の入れ子の条件式を、二つの別個の代入に置き換えた。
assign data_to_from_bus = enab_a ? reg_a_to_bus : 32'bz; assign data_to_from_bus = enab_b ? reg_b_to_bus : 32'bz;
Take reg_a_to_bus = 32'h0000_00FF, reg_b_to_bus = 32'h0000_0F0F.取 reg_a_to_bus = 32'h0000_00FF、reg_b_to_bus = 32'h0000_0F0F。reg_a_to_bus = 32'h0000_00FF、reg_b_to_bus = 32'h0000_0F0F とする。
enab_a = 1, enab_b = 0, what is on the bus?當 enab_a = 1、enab_b = 0 時,匯流排上是什麼?enab_a = 1、enab_b = 0 のとき、バス上には何があるか。enab_a = enab_b = 1, what does the simulator show for the low byte, bit by bit?當 enab_a = enab_b = 1 時,模擬器對低位元組逐位元顯示什麼?enab_a = enab_b = 1 のとき、シミュレータは下位バイトをビットごとにどう表示するか。enab_a = enab_b = 0, what is on the bus, and what happens to a CMOS gate downstream of it in real silicon?當 enab_a = enab_b = 0 時,匯流排上是什麼,而在真實矽片上它下游的一個 CMOS 閘會怎麼樣?enab_a = enab_b = 0 のとき、バス上には何があるか。実際のシリコンで、その下流の CMOS ゲートには何が起きるか。1. Driver A drives, driver B is at z. Verilog's net resolution gives precedence to any driven value over z, so the bus reads 32'h0000_00FF.1. 驅動器 A 在驅動,驅動器 B 在 z。Verilog 的網路仲裁讓任何被驅動的值優先於 z,所以匯流排讀起來是 32'h0000_00FF。1. ドライバ A が駆動し、ドライバ B は z である。Verilog のネット解決は駆動された値を z より優先するので、バスは 32'h0000_00FF と読める。
2. Both drive. Resolution is bitwise: bits where the two agree keep that value; bits where they disagree become x. Low byte: A gives 1111_1111, B gives 0000_1111. Bits 0–3 agree at 1 and stay 1; bits 4–7 disagree and go unknown. Result 8'bxxxx_1111; the upper 24 bits are zero in both and stay zero. In silicon those four bits are shorts between two output stages.2. 兩者都在驅動。仲裁是逐位元的:兩者一致的位元保住那個值;不一致的位元變成 x。低位元組:A 給 1111_1111,B 給 0000_1111。位元 0–3 一致為 1 而保持 1;位元 4–7 不一致而變成未知。結果是 8'bxxxx_1111;高 24 位元在兩者都是零,維持零。在矽片上,那四個位元是兩個輸出級之間的短路。2. 両方が駆動する。解決はビット単位で、一致するビットはその値を保ち、食い違うビットは x になる。下位バイトは、A が 1111_1111、B が 0000_1111 を与える。ビット 0〜3 は 1 で一致して 1 のまま、ビット 4〜7 は食い違って不定になる。結果は 8'bxxxx_1111。上位 24 ビットは両者ともゼロでゼロのままである。シリコンではその 4 ビットが二つの出力段どうしの短絡になる。
3. Both at z, so the whole bus is 32'hzzzz_zzzz. A downstream CMOS input at an intermediate voltage turns on both its pull-up and pull-down networks, drawing static crowbar current, heating, and possibly oscillating. This is why floating internal nets need a bus keeper, and why a design that can ever leave a bus undriven is defective even if simulation looks tidy.3. 兩者都在 z,所以整條匯流排是 32'hzzzz_zzzz。下游一個處在中間電壓的 CMOS 輸入會同時打開它的上拉與下拉網路,汲取靜態貫穿電流、發熱,可能還會振盪。這就是為什麼浮接的內部 net 需要 bus keeper,也是為什麼一個有可能讓匯流排沒人驅動的設計是有缺陷的,即使模擬看起來很整齊。3. 両方が z なので、バス全体が 32'hzzzz_zzzz になる。下流で中間電位にある CMOS 入力は、プルアップ網とプルダウン網を同時に導通させ、静的な貫通電流を引き、発熱し、場合によっては発振する。浮いた内部ネットにバスキーパが要る理由であり、バスが無駆動になりうる設計がシミュレーション上は整然と見えても欠陥である理由でもある。
4. Nested version: (1) 32'h0000_00FF, identical. (2) enab_a wins, so 32'h0000_00FF — no x, no contention, a defined answer. (3) still 32'hzzzz_zzzz, because nothing is enabled; the mux fixes contention but not floating, and avoiding the float is the protocol's job.4. 巢狀版本:(1) 32'h0000_00FF,相同。(2) enab_a 勝,所以是 32'h0000_00FF——沒有 x、沒有競爭、一個有定義的答案。(3) 仍然是 32'hzzzz_zzzz,因為沒有任何東西被致能;多工器修掉的是競爭,不是浮接,而避免浮接是協定的工作。4. 入れ子の版:(1) 32'h0000_00FF で同じ。(2) enab_a が勝つので 32'h0000_00FF。x も競合もない、定義された答えである。(3) やはり 32'hzzzz_zzzz。何も有効でないからだ。マルチプレクサが直すのは競合であって浮きではなく、浮きを避けるのはプロトコルの仕事である。
5. Three practical mechanisms. Add a checker process that flags any cycle in which more than one enable is asserted — a one-line reduction such as counting the set bits of the enable vector and asserting it is at most one. Scan the bus for x with a $monitor or a dedicated checker, since contention always produces x on at least one bit when the data differ. And at sign-off, run x-propagation-aware gate simulation, which is what turns a bus with an undefined value into a visible failure rather than a lucky pass.5. 三種實用機制。加一個檢查用的行程,標示出任何有一個以上致能被宣告的週期——一行歸約就夠了,例如數一數致能向量裡被設起來的位元數,並斷言它最多是一。用 $monitor 或專門的檢查器掃描匯流排上的 x,因為當資料不同時,競爭一定會在至少一個位元上產生 x。而在簽核時跑一次 x 傳播感知的閘級模擬,那正是把一條值未定義的匯流排從僥倖通過變成可見失敗的做法。5. 実用的な仕組みが三つある。イネーブルが二つ以上アサートされたサイクルを旗立てするチェッカプロセスを足すこと。イネーブルベクタの立っているビット数を数え、それが高々 1 であるとアサートする一行のリダクションで足りる。$monitor か専用のチェッカでバス上の x を監視すること。データが異なれば競合は必ず少なくとも 1 ビットに x を生むからだ。そしてサインオフでは x 伝播を考慮したゲートシミュレーションを走らせること。値の定まらないバスを、まぐれの合格ではなく目に見える失敗に変えてくれる。
alu_with_z1 versus alu_with_z210.1 投影片的實驗:alu_with_z1 對上 alu_with_z210.1 スライドの実験:alu_with_z1 対 alu_with_z2The lecture ends its bus material with a controlled experiment, and it is the most quantitatively instructive page in the whole deck. Two modules are identical except for one literal in the default arm.本講以一個受控實驗結束匯流排的材料,那也是整份投影片中量化上最具教育意義的一頁。兩個模組除了 default 分支裡的一個字面值以外完全相同。講義はバスの話題を制御された実験で締めくくる。これはスライド全体で最も定量的に有益な一ページである。二つのモジュールは default の枝のリテラル一つを除いて完全に同一である。
module alu_with_z1 (alu_out, data_a, data_b, enable, opcode); input [2:0] opcode; input [3:0] data_a, data_b; input enable; output alu_out; // scalar for illustration reg [3:0] alu_reg; assign alu_out = (enable == 1) ? alu_reg : 4'bz; always @ (opcode or data_a or data_b) case (opcode) 3'b001: alu_reg = data_a | data_b; 3'b010: alu_reg = data_a ^ data_b; 3'b110: alu_reg = ~data_b; default: alu_reg = 4'b0; // alu_with_z2 writes 4'bx here instead endcase endmodule
Both versions are latch-free: the case is complete in both. Both have a tri-state output, because the conditional's false branch is 4'bz. The only difference is what the five unlisted opcodes produce.兩個版本都沒有鎖存器:兩者的 case 都是完整的。兩者都有三態輸出,因為條件式的假分支是 4'bz。唯一的差別是那五個沒列出的 opcode 會產生什麼。どちらの版もラッチを持たない。case はどちらも完全である。どちらもトライステート出力を持つ。条件式の偽の枝が 4'bz だからだ。唯一の違いは、列挙されていない五つの opcode が何を生むかである。
In alu_with_z1 they produce zero. That is a requirement, and the optimiser must meet it. It needs logic that detects "opcode is not one of 001, 010, 110" and forces the output to zero, which means the opcode decode must be built in full and an extra selection stage added. The slide's figure (a) shows the result: an inverter, three NAND/NOR cells computing the data functions, two multiplexers, a NAND and an inverter driving alu_out.在 alu_with_z1 裡它們產生零。那是一個要求,最佳化器必須達成。它需要能偵測「opcode 不是 001、010、110 其中之一」並把輸出強制為零的邏輯,也就是說 opcode 解碼必須完整建出來,再加上一級額外的選擇。投影片的圖 (a) 顯示的結果是:一個反相器、三個算出資料函數的 NAND/NOR 元件、兩個多工器,以及一個 NAND 加一個反相器驅動 alu_out。alu_with_z1 ではそれらがゼロを出す。これは要求であり、最適化器は満たさねばならない。「opcode が 001、010、110 のいずれでもない」ことを検出して出力をゼロに強制する論理が要る。つまり opcode のデコードを完全に作り、さらに選択段を一つ追加することになる。スライドの図 (a) が示す結果は、インバータ 1 個、データ関数を計算する NAND/NOR セル 3 個、二つのマルチプレクサ、そして alu_out を駆動する NAND 1 個とインバータ 1 個である。
In alu_with_z2 they produce x. That is a permission. The optimiser may output whatever is cheapest for those five opcodes, and it exploits this to merge them into whichever listed branch costs least to extend. The slide's figure (b) shows the result: one AND, one inverter, one NOR, one multiplexer, and the output inverter — captioned "with don't cares". Perhaps 40 % less logic for the price of one character in the source.在 alu_with_z2 裡它們產生 x。那是一個許可。最佳化器可以對那五個 opcode 輸出任何最便宜的東西,而它利用這一點,把它們併進最便宜擴張的那個已列分支裡。投影片的圖 (b) 顯示的結果是:一個 AND、一個反相器、一個 NOR、一個多工器,以及輸出的反相器——圖說寫著「with don't cares」。用原始碼裡一個字元的代價,換來大概少 40 % 的邏輯。alu_with_z2 ではそれらが x を出す。これは許可である。最適化器はその五つの opcode に対し最も安いものを何でも出してよく、その自由を使って、拡張するのが最も安く済む既出の枝へそれらを併合する。スライドの図 (b) が示す結果は、AND 1 個、インバータ 1 個、NOR 1 個、一つのマルチプレクサ、そして出力のインバータである。キャプションには「with don't cares」とある。ソースのたった一文字の代価で、論理がおよそ 40 % 減る。
alu_with_z1 | alu_with_z2 | |
|---|---|---|
default valuedefault 的值default の値 | 4'b0 | 4'bx |
| Latch inferred?有推論出鎖存器嗎?ラッチは推論されるか | No否いいえ | No否いいえ |
| Multiplexers in the netlist網表中的多工器數ネットリスト中のマルチプレクサ数 | Two兩個二つ | One一個一つ |
| Behaviour on a reserved opcode遇到保留 opcode 時的行為予約 opcode に対する振る舞い | Outputs 0, predictably, in RTL and gates在 RTL 與閘級都可預測地輸出 0RTL でもゲートでも予測どおり 0 を出す | RTL outputs x; gates output some arbitrary but definite valueRTL 輸出 x;閘級輸出某個任意但確定的值RTL は x を出し、ゲートは任意だが確定した値を出す |
| Safe when reserved opcodes can occur?當保留 opcode 可能發生時安全嗎?予約 opcode が起こりうるとき安全か | Yes是安全 | No否安全でない |
The mechanism is worth understanding because it generalises far beyond this example. A Boolean function with $k$ don't-care minterms is not one function but a family of $2^k$ functions, all of which satisfy the specification. The optimiser is free to pick any member of the family, and it picks the one with the lowest cost. Every don't-care you declare doubles the search space and can only ever reduce the cost, never increase it. That is why the same trick produced the ten-to-one area reduction in the plain 8:3 encoder of Section 8.2: declaring 248 of 256 input patterns as don't-cares left the optimiser with so much freedom that the equality comparators dissolved into three OR gates.這個機制值得理解,因為它的適用範圍遠遠超出這個例子。一個有 $k$ 個不在意最小項的布林函數不是一個函數,而是一族 $2^k$ 個函數,它們全都滿足規格。最佳化器可以自由挑這一族裡的任何一個,而它挑成本最低的那個。你每宣告一個不在意項,搜尋空間就加倍一次,而且只可能降低成本,絕不可能提高。這就是為什麼同樣的手法在第 8.2 節一般 8:3 編碼器上造成了十比一的面積縮減:把 256 個輸入樣式中的 248 個宣告成不在意項,留給最佳化器的自由度大到那些相等比較器直接溶解成三個 OR 閘。この機構は理解する価値がある。この例をはるかに超えて一般化するからだ。$k$ 個のドントケア最小項を持つブール関数は一つの関数ではなく、仕様を満たす $2^k$ 個の関数からなる族である。最適化器はその族の任意の一つを自由に選べ、最も費用の低いものを選ぶ。ドントケアを一つ宣言するたびに探索空間は倍になり、費用は下がることはあっても上がることは決してない。第 8.2 節の素の 8:3 エンコーダで同じ手口が 10 対 1 の面積削減をもたらした理由がこれである。256 個の入力パターンのうち 248 個をドントケアと宣言したことで、最適化器に与えられた自由度が大きすぎて、等価比較器が OR ゲート三つに溶けてしまったのだ。
The risk is equally mechanical. You have told the tool "I promise these inputs never occur." If the promise is false, two things go wrong at once. The hardware produces a value you did not choose and cannot predict from the source; and your RTL simulation produces x, so the two disagree — which means the mismatch will not be caught by the gate-level regression of Section 1.2 unless your stimulus happens to drive the forbidden input.風險同樣是機械式的。你已經告訴工具「我保證這些輸入永遠不會出現」。如果這個保證是假的,有兩件事會同時出錯。硬體產生一個不是你挑的、也無法從原始碼預測的值;而你的 RTL 模擬產生 x,所以兩邊不一致——這意味著除非你的激勵剛好驅動那個被禁止的輸入,否則第 1.2 節的閘級回歸測試抓不到這個不匹配。リスクも同じく機械的である。あなたはツールに「これらの入力は決して現れないと約束する」と告げた。その約束が偽なら、二つのことが同時に狂う。ハードウェアは、自分が選んだのでもソースから予測できるのでもない値を出す。そして RTL シミュレーションは x を出すので両者は食い違う。つまり、刺激がたまたまその禁じた入力を与えない限り、第 1.2 節のゲートレベル・リグレッションはこの不一致を捕まえない。
'bx in a default, and when not to什麼時候該在 default 裡用 'bx,什麼時候不該default に 'bx を使うべきとき、使うべきでないときUse it when the uncovered inputs are structurally impossible — an unreachable FSM state encoding, a one-hot vector produced by your own decoder, a selector whose illegal values are blocked upstream. Pair it with an assertion that fires in simulation if the impossible input ever appears, so that a broken promise becomes visible rather than silent.該用,當那些沒涵蓋的輸入在結構上不可能發生時——一個到不了的 FSM 狀態編碼、一個由你自己的解碼器產生的 one-hot 向量、一條非法值在上游就被擋住的選擇線。請把它與一條斷言配成一對,一旦那個不可能的輸入真的出現就在模擬中觸發,好讓一個被打破的承諾變成看得見的,而不是無聲的。使うべきなのは、未カバーの入力が構造的に不可能なときである。到達しない FSM の状態符号、自分のデコーダが生むワンホットのベクタ、不正値が上流で遮断されるセレクタなど。あわせて、その不可能な入力が万一現れたらシミュレーションで発火するアサーションを対にしておくこと。破られた約束を無言ではなく目に見えるものにするためである。
Do not use it for anything reachable from a chip input, from software, or from another clock domain. "The driver will never send that opcode" is a statement about software, and software changes. The cost of a concrete default is a handful of gates; the cost of a wrong don't-care is a silicon respin.不該用在任何從晶片輸入、從軟體或從另一個時脈域到得了的地方。「驅動程式永遠不會送那個 opcode」是一句關於軟體的陳述,而軟體會改。一個具體 default 的代價是幾個閘;一個錯誤的不在意項的代價是重新流片。使うべきでないのは、チップ入力・ソフトウェア・別のクロックドメインから到達しうるものすべてである。「ドライバはその opcode を決して送らない」はソフトウェアについての言明であり、ソフトウェアは変わる。具体的なデフォルトの代価はゲート数個、誤ったドントケアの代価はシリコンの作り直しである。
Never use it as a way of silencing a lint warning you have not understood. A default: y = 'bx; added reflexively to remove a latch message is a correct fix only if the branch really is unreachable; otherwise you have swapped a visible defect for an invisible one.絕對不要把它當成讓一個你還沒搞懂的 lint 警告閉嘴的手段。為了移除一則鎖存器訊息而反射性地加上 default: y = 'bx;,只有在那個分支真的不可到達時才是正確的修法;否則你只是把一個看得見的缺陷換成一個看不見的。決して、理解していない lint 警告を黙らせる手段として使ってはならない。ラッチの警告を消すために反射的に default: y = 'bx; を足すのは、その枝が本当に到達不能な場合にのみ正しい修正である。そうでなければ、目に見える欠陥を見えない欠陥に取り替えただけである。
One further subtlety that the slides do not mention. Assigning 'bx and asserting the full_case pragma (Section 6.5) produce the same netlist, because both tell the optimiser the same thing. They differ entirely in what the simulator sees. With 'bx the simulator also produces x, so the RTL warns you loudly at the first forbidden input. With full_case the simulator holds the previous value and says nothing. Given identical hardware and strictly better observability, default: y = 'bx; dominates full_case in every respect — which is the whole argument for the pragma's obsolescence.還有一個投影片沒提到的微妙之處。賦值 'bx 與宣告 full_case pragma(第 6.5 節)會產生相同的網表,因為兩者告訴最佳化器的是同一件事。它們完全不同的地方在於模擬器看到什麼。用 'bx,模擬器同樣產生 x,所以在第一個被禁止的輸入出現時 RTL 就大聲警告你。用 full_case,模擬器保住前一個值而且什麼也不說。既然硬體一模一樣而可觀測性嚴格更好,default: y = 'bx; 在每一方面都壓過 full_case——這就是那個 pragma 該被淘汰的全部論據。スライドが触れていない微妙な点をもう一つ。'bx の代入と full_case プラグマの宣言(第 6.5 節)は同じネットリストを生む。最適化器に同じことを伝えるからだ。まったく違うのはシミュレータに何が見えるかである。'bx ならシミュレータも x を出すので、最初の禁じられた入力で RTL が大声で警告する。full_case ならシミュレータは前の値を保持し、何も言わない。ハードウェアが同一で可観測性は厳密に良いのだから、default: y = 'bx; はあらゆる点で full_case を上回る。これがプラグマが廃れるべき論拠のすべてである。
Problem: A 4-to-1 selector takes a 4-bit one-hot sel and four 8-bit inputs.題目:一個 4 對 1 的選擇器接受一個 4 位元 one-hot 的 sel 與四個 8 位元輸入。問題:4 対 1 のセレクタが 4 ビットのワンホット sel と 8 ビットの入力四つを受け取る。
always @* case (sel) 4'b0001: y = w; 4'b0010: y = x; 4'b0100: y = v; 4'b1000: y = u; default: y = ???; endcase
default: y = 8'b0;, describe the logic the tool must build, and estimate its size.在 default: y = 8'b0; 之下,描述工具必須建出的邏輯,並估計它的大小。default: y = 8'b0; の場合、ツールが作らねばならない論理を述べ、その規模を見積もれ。default: y = 8'bx;, describe it again. What single Boolean form does each output bit take?在 default: y = 8'bx; 之下,再描述一次。每個輸出位元取的是哪一個布林形式?default: y = 8'bx; の場合をもう一度述べよ。各出力ビットはどの単一のブール形をとるか。sel comes from an off-chip register that software can write. Which default do you choose, and what do you add?sel 來自一個軟體可寫的晶片外暫存器。你會選哪個 default,又會加上什麼?sel はソフトウェアが書き込めるチップ外のレジスタから来る。どちらの default を選び、何を足すか。sel were a 2-bit binary selector with all four values listed, would the choice of default matter at all?如果 sel 改成一個四個值都列出來的 2 位元二進位選擇線,default 的選擇還有任何差別嗎?もし sel が四つの値をすべて列挙した 2 ビットの二進セレクタだったら、default の選択に何か違いはあるか。1. The tool must distinguish the four legal one-hot patterns from the other twelve and force zero on all twelve. That requires a full 4-bit equality comparison against each of four constants — four 4-input AND/NOR groups — plus an eight-bit-wide five-way selection. Call it roughly 16 GE of decode plus 8 bits × about 7 GE of selection, so on the order of 70 GE.1. 工具必須把四個合法的 one-hot 樣式與另外十二個區分開來,並對那十二個強制為零。那需要對四個常數各做一次完整的 4 位元相等比較——四組 4 輸入 AND/NOR——再加上一個八位元寬的五路選擇。算起來大約是 16 GE 的解碼,加上 8 位元 × 約 7 GE 的選擇,所以是 70 GE 這個數量級。1. ツールは四つの合法なワンホットパターンを残る 12 個と区別し、その 12 個でゼロを強制しなければならない。四つの定数それぞれに対する完全な 4 ビット等価比較、すなわち 4 入力 AND/NOR 群が四つ、加えて 8 ビット幅の 5 分岐の選択が要る。デコードがおよそ 16 GE、選択が 8 ビット × 約 7 GE で、合計 70 GE のオーダーである。
2. The twelve illegal patterns are don't-cares, so the optimiser may assume sel is one-hot. Under that assumption each output bit is simply the one-hot AND-OR $y_i = s_0w_i + s_1x_i + s_2v_i + s_3u_i$ — no comparators at all, because each select bit alone identifies its source. Eight copies of a four-way AND-OR, about 6 GE each.2. 那十二個非法樣式是不在意項,所以最佳化器可以假設 sel 是 one-hot。在那個假設下,每個輸出位元就只是 one-hot 的 AND-OR:$y_i = s_0w_i + s_1x_i + s_2v_i + s_3u_i$——完全不需要比較器,因為每一個選擇位元單獨就足以標示它的來源。八份四路 AND-OR 的副本,每份約 6 GE。2. 不正な 12 パターンはドントケアなので、最適化器は sel がワンホットだと仮定してよい。その仮定の下では各出力ビットは単なるワンホットの AND-OR、$y_i = s_0w_i + s_1x_i + s_2v_i + s_3u_i$ になる。比較器はまったく要らない。選択ビット 1 本だけでその供給元が特定できるからである。4 分岐 AND-OR のコピーが八つ、各約 6 GE。
3. Roughly 70 GE against roughly 48 GE, so about 1.5:1 — and the gap widens with the number of sources, because the comparator cost grows while the one-hot cost does not. For a 16-way one-hot selector the ratio is several times larger.3. 大約 70 GE 對上大約 48 GE,所以約 1.5:1——而且來源數越多差距越大,因為比較器的成本會成長,one-hot 的成本不會。對一個 16 路的 one-hot 選擇器,這個比值會大上好幾倍。3. 約 70 GE 対約 48 GE で、およそ 1.5 対 1 である。しかもソース数が増えるほど差は開く。比較器のコストは増えるが、ワンホットのコストは増えないからだ。16 分岐のワンホットセレクタなら比は数倍になる。
4. Software-writable means the promise cannot be kept, so take default: y = 8'b0; and pay the gates. Then add a hardware check that sel is one-hot — for a 4-bit vector, sel != 0 && (sel & (sel-1)) == 0 — and either flag an error register or force a safe value. An illegal write becomes a reported fault instead of an undefined output.4. 軟體可寫意味著那個保證守不住,所以選 default: y = 8'b0; 並付出那些閘。接著加一個檢查 sel 是否為 one-hot 的硬體——對一個 4 位元向量就是 sel != 0 && (sel & (sel-1)) == 0——然後不是把錯誤記進一個錯誤暫存器,就是強制一個安全值。一次非法的寫入就變成一個被回報的故障,而不是一個未定義的輸出。4. ソフトウェアが書けるということは約束を守れないということなので、default: y = 8'b0; を取ってゲートを払う。そのうえで sel がワンホットかを調べるハードウェアを足す。4 ビットのベクタなら sel != 0 && (sel & (sel-1)) == 0 である。そしてエラーレジスタに記録するか、安全な値を強制する。不正な書き込みは未定義の出力ではなく、報告される故障になる。
5. No. With a 2-bit selector and all four values listed, the default is unreachable, so there are no don't-care minterms to exploit and nothing for the optimiser to do differently. Write default: y = 8'bx; anyway, purely as insurance against someone widening the selector later.5. 沒有差別。在一個 2 位元選擇線、四個值全列出的情況下,default 不可到達,所以沒有可利用的不在意最小項,最佳化器也沒有任何可以做得不一樣的事。還是把 default: y = 8'bx; 寫上去,純粹當作日後有人把選擇線加寬的保險。5. 違いはない。2 ビットのセレクタで四つの値をすべて列挙していれば default は到達不能なので、利用できるドントケア最小項はなく、最適化器にできる別のこともない。それでも default: y = 8'bx; は書いておくこと。後日誰かがセレクタを広げたときの保険としてである。
The last slide of the lecture carries an instruction in italics: before RTL coding, do the resource sharing first — it can make the hardware own the low-cost property. The wording is awkward but the advice is sound and it is worth taking seriously, because resource sharing is the one optimisation in this lecture that you must perform yourself. Latch avoidance is a coding rule; minimisation is the tool's job; mux-versus-priority follows automatically from your conditions. Operator sharing, by contrast, depends on an architectural judgement that the tool can make only in narrow circumstances.本講最後一張投影片帶著一句斜體的指示:before RTL coding, do the resource sharing first — it can make the hardware own the low-cost property。措辭有點彆扭,但建議是對的,值得認真看待,因為資源共用是本講唯一一個你必須親自動手的最佳化。避免鎖存器是一條寫碼規則;最小化是工具的工作;多工器對優先權鏈會從你的條件自動跟著走。相對地,運算子共用取決於一個架構判斷,而工具只在很窄的情況下才做得出這個判斷。講義の最後のスライドにはイタリックの指示が添えられている。before RTL coding, do the resource sharing first — it can make the hardware own the low-cost property。言い回しはぎこちないが助言は妥当で、真面目に受け取る価値がある。リソース共有は本講義で唯一、自分の手で行わねばならない最適化だからだ。ラッチの回避はコーディング規則であり、最小化はツールの仕事であり、マルチプレクサか優先順位かは条件から自動的に決まる。これに対し演算子の共有はアーキテクチャ上の判断に依存し、ツールがその判断を下せるのは限られた状況だけである。
module res_share (y_out, sel, data_a, data_b, accum); input [3:0] data_a, data_b, accum; input sel; output [4:0] y_out; // Before: two adders and a 5-bit output mux // assign y_out = sel ? (data_a + accum) : (data_a + data_b); // After: one 4-bit input mux and one adder assign y_out = data_a + (sel ? accum : data_b); endmodule
The transformation is nothing more than factoring applied to arithmetic rather than to Boolean algebra. Both branches add data_a; only the second operand differs. Pull the common operand out of the conditional and the choice moves to the input side, where it selects between two 4-bit values instead of between two 5-bit sums. The slide's note is blunt and correct: only 1 adder, instead of 2 adders.這個轉換不過就是把因式分解用在算術上,而不是用在布林代數上。兩個分支都加 data_a;只有第二個運算元不同。把共同的運算元從條件式裡提出來,選擇就移到輸入端,在那裡它是在兩個 4 位元值之間選,而不是在兩個 5 位元的和之間選。投影片的註記直白而正確:only 1 adder, instead of 2 adders。この変換は、因数分解をブール代数ではなく算術に適用しただけのものである。どちらの枝も data_a を加えており、違うのは第二のオペランドだけである。共通のオペランドを条件式から外へ出せば、選択は入力側へ移り、5 ビットの和二つの間ではなく 4 ビットの値二つの間で選ぶことになる。スライドの注記はそっけなく、そして正しい。only 1 adder, instead of 2 adders。
Hand-waving about "saving an adder" is not enough; you need to be able to decide a sharing question with numbers. Use these per-bit figures, which are representative of a mature standard-cell library at any node:光是揮手說「省下一個加法器」不夠;你得有能力用數字去決定一個共用問題。請用以下這些每位元的數據,它們足以代表任何製程節點上一座成熟的標準元件庫:「加算器が一つ減る」と手を振るだけでは足りない。共有の是非を数値で決められなければならない。以下のビットあたりの数値を使うとよい。どのプロセスノードでも成熟した標準セルライブラリを代表する値である。
| Element元件要素 | Area per bit每位元面積1 ビットあたりの面積 | Delay contribution延遲貢獻遅延への寄与 |
|---|---|---|
| 2:1 multiplexer2:1 多工器2:1 マルチプレクサ | 2.3 GE2.3 GE2.3 GE | 1 level1 層1 段 |
| Ripple-carry adder (full adder)漣波進位加法器(全加器)リップルキャリー加算器(全加算器) | 6 GE6 GE6 GE | $n$ levels (carry chain)$n$ 層(進位鏈)$n$ 段(キャリーチェーン) |
| Carry-lookahead / carry-select adder超前進位/進位選擇加法器キャリールックアヘッド/キャリーセレクト加算器 | 9–12 GE9–12 GE9〜12 GE | $O(\log n)$ levels$O(\log n)$ 層$O(\log n)$ 段 |
| Incrementer (add a constant 1)遞增器(加上常數 1)インクリメンタ(定数 1 を加える) | 2 GE2 GE2 GE | $n$ levels, but cheap cells$n$ 層,但元件很便宜$n$ 段、ただしセルは安い |
| Equality comparator (XNOR + AND tree)相等比較器(XNOR 加 AND 樹)等価比較器(XNOR + AND ツリー) | 2.5 GE, output 1 bit2.5 GE,輸出 1 位元2.5 GE、出力は 1 ビット | $O(\log n)$ levels$O(\log n)$ 層$O(\log n)$ 段 |
| Array multiplier陣列乘法器アレイ乗算器 | $\approx 6n$ GE per bit, i.e. $O(n^2)$ total每位元約 $6n$ GE,亦即總共 $O(n^2)$1 ビットあたり約 $6n$ GE、すなわち総計 $O(n^2)$ | $O(n)$ levels$O(n)$ 層$O(n)$ 段 |
sel only has to beat the output mux — the adder and the select resolution happen in parallel. Shared, the mux is in series before the adder, so the path is mux delay plus full carry propagation, and nothing can start until sel is stable. If sel is produced late — by a comparison, by a decode of a slow opcode — the shared version is one mux delay plus the whole adder delay worse on that path.延遲。未共用時,加法器一拿到運算元就開始算,而 sel 只需要趕上輸出的多工器——加法與選擇的解析是平行發生的。共用之後,多工器串在加法器前面,所以路徑是多工器延遲加上整條進位傳播,而且在 sel 穩定之前什麼都不能開始。如果 sel 來得晚——由一次比較產生、或由一個很慢的 opcode 解碼產生——那麼在那條路徑上,共用版本會差上一個多工器延遲加上整個加法器延遲。遅延。共有しない場合、加算器はオペランドが届き次第計算を始め、sel は出力マルチプレクサに間に合えばよい。加算と選択の確定は並行して進む。共有すると、マルチプレクサは加算器の手前に直列に入るので、経路はマルチプレクサの遅延にキャリー伝搬の全体が加わり、しかも sel が安定するまで何も始まらない。sel が遅く来る場合——比較で作られる、遅い opcode のデコードで作られるなど——共有版はその経路でマルチプレクサ 1 段分と加算器全体の遅延だけ不利になる。That last point generalises into the only honest summary of the trade-off: sharing moves the multiplexer from after the operator to before it. Everything else follows. Area improves whenever the operator is bigger than the extra muxing. Delay improves if the select signal arrives early and worsens if it arrives late, because a mux in front of an operator serialises with it while a mux behind it does not.最後這一點可以推廣成對這個取捨唯一誠實的總結:共用是把多工器從運算子之後搬到運算子之前。其餘一切都從這裡跟著走。只要運算子比多出來的多工代價大,面積就改善。延遲則在選擇訊號來得早時改善、來得晚時變差,因為放在運算子前面的多工器會與它串接,放在後面的則不會。この最後の点は、このトレードオフの唯一正直な要約へと一般化できる。共有とはマルチプレクサを演算子の後ろから前へ移すことである。他はすべてそこから従う。演算子が追加のマルチプレクサより大きければ面積は改善する。遅延は、選択信号が早く届けば改善し、遅く届けば悪化する。演算子の前に置かれたマルチプレクサはそれと直列になるが、後ろに置かれたものはならないからだ。
where $C_{\text{op}}$ is the area of one operator instance, $m \approx 2.3$ GE is the per-bit multiplexer cost, $W_{\text{in}}$ is the total width of the operands that must now be multiplexed, and $W_{\text{out}}$ is the width of the result that no longer needs multiplexing.其中 $C_{\text{op}}$ 是一個運算子實體的面積,$m \approx 2.3$ GE 是每位元的多工器成本,$W_{\text{in}}$ 是現在必須被多工的運算元總寬度,而 $W_{\text{out}}$ 是不再需要被多工的結果寬度。ここで $C_{\text{op}}$ は演算子インスタンス 1 個の面積、$m \approx 2.3$ GE はビットあたりのマルチプレクサのコスト、$W_{\text{in}}$ は新たに多重化しなければならないオペランドの総幅、$W_{\text{out}}$ はもう多重化せずに済む結果の幅である。
Read the inequality and two regimes fall out. If the operator is width-preserving — an adder, a subtractor, a shifter, an ALU, where $W_{\text{out}} \approx W_{\text{in}}$ or where one operand is shared so $W_{\text{in}}$ is small — the right-hand side is near zero or negative and sharing essentially always wins on area. If the operator is compressing — an equality comparator, a reduction, a priority encoder, anything that turns many bits into few — then $W_{\text{in}} \gg W_{\text{out}}$, the right-hand side is large, and sharing can easily cost more than it saves. That asymmetry is the single most useful thing to carry away from this section, and Practice 14 makes it concrete.讀這個不等式,兩種情況就掉出來。如果運算子是保寬的——加法器、減法器、移位器、ALU,那些 $W_{\text{out}} \approx W_{\text{in}}$ 的,或是有一個運算元被共用因而 $W_{\text{in}}$ 很小的——那麼右邊接近零或為負,共用在面積上基本上永遠贏。如果運算子是壓縮型的——相等比較器、歸約、優先權編碼器,任何把很多位元變成很少位元的東西——那麼 $W_{\text{in}} \gg W_{\text{out}}$,右邊很大,共用很容易賠得比省得多。那個不對稱是本節最值得帶走的一件事,而練習 14 會把它具體化。この不等式を読むと二つの領域が現れる。演算子が幅を保つもの——加算器、減算器、シフタ、ALU のように $W_{\text{out}} \approx W_{\text{in}}$ であるもの、あるいは片方のオペランドが共通で $W_{\text{in}}$ が小さいもの——なら右辺はほぼゼロか負になり、面積の面では共有がほぼ常に勝つ。演算子が圧縮型——等価比較器、リダクション、プライオリティエンコーダなど、多くのビットを少数のビットに変えるもの——なら $W_{\text{in}} \gg W_{\text{out}}$ となって右辺は大きく、共有は節約以上に損をしやすい。この非対称性こそ本節から持ち帰るべき最も有用なことであり、演習 14 がそれを具体化する。
Four situations where the italic advice on the slide should be ignored.有四種情況,投影片上那句斜體建議應該被忽略。スライドのイタリックの助言を無視すべき状況が四つある。
When the operations are not mutually exclusive. This is the hard precondition and it is easy to forget. Sharing works because only one of the two results is needed at a time; the multiplexer encodes that choice. If your design needs a+b and c+d in the same cycle, there is nothing to share, and writing the expression as though there were simply produces a wrong answer. In a multi-cycle design you can create the exclusivity deliberately — schedule the two additions into different cycles and share the adder across them — but that is a datapath-plus-controller restructuring, not an expression rewrite, and it belongs to Lecture 8's design-example material.當那些運算不互斥時。這是硬性前提,而且很容易忘記。共用之所以成立,是因為一次只需要兩個結果中的一個;多工器編碼的就是那個選擇。如果你的設計在同一個週期裡同時需要 a+b 與 c+d,那就沒有東西可以共用,而把運算式寫成好像可以共用的樣子,只會產生錯誤答案。在多週期設計裡你可以刻意製造出互斥性——把兩個加法排程到不同週期,並讓它們共用一個加法器——但那是資料路徑加控制器的重構,不是運算式改寫,而且它屬於第 8 講的設計範例材料。演算が互いに排他的でないとき。これは厳格な前提条件であり、忘れやすい。共有が成り立つのは、一度に必要なのが二つの結果のうち一方だけだからである。マルチプレクサはその選択を符号化している。同じサイクルで a+b と c+d の両方が必要な設計なら共有できるものはなく、できるかのように式を書けば誤った答えを生むだけである。マルチサイクルの設計なら排他性を意図的に作り出せる。二つの加算を別々のサイクルにスケジュールし、加算器を跨いで共有すればよい。だがそれはデータパスと制御器の再構成であって式の書き換えではなく、第 8 講の設計例の題材に属する。
When the select arrives late. As the costing above shows, sharing puts the mux on the critical path ahead of the operator. If sel is the output of a comparator that is itself slow, the shared version can lose several hundred picoseconds, which on a tight clock is the whole margin. The usual fix is to compute the select one cycle early and register it, which restores the sharing win.當選擇訊號來得晚時。如上面的成本計算所示,共用把多工器放到運算子前面的關鍵路徑上。如果 sel 是某個本身就很慢的比較器的輸出,共用版本可能損失好幾百皮秒,而在一個緊的時脈下那就是全部的餘裕。常見的修法是提早一個週期算出選擇訊號並把它暫存起來,這樣就把共用的好處救回來了。選択信号が遅く届くとき。上のコスト計算が示したとおり、共有はマルチプレクサを演算子の手前のクリティカルパス上に置く。sel がそれ自体遅い比較器の出力なら、共有版は数百ピコ秒を失いかねず、厳しいクロックではそれが全マージンである。よくある対処は、選択信号を 1 サイクル早く計算してレジスタに入れることで、こうすれば共有の利点が戻る。
When one branch would have optimised to something much cheaper. Consider y = sel ? (a + b) : (a + 1). Unshared, the tool builds one full adder and one incrementer, and an incrementer is roughly a third of the cost of an adder because one operand is a constant and most of the carry logic folds away. Share it as y = a + (sel ? b : 1) and the constant is gone — the second operand is now a variable, so you must build a full adder. You saved an incrementer and paid for a mux plus the upgrade from incrementer to adder. Constant folding is a bigger lever than sharing, and sharing can destroy it.當其中一個分支本來會被最佳化成便宜得多的東西時。考慮 y = sel ? (a + b) : (a + 1)。不共用時,工具建出一個完整的加法器與一個遞增器,而遞增器大約只有加法器三分之一的成本,因為它有一個運算元是常數,大部分進位邏輯會摺疊掉。把它共用成 y = a + (sel ? b : 1),常數就消失了——第二個運算元現在是變數,所以你必須建一個完整的加法器。你省下一個遞增器,卻付了一個多工器外加「從遞增器升級成加法器」的差價。常數摺疊是比共用更大的槓桿,而共用可能把它毀掉。片方の枝が本来ずっと安いものに最適化されたはずのとき。y = sel ? (a + b) : (a + 1) を考えよう。共有しなければ、ツールは完全な加算器一つとインクリメンタ一つを作る。インクリメンタは片方のオペランドが定数でキャリー論理の大半が畳み込まれるため、加算器のおよそ三分の一の費用で済む。これを y = a + (sel ? b : 1) として共有すると定数が消える。第二オペランドが変数になるので完全な加算器を作らねばならない。インクリメンタ一つを節約した代わりに、マルチプレクサ一つと、インクリメンタから加算器への格上げ分を払ったことになる。定数畳み込みは共有より大きな梃子であり、共有はそれを壊しうる。
When it costs power rather than saving it. Two dedicated adders, each fed by operands that change only when their branch is active, toggle half as often as one shared adder whose inputs change on every select flip. Shared logic concentrates switching activity. Where leakage dominates, sharing wins; where dynamic power dominates and the operands are wide, two quiet units can beat one busy one. Modern flows mitigate this with operand isolation — gating the unused operator's inputs so it stops toggling — which, interestingly, is sharing's opposite: it keeps two units and makes them quiet.當它花掉功耗而不是省下功耗時。兩個專用的加法器,各自由只在自己分支作用時才變動的運算元餵入,其切換次數只有一個共用加法器的一半,因為後者的輸入在每次選擇線翻轉時都會變。共用的邏輯會把切換活動集中起來。在漏電主導的場合共用贏;在動態功耗主導而運算元又很寬的場合,兩個安靜的單元可能勝過一個忙碌的單元。現代流程用運算元隔離(operand isolation)來緩解這件事——把沒在用的運算子輸入閘控住,讓它停止切換——有趣的是,那正是共用的反面:它保留兩個單元,並讓它們安靜。電力を節約するどころか消費するとき。専用の加算器を二つ置き、それぞれ自分の枝が有効なときだけ変化するオペランドを与えれば、トグル回数は共有加算器の半分で済む。共有加算器の入力は選択が反転するたびに変わるからだ。共有された論理はスイッチング活性を一点に集中させる。リークが支配的なら共有が勝ち、動的電力が支配的でオペランドが幅広なら、静かな二つのユニットが忙しい一つのユニットに勝ちうる。現代のフローはオペランド分離でこれを緩和する。使っていない演算子の入力をゲートしてトグルを止めるのだ。興味深いことに、これは共有の正反対である。ユニットを二つ保ったまま、静かにさせるのだから。
Modern synthesisers do perform automatic resource sharing, within limits, and knowing those limits tells you where your own effort is needed.現代合成器確實會在一定限度內執行自動資源共用,而知道那些限度在哪裡,就知道你自己的力氣該花在哪裡。現代の合成器は一定の範囲で自動的なリソース共有を行う。その範囲を知れば、自分の労力をどこに割くべきかが分かる。
They will share arithmetic operators that appear in different arms of the same conditional expression or case statement, because mutual exclusivity is then syntactically evident. Design Compiler's datapath extraction will rewrite sel ? (a+b) : (a+c) into the shared form without being asked. They will also merge operators across an if/else inside one always block, for the same reason.它們會共用出現在同一個條件運算式或 case 敘述不同分支裡的算術運算子,因為此時互斥性在語法上就看得出來。Design Compiler 的資料路徑抽取會自動把 sel ? (a+b) : (a+c) 改寫成共用形式,不用你開口。基於同樣的理由,它們也會跨越單一 always 區塊內的 if/else 合併運算子。同一の条件式や case 文の別々の枝に現れる算術演算子は共有してくれる。その場合、排他性が構文上すぐ分かるからだ。Design Compiler のデータパス抽出は、頼まなくても sel ? (a+b) : (a+c) を共有形に書き換える。同じ理由で、一つの always ブロック内の if/else をまたいだ演算子の併合も行う。
They will not share across module boundaries unless you enable cross-boundary optimisation, and even then only tentatively. They will not share across clock cycles — that requires a controller and a state register, which is an architectural decision no synthesiser will make. They will not share when the exclusivity is a property of your protocol rather than of your code: if two adders are in different always blocks and you happen to know that only one is ever used per cycle, the tool cannot know it and will build both.它們不會跨模組邊界共用,除非你啟用跨邊界最佳化,而且即使啟用了也只會很保守地做。它們不會跨時脈週期共用——那需要一個控制器與一個狀態暫存器,那是任何合成器都不會做的架構決策。當互斥性是你協定的性質而不是你程式碼的性質時,它們也不會共用:如果兩個加法器在不同的 always 區塊裡,而你剛好知道每個週期只會用到其中一個,工具無從得知,它會把兩個都建出來。モジュール境界を越えた共有はしてくれない。境界越えの最適化を有効にした場合でも、やるのは控えめである。クロックサイクルをまたいだ共有もしない。それには制御器と状態レジスタが要り、どの合成器も下さないアーキテクチャ上の判断だからである。排他性がコードの性質ではなくプロトコルの性質である場合も共有しない。二つの加算器が別々の always ブロックにあり、各サイクルで使われるのは一方だけだとあなたが知っていても、ツールには知る術がなく、両方を作る。
Write the shared form yourself when the operator is large (a multiplier, a wide adder, a divider) and you can see the exclusivity. Leave small operators alone — the tool handles them and your version is less readable. And never share across cycles by hand-rolling a mux without also building the controller that guarantees the two uses never collide; that is the one sharing bug that produces wrong answers rather than merely a bigger chip.當運算子很大(乘法器、寬加法器、除法器)而且你看得出互斥性時,自己把共用形式寫出來。小的運算子就別碰——工具會處理,而你的版本可讀性更差。還有,絕對不要靠手工湊一個多工器來跨週期共用,卻不同時建出保證兩次使用永遠不會撞在一起的控制器;那是唯一一種會產生錯誤答案、而不只是讓晶片變大的共用錯誤。演算子が大きく(乗算器、幅広の加算器、除算器)、排他性が自分で見てとれるときは、共有形を自分で書くこと。小さな演算子は放っておくこと。ツールが面倒を見てくれるし、自作のほうが読みにくい。そして、二つの使用が決して衝突しないことを保証する制御器も作らずに、手でマルチプレクサをこしらえてサイクルをまたいだ共有をしては決していけない。それだけが、チップを大きくするのではなく誤った答えを生む種類の共有のバグである。
Problem: Rewrite each so that only one instance of the expensive operator is built, or explain why you should not.題目:把以下每一個改寫成只建出一份昂貴運算子的實體,或者解釋為什麼不該這麼做。問題:次の各式を、高価な演算子のインスタンスが一つだけになるよう書き直せ。あるいは、そうすべきでない理由を説明せよ。
assign y = sel ? (a * b) : (a * c); with 16-bit operands.assign y = sel ? (a * b) : (a * c);,運算元 16 位元。assign y = sel ? (a * b) : (a * c);(オペランドは 16 ビット)。assign y = sel ? (a + b) : (c + d); with 8-bit operands.assign y = sel ? (a + b) : (c + d);,運算元 8 位元。assign y = sel ? (a + b) : (c + d);(オペランドは 8 ビット)。assign y = sel ? (a + b) : (a - b); with 12-bit operands.assign y = sel ? (a + b) : (a - b);,運算元 12 位元。assign y = sel ? (a + b) : (a - b);(オペランドは 12 ビット)。assign y = sel ? (a + 8'd1) : (a + b); with 8-bit operands.assign y = sel ? (a + 8'd1) : (a + b);,運算元 8 位元。assign y = sel ? (a + 8'd1) : (a + b);(オペランドは 8 ビット)。addr + stride in state RUN and base + offset in state LOAD, in different cycles, in two separate always blocks.一個 FSM 在狀態 RUN 時算 addr + stride,在狀態 LOAD 時算 base + offset,發生在不同週期,寫在兩個分開的 always 區塊裡。ある FSM が状態 RUN で addr + stride を、状態 LOAD で base + offset を、別々のサイクルで、二つの別個の always ブロックで計算している。1. assign y = a * (sel ? b : c); — one 16-bit multiplier instead of two. A 16×16 array multiplier is on the order of 1500 GE; a 16-bit mux is 37 GE. This is the textbook case: an enormous win, and the tool will usually find it, but write it anyway because it also halves elaboration time and makes the intent obvious.1. assign y = a * (sel ? b : c);——一個 16 位元乘法器而不是兩個。一個 16×16 的陣列乘法器大約是 1500 GE 的量級;一個 16 位元多工器是 37 GE。這是教科書案例:贏得非常誇張,而工具通常也找得到,但還是自己寫出來,因為這樣也讓展開時間減半,而且讓意圖一目瞭然。1. assign y = a * (sel ? b : c);。16 ビット乗算器が二つから一つになる。16×16 のアレイ乗算器は 1500 GE のオーダー、16 ビットのマルチプレクサは 37 GE である。教科書的な事例で、利得は莫大であり、ツールもたいてい見つける。それでも自分で書くべきである。エラボレーション時間も半減し、意図も一目で分かるからだ。
2. assign y = (sel ? a : c) + (sel ? b : d); — one adder, two muxes. Cost check with the break-even formula: $C_{\text{op}} = 8 \times 6 = 48$ GE; $W_{\text{in}} = 16$ (both operands muxed), $W_{\text{out}} = 9$; $m(W_{\text{in}} - W_{\text{out}}) = 2.3 \times 7 \approx 16$ GE. Since $48 > 16$, share — saving about 32 GE. The two muxes are in parallel, so the delay penalty is one mux level, not two.2. assign y = (sel ? a : c) + (sel ? b : d);——一個加法器,兩個多工器。用損益平衡公式檢查成本:$C_{\text{op}} = 8 \times 6 = 48$ GE;$W_{\text{in}} = 16$(兩個運算元都要多工),$W_{\text{out}} = 9$;$m(W_{\text{in}} - W_{\text{out}}) = 2.3 \times 7 \approx 16$ GE。既然 $48 > 16$,就共用——省下大約 32 GE。那兩個多工器是平行的,所以延遲代價是一層多工器,不是兩層。2. assign y = (sel ? a : c) + (sel ? b : d);。加算器 1 個、マルチプレクサ 2 個である。損益分岐の式で確認しよう。$C_{\text{op}} = 8 \times 6 = 48$ GE、$W_{\text{in}} = 16$(両方のオペランドを多重化)、$W_{\text{out}} = 9$、$m(W_{\text{in}} - W_{\text{out}}) = 2.3 \times 7 \approx 16$ GE。$48 > 16$ なので共有する。約 32 GE の節約になる。二つのマルチプレクサは並列なので、遅延の代償は 2 段ではなく 1 段である。
3. Do not mux the operands; share the adder by turning it into an adder/subtractor. Write assign y = a + (sel ? b : ~b) + (sel ? 1'b0 : 1'b1);, or more idiomatically let the tool do it from sel ? a+b : a-b — datapath extraction recognises this pattern and builds one carry-propagate adder with an XOR row on the b input and the carry-in tied to the subtract control. Cost: one adder (72 GE) plus twelve XORs (24 GE) instead of two adders (about 165 GE for an adder plus a subtractor). The lesson is that "share" sometimes means "merge into one configurable unit", not "mux the inputs".3. 不要去多工運算元;把加法器共用成一個加法/減法器。寫成 assign y = a + (sel ? b : ~b) + (sel ? 1'b0 : 1'b1);,或者更慣用的做法是直接寫 sel ? a+b : a-b 讓工具去做——資料路徑抽取認得這個樣式,會建出一個進位傳播加法器,在 b 輸入上加一列 XOR,並把進位輸入接到減法控制訊號。成本:一個加法器(72 GE)加十二個 XOR(24 GE),相對於兩個加法器(一個加法器加一個減法器大約 165 GE)。教訓是「共用」有時候的意思是「併成一個可組態的單元」,而不是「把輸入多工起來」。3. オペランドを多重化してはいけない。加算器を加減算器にまとめて共有する。assign y = a + (sel ? b : ~b) + (sel ? 1'b0 : 1'b1); と書くか、より慣用的には sel ? a+b : a-b と書いてツールに任せる。データパス抽出はこのパターンを認識し、b 入力に XOR の列を付け、キャリー入力を減算制御につないだキャリー伝搬加算器を一つ作る。コストは加算器 1 個(72 GE)と XOR 12 個(24 GE)、対して加算器と減算器を別々に持てば約 165 GE である。教訓は、「共有」とは「入力を多重化する」ことではなく「一つの設定可能なユニットに併合する」ことを意味する場合があるということだ。
4. Do not share. Unshared, the first branch is an incrementer at about $8 \times 2 = 16$ GE and the second a full adder at 48 GE, plus a 9-bit output mux at 21 GE — about 85 GE. Shared as a + (sel ? 8'd1 : b), the constant is lost: one 8-bit mux at 18 GE plus a full adder at 48 GE — about 66 GE. Here sharing still wins slightly on area, but notice how much of the expected saving evaporated: the incrementer you deleted was cheap, and you paid for its replacement. Make the operand widths 4 bits and the two numbers converge; make one branch a + 0 and sharing becomes an outright loss. Always check whether constant folding was doing the work for you.4. 不要共用。不共用時,第一個分支是一個遞增器,約 $8 \times 2 = 16$ GE,第二個是一個完整加法器 48 GE,再加上一個 9 位元輸出多工器 21 GE——大約 85 GE。共用成 a + (sel ? 8'd1 : b) 之後,常數沒了:一個 8 位元多工器 18 GE 加一個完整加法器 48 GE——大約 66 GE。這裡共用在面積上還是小勝,但請注意預期的節省蒸發了多少:你刪掉的那個遞增器本來就很便宜,而你又為它的替代品付了錢。把運算元寬度改成 4 位元,兩個數字就收斂;把其中一個分支改成 a + 0,共用就變成徹底的虧損。永遠要檢查是不是常數摺疊在替你幹活。4. 共有してはいけない。共有しない場合、第一の枝はインクリメンタで約 $8 \times 2 = 16$ GE、第二の枝は完全な加算器で 48 GE、加えて 9 ビットの出力マルチプレクサが 21 GE、合わせて約 85 GE である。a + (sel ? 8'd1 : b) として共有すると定数が失われ、8 ビットのマルチプレクサ 18 GE と完全な加算器 48 GE で約 66 GE になる。面積ではまだわずかに共有が勝つが、期待された節約がどれだけ蒸発したかに注目したい。消したインクリメンタはもともと安く、その置き換え分を払ったのだ。オペランド幅を 4 ビットにすれば二つの数値は近づき、片方の枝を a + 0 にすれば共有は完全な損になる。定数畳み込みが代わりに働いていたのではないか、常に確かめること。
5. No syntactic sharing is possible — different blocks, no visible exclusivity — and the tool will build two adders. If you want one, you must restructure: a single always block computing sum = op1 + op2, with op1 and op2 multiplexed by the state, and the controller guaranteeing the two uses land in different cycles. That is a datapath-and-controller refactor, exactly the pattern Lecture 8 develops, and it is the only form of sharing that saves a large unit across time.5. 語法上無從共用——不同區塊、看不見互斥性——工具會建兩個加法器。如果你要一個,就必須重構:一個單一的 always 區塊算 sum = op1 + op2,而 op1 與 op2 由狀態多工選出,再由控制器保證兩次使用落在不同週期。那是一次資料路徑加控制器的重構,正是第 8 講發展的樣式,也是唯一一種能跨時間省下一個大型單元的共用形式。5. 構文上は共有できない。別々のブロックで、排他性も見えないので、ツールは加算器を二つ作る。一つにしたいなら再構成するしかない。sum = op1 + op2 を計算する単一の always ブロックを置き、op1 と op2 を状態で多重化し、制御器が二つの使用を別サイクルに落とすことを保証する。これはデータパスと制御器の再構成であり、第 8 講が展開するパターンそのもので、大きなユニットを時間をまたいで節約できる唯一の共有の形である。
Problem: Use $m = 2.3$ GE per multiplexed bit, 6 GE per adder bit, and 2.5 GE per bit for an equality comparator (whose output is one bit).題目:使用每個被多工的位元 $m = 2.3$ GE、加法器每位元 6 GE,以及相等比較器每位元 2.5 GE(其輸出為一位元)。問題:多重化するビットあたり $m = 2.3$ GE、加算器はビットあたり 6 GE、等価比較器はビットあたり 2.5 GE(出力は 1 ビット)として計算せよ。
y = sel ? (a == b) : (c == d); with 32-bit operands, unshared and shared. Which wins, and by how much?計算 y = sel ? (a == b) : (c == d); 在 32 位元運算元下,未共用與共用的成本。哪個贏,贏多少?32 ビットのオペランドで y = sel ? (a == b) : (c == d); の共有なし・共有ありのコストを求めよ。どちらがどれだけ勝つか。y = sel ? (a + b) : (c + d); at 32 bits. Which wins?現在計算 y = sel ? (a + b) : (c + d); 在 32 位元下的成本。哪個贏?次に 32 ビットで y = sel ? (a + b) : (c + d); のコストを求めよ。どちらが勝つか。1. Unshared: two 32-bit comparators at $32 \times 2.5 = 80$ GE each = 160 GE, plus a 1-bit output mux at 2.3 GE → 162.3 GE. Shared: two 32-bit operand muxes at $32 \times 2.3 = 73.6$ GE each = 147.2 GE, plus one comparator at 80 GE → 227.2 GE. Unshared wins by about 65 GE, which is 40 % worse for the "optimised" version.1. 未共用:兩個 32 位元比較器,各 $32 \times 2.5 = 80$ GE,共 160 GE,加上一個 1 位元輸出多工器 2.3 GE → 162.3 GE。共用:兩個 32 位元運算元多工器,各 $32 \times 2.3 = 73.6$ GE,共 147.2 GE,加上一個比較器 80 GE → 227.2 GE。未共用贏約 65 GE,也就是那個「最佳化過」的版本反而差了 40 %。1. 共有なし:32 ビットの比較器が二つ、各 $32 \times 2.5 = 80$ GE で 160 GE、加えて 1 ビットの出力マルチプレクサ 2.3 GE → 162.3 GE。共有あり:32 ビットのオペランドマルチプレクサが二つ、各 $32 \times 2.3 = 73.6$ GE で 147.2 GE、加えて比較器 1 個 80 GE → 227.2 GE。共有なしが約 65 GE 勝つ。つまり「最適化した」版のほうが 40 % 悪い。
2. $C_{\text{op}} = 80$, $W_{\text{in}} = 64$ (both operands must be muxed), $W_{\text{out}} = 1$. The condition is $80 > 2.3(64-1) = 145$, which is false. The comparator compresses 64 bits into 1, so sharing moves the multiplexer from the cheap side of the operator to the expensive side. You are muxing 64 bits to avoid muxing one.2. $C_{\text{op}} = 80$、$W_{\text{in}} = 64$(兩個運算元都必須多工)、$W_{\text{out}} = 1$。條件是 $80 > 2.3(64-1) = 145$,這是假的。比較器把 64 個位元壓縮成 1 個,所以共用把多工器從運算子便宜的那一側搬到了昂貴的那一側。你為了避免多工一個位元,而去多工六十四個位元。2. $C_{\text{op}} = 80$、$W_{\text{in}} = 64$(両方のオペランドを多重化しなければならない)、$W_{\text{out}} = 1$ である。条件は $80 > 2.3(64-1) = 145$ となり、偽である。比較器は 64 ビットを 1 ビットに圧縮するので、共有はマルチプレクサを演算子の安い側から高い側へ移すことになる。1 ビットを多重化せずに済ませるために、64 ビットを多重化しているのだ。
3. Let $n$ be the operand width. Unshared $= 2(2.5n) + 2.3 = 5n + 2.3$. Shared $= 2(2.3n) + 2.5n = 7.1n$. Equal when $7.1n = 5n + 2.3$, i.e. $n \approx 1.1$. So for any operand wider than about one bit, sharing a comparator loses — the break-even is essentially immediate, which is why you should never do it.3. 令 $n$ 為運算元寬度。未共用 $= 2(2.5n) + 2.3 = 5n + 2.3$。共用 $= 2(2.3n) + 2.5n = 7.1n$。相等時 $7.1n = 5n + 2.3$,也就是 $n \approx 1.1$。所以只要運算元寬於大約一位元,共用比較器就是輸——損益平衡點幾乎是立刻到達,這就是為什麼你永遠不該這麼做。3. オペランド幅を $n$ とする。共有なしは $2(2.5n) + 2.3 = 5n + 2.3$、共有ありは $2(2.3n) + 2.5n = 7.1n$ である。等しくなるのは $7.1n = 5n + 2.3$、すなわち $n \approx 1.1$ のときである。つまりオペランドが約 1 ビットより広ければ比較器の共有は負ける。損益分岐点は事実上すぐそこにあり、だからこれは決してやるべきでない。
4. Unshared $= 2(32 \times 6) + 33 \times 2.3 = 384 + 75.9 = 459.9$ GE. Shared $= 2(32 \times 2.3) + 32 \times 6 = 147.2 + 192 = 339.2$ GE. Sharing wins by about 121 GE, 26 %. Check against the inequality: $C_{\text{op}} = 192$, $m(W_{\text{in}} - W_{\text{out}}) = 2.3(64 - 33) \approx 71$, and $192 > 71$, so share.4. 未共用 $= 2(32 \times 6) + 33 \times 2.3 = 384 + 75.9 = 459.9$ GE。共用 $= 2(32 \times 2.3) + 32 \times 6 = 147.2 + 192 = 339.2$ GE。共用贏約 121 GE,也就是 26 %。用不等式檢查:$C_{\text{op}} = 192$,$m(W_{\text{in}} - W_{\text{out}}) = 2.3(64 - 33) \approx 71$,而 $192 > 71$,所以共用。4. 共有なしは $2(32 \times 6) + 33 \times 2.3 = 384 + 75.9 = 459.9$ GE。共有ありは $2(32 \times 2.3) + 32 \times 6 = 147.2 + 192 = 339.2$ GE。共有が約 121 GE、26 % 勝つ。不等式で確認すると $C_{\text{op}} = 192$、$m(W_{\text{in}} - W_{\text{out}}) = 2.3(64 - 33) \approx 71$ で、$192 > 71$ なので共有する。
5. Share operators that preserve width; never share operators that compress it. Sharing relocates the multiplexer from the operator's output to its inputs, so it pays exactly when the inputs are not much wider than the output.5. 共用保寬的運算子;絕不共用壓縮寬度的運算子。共用把多工器從運算子的輸出搬到它的輸入,所以它恰好在輸入不比輸出寬太多時才划算。5. 幅を保つ演算子は共有し、幅を圧縮する演算子は決して共有しない。共有はマルチプレクサを演算子の出力から入力へ移すので、入力が出力よりさほど広くないときにちょうど割に合う。
x/z as values, or scheduling order.任何涉及模擬時間、把 x/z 當值、或涉及排程順序的東西都不行。シミュレーション時間を参照するもの、x/z を値として扱うもの、スケジューリング順序に依存するものはすべて不可。initial, fork, wait, ===, force, sequential UDPs, switch primitives.不可有 initial、fork、wait、===、force、序向 UDP、開關級原件。initial、fork、wait、===、force、順序 UDP、スイッチレベルのプリミティブは不可。for is fine because it unrolls; disable of the enclosing block is fine because it becomes guards.for 沒問題,因為它會展開;disable 所在的那個包圍區塊沒問題,因為它會變成守衛條件。for は展開されるので問題ない。囲っているブロックの disable もガード条件になるので問題ない。always @*; never hand-maintain a sensitivity list.用 always @*;永遠不要手動維護敏感度列表。always @* を使う。センシティビティリストを手で保守しない。reg does not mean register. It means "procedurally assigned".reg 不代表暫存器。它代表「以程序方式被賦值」。reg はレジスタを意味しない。「手続き的に代入される」という意味である。else, default: arm, full-coverage casez.解法:在頂端放預設賦值(最佳)、最後的 else、default: 分支、完整覆蓋的 casez。対処:冒頭のデフォルト代入(最良)、最後の else、default: の枝、完全に覆う casez。full_case/parallel_case: identical gates, silently worse simulation.避開 full_case/parallel_case:閘一模一樣,模擬卻無聲地變差。full_case/parallel_case は避ける。ゲートは同一で、シミュレーションだけが黙って悪くなる。y = y in a level-sensitive block builds a mux with feedback.在準位敏感區塊裡寫 y = y 會造出一個帶回授的多工器。レベルセンシティブなブロックの y = y は、フィードバック付きのマルチプレクサを作る。x default = three OR gates; priority encoder = masking chain plus valid_data.default 為 x 的一般編碼器 = 三個 OR 閘;優先權編碼器 = 遮罩鏈加上 valid_data。default が x の素のエンコーダ = OR ゲート 3 個。プライオリティエンコーダ = マスキングの鎖 + valid_data。casez with ? to casex with x: identical gates, no hidden x-matching.優先選用搭配 ? 的 casez,而不是搭配 x 的 casex:閘一模一樣,卻沒有暗藏的 x 匹配。x を使う casex より ? を使う casez を選ぶ。ゲートは同一で、隠れた x の一致が起きない。cond ? value : 'bz is the tri-state template; the target must be a net.cond ? value : 'bz 是三態範本;目標必須是一條 net。cond ? value : 'bz がトライステートのテンプレート。対象はネットでなければならない。default: 'bx is a permission, not a value: it shrinks logic and removes the x warning.default: 'bx 是一個許可,不是一個值:它縮小邏輯,也拿掉那個 x 警告。default: 'bx は値ではなく許可である。論理を縮める代わりに x の警告を取り去る。always @* // complete sensitivity, compiler-built begin y = DEFAULT_Y; // every variable gets a value before any branch valid = 1'b0; case (sel) ... // each arm states only what differs default: ; // present even when unreachable endcase end
Lecture 5 is the hinge of the course: everything before it is about describing and simulating, everything after it is about building. It is worth being explicit about the connections, because several of the rules here only make full sense once you have seen what comes next.第 5 講是整門課的樞紐:它之前的一切都在講描述與模擬,它之後的一切都在講建造。把這些連結明白講出來是值得的,因為這裡好幾條規則,要等你看過後面的內容才完全說得通。第 5 講は科目全体の蝶番である。これより前はすべて記述とシミュレーションの話で、これより後はすべて作ることの話になる。ここにある規則のいくつかは後続の内容を見て初めて完全に腑に落ちるので、そのつながりを明示しておく価値がある。
| Lecture講次講義 | Connection to this material與本講材料的連結本講義との関係 |
|---|---|
| 1 — Introduction1 — 導論1 — 序論 | Supplies the design flow, the levels of abstraction and the nets-versus-registers distinction. The reg-is-not-a-register point in Section 4.4 is the one from Lecture 1 that students most often fail to internalise until they meet an inferred latch.提供設計流程、抽象層次,以及 net 與 register 的區分。第 4.4 節「reg 不是暫存器」那一點,正是第 1 講裡學生最常要等到遇上推論鎖存器才真正內化的那一點。設計フロー、抽象化のレベル、ネットとレジスタの区別を提供する。第 4.4 節の「reg はレジスタではない」という点は、第 1 講の内容のうち、学生が推論ラッチに出会うまで最も腑に落ちないままになりがちなものである。 |
| 2 — Simulation and test benches2 — 模擬與測試平台2 — シミュレーションとテストベンチ | Supplies the stimulus generator and response comparator in Section 1.2. The gate-level regression that catches a latch's stale output is a Lecture 2 test bench pointed at a Lecture 5 netlist.提供第 1.2 節裡的激勵產生器與回應比較器。那個會抓到鎖存器陳舊輸出的閘級回歸測試,就是一個第 2 講的測試平台指向一份第 5 講的網表。第 1.2 節の刺激生成器と応答比較器を提供する。ラッチの古い出力を捕まえるゲートレベル・リグレッションとは、第 2 講のテストベンチを第 5 講のネットリストに向けたものである。 |
| 3 — Syntax, UDPs, delay3 — 語法、UDP、延遲3 — 構文・UDP・遅延 | Supplies the UDP syntax of Section 4.2 and the delay modelling that Section 2.2 declares un-synthesisable. The combinational-versus-sequential UDP distinction is a Lecture 3 topic with a Lecture 5 consequence.提供第 4.2 節的 UDP 語法,以及第 2.2 節宣告為不可合成的延遲建模。組合 UDP 與序向 UDP 的區分是第 3 講的主題,卻有第 5 講的後果。第 4.2 節の UDP 構文と、第 2.2 節が合成不可と宣言する遅延モデリングを提供する。組合せ UDP と順序 UDP の区別は第 3 講の話題でありながら、帰結は第 5 講に現れる。 |
| 4 — Behavioural modelling4 — 行為式建模4 — 行動レベルのモデリング | Supplies always, blocking versus non-blocking assignment, loops, tasks and functions. Everything in Sections 4 to 7 is Lecture 4's constructs seen through the synthesiser's eyes. Blocking assignment (=) is correct throughout this lecture precisely because the logic is combinational.提供 always、阻擋式與非阻擋式賦值、迴圈、task 與 function。第 4 到第 7 節的一切,都是第 4 講的構件透過合成器的眼睛去看。阻擋式賦值(=)在本講從頭到尾都是正確的,正是因為這裡的邏輯是組合邏輯。always、ブロッキング代入とノンブロッキング代入、ループ、task、function を提供する。第 4 節から第 7 節までの内容はすべて、第 4 講の構文を合成器の目を通して見たものである。本講義を通してブロッキング代入(=)が正しいのは、まさに扱う論理が組合せだからである。 |
| 6 — Synthesis of sequential logic6 — 序向邏輯的合成6 — 順序論理の合成 | The direct sequel. The latch template of Section 7.3 becomes the flip-flop template always @(posedge clk); blocking assignment gives way to non-blocking; and the "assign on every path" rule inverts — in a clocked block an unassigned path is correct and means "hold", which is exactly what a register does.直接的續集。第 7.3 節的鎖存器範本變成正反器範本 always @(posedge clk);阻擋式賦值讓位給非阻擋式;而「每條路徑都要賦值」這條規則會反轉——在一個時脈驅動的區塊裡,一條沒有賦值的路徑是正確的,意思是「保持」,而那正是暫存器在做的事。直接の続編である。第 7.3 節のラッチのテンプレートはフリップフロップのテンプレート always @(posedge clk) になり、ブロッキング代入はノンブロッキング代入に場を譲る。そして「すべての経路で代入せよ」という規則は反転する。クロック同期ブロックでは、代入しない経路こそが正しく、それは「保持」を意味する。レジスタがするのはまさにそれである。 |
| 7 — Synthesis of language constructs7 — 語法構件的合成7 — 言語構文の合成 | Extends Section 2's lists with the detailed treatment of operators, loop unrolling and parameterisation.以對運算子、迴圈展開與參數化的詳細處理,擴充第 2 節的那些清單。演算子・ループ展開・パラメータ化の詳細な扱いによって、第 2 節の一覧を拡張する。 |
| 8 — Design examples8 — 設計範例8 — 設計例 | Where resource sharing becomes architectural: scheduling operations into cycles so that one large unit serves several uses, which Section 11.3 flags as the only sharing a synthesiser will not do for you.資源共用在這裡變成架構層級的事:把運算排程進不同的週期,讓一個大型單元服務好幾次使用,而第 11.3 節正是把那標示為合成器唯一不會替你做的共用。リソース共有がアーキテクチャの問題になる場所である。演算をサイクルに割り付け、一つの大きなユニットが複数の用途に仕える。第 11.3 節が、合成器が代わりにやってくれない唯一の共有として挙げたものである。 |
Synthesis is pattern matching against a small catalogue of templates, followed by Boolean optimisation that is free to rewrite anything you did not pin down. Both halves of that sentence matter. Because of the first half, the way you shape a description determines what kind of element you get — latch, flip-flop, mux, priority chain, tri-state driver — and small deviations from a template produce large surprises. Because of the second half, the way you write a function does not determine its gates: five descriptions of the same Boolean function give one netlist. Shape your code to the templates, and then stop worrying about the gates.合成是對一小本範本目錄做樣式比對,之後再接一道布林最佳化,而那道最佳化可以自由改寫任何你沒有釘死的東西。這句話的兩半都重要。因為前半,你塑造一段描述的方式決定了你拿到哪一種元件——鎖存器、正反器、多工器、優先權鏈、三態驅動器——而偏離範本一點點就會產生很大的意外。因為後半,你書寫一個函數的方式並不決定它的邏輯閘:同一個布林函數的五種描述給出同一份網表。把你的程式碼塑造成範本的形狀,然後就別再為那些閘操心了。合成とは、小さなテンプレートのカタログに対するパターン照合であり、その後に、こちらが固定しなかったものを自由に書き換えてよいブール最適化が続くものである。この文の前半も後半も重要である。前半ゆえに、記述をどう形づくるかが、得られる素子の種類——ラッチ、フリップフロップ、マルチプレクサ、優先順位チェーン、トライステートドライバ——を決め、テンプレートからのわずかなずれが大きな驚きを生む。後半ゆえに、関数をどう書くかはそのゲートを決めない。同じブール関数の五通りの記述が一つのネットリストを与える。コードをテンプレートの形に整え、そのうえでゲートのことは気に病まないことだ。