The cell-based design flow, the abstraction domains, and the first half of the Verilog language: not a summary of the slides but an account of why each rule exists, what the simulator and the synthesiser do with it, and how it fails when you get it wrong.Cell-based 設計流程、設計模型的各個領域,以及 Verilog 語言的前半部:這不是投影片的摘要,而是說明每一條規則為何存在、模擬器與合成器拿它來做什麼,以及一旦寫錯會如何失敗。セルベース設計フロー、設計モデルの各ドメイン、そして Verilog 言語の前半部。スライドの要約ではなく、各規則がなぜ存在するのか、シミュレータと合成器がそれをどう扱うのか、そして間違えたときどう破綻するのかを述べる。
Before any of the Verilog in this lecture makes sense, you need a map of the territory. A modern digital chip is not designed the way a breadboard circuit is designed. Nobody draws twenty million transistors. Instead there is a long, highly automated pipeline that starts with a paragraph of English and ends with a set of photomasks, and at every stage of that pipeline a different tool reads a different file in a different language and hands its output to the next tool. Verilog is the file format that the first half of the pipeline consumes, and almost everything you will learn in this course is about writing Verilog that the rest of the pipeline can actually use.在這一講的 Verilog 能說得通之前,你需要一張地形圖。現代數位晶片的設計方式和麵包板電路完全不同,沒有人會去畫兩千萬顆電晶體。取而代之的,是一條冗長而高度自動化的管線:它從一段英文敘述開始,最後產出一組光罩;在這條管線的每一個階段,都有不同的工具讀取不同語言寫成的不同檔案,再把輸出交給下一個工具。Verilog 正是這條管線前半段所消耗的檔案格式,而你在這門課裡學到的幾乎所有東西,都是為了寫出管線後續真的用得上的 Verilog。この講義の Verilog が意味を持つには、まず地形図が必要である。現代のデジタルチップは、ブレッドボード回路のようには設計されない。二千万個のトランジスタを誰も描かない。代わりにあるのは、英語の一段落から始まり一組のフォトマスクで終わる、長く高度に自動化されたパイプラインである。その各段階で、異なるツールが異なる言語で書かれた異なるファイルを読み、出力を次のツールへ渡す。Verilog はそのパイプラインの前半が消費するファイル形式であり、この科目で学ぶことのほとんどは、パイプラインの残りが実際に使える Verilog を書くためのものである。
The slides split the pipeline into a front end and a back end. The division is not arbitrary. Everything in the front end is a description of function: what the chip computes, and roughly how fast. Everything in the back end is a description of geometry: where each transistor physically sits, how wide each wire is, how much capacitance that wire adds. The hand-off point between the two is the gate-level netlist — a file that says "instance U17 is a NAND2X1 cell from the library; its A pin connects to net n43" and nothing about position. The front end produces it; the back end places it on silicon.投影片把這條管線分成前段(front end)與後段(back end)。這個分界並不隨意。前段的一切都是在描述功能:晶片算什麼,大致多快。後段的一切則是在描述幾何:每顆電晶體實際擺在哪裡、每條線有多寬、那條線又增加了多少電容。兩者之間的交接點是閘級網表(gate-level netlist) — 一個只說「instance U17 是函式庫裡的 NAND2X1 cell;它的 A 腳接到 net n43」而完全不提位置的檔案。前段產生它,後段把它擺到矽晶片上。スライドはこのパイプラインをフロントエンドとバックエンドに分ける。この区切りは恣意的ではない。フロントエンドにあるものはすべて機能の記述である。チップが何を計算し、おおよそどれだけ速いか。バックエンドにあるものはすべて幾何形状の記述である。各トランジスタが物理的にどこに座り、各配線がどれだけ太く、その配線がどれだけ容量を加えるか。両者の引き渡し点がゲートレベル・ネットリストである。「インスタンス U17 はライブラリの NAND2X1 セルであり、その A ピンはネット n43 につながる」とだけ述べ、位置については何も言わないファイルだ。フロントエンドがこれを生み、バックエンドがシリコン上に配置する。
Design specification. This is a document, not a file a tool reads. It states what the block does, its interface, its throughput, its area and power budget, and — crucially — its timing contract: which signals are synchronous to which clock, what the clock period is, how many cycles of latency the outside world may expect. Everything downstream is measured against this document. A specification that says "decodes a convolutional code" without saying "at 100 Msymbol/s with a traceback depth of 35" cannot be verified, because there is no statement that could be false.設計規格。這是一份文件,不是工具讀的檔案。它陳述這個區塊做什麼、它的介面、吞吐率、面積與功耗預算,而且關鍵的是它的時序契約:哪些訊號同步於哪個時脈、時脈週期是多少、外界可以預期多少個週期的延遲。下游的一切都以這份文件為量尺。一份只說「解碼一個迴旋碼」而沒說「在 100 Msymbol/s 下、回溯深度 35」的規格無法被驗證,因為裡面沒有任何一句話有可能是錯的。設計仕様。これは文書であって、ツールが読むファイルではない。そのブロックが何をするか、インタフェース、スループット、面積と電力のバジェット、そして決定的に重要なタイミング契約——どの信号がどのクロックに同期するか、クロック周期はいくつか、外界が何サイクルのレイテンシを見込んでよいか——を述べる。下流のすべてはこの文書を基準に測られる。「畳み込み符号を復号する」とだけ書き、「100 Msymbol/s でトレースバック長 35」と書かない仕様は検証できない。偽になりうる主張が一つも存在しないからである。
Design partition. You cut the system into modules that one person can write and one simulation can exercise. Partitioning is the single most consequential decision in the front end and the one that EDA tools help with the least. The rule of thumb that matters for synthesis is: register the outputs of every block, and put a whole combinational path inside one module. If a combinational path starts in block A, runs through block B and ends in a flip-flop in block C, the synthesiser must optimise it in three separate passes with artificial budgets at each boundary, and it will almost always produce a worse result than if the path had lived inside one module. Lectures 5 and 6 come back to this when they discuss synthesis constraints.設計切割。你把系統切成一個人寫得完、一次模擬跑得動的模組。切割是前段最具決定性的一步,卻也是 EDA 工具幫忙最少的一步。對合成而言最要緊的經驗法則是:每個區塊的輸出都要先過暫存器,而且把一整條組合路徑放在同一個模組裡。如果一條組合路徑起於區塊 A、穿過區塊 B、終於區塊 C 的正反器,合成器就必須分三趟最佳化它,而且每個邊界都只能給一個人造的預算;結果幾乎必然比整條路徑都住在同一個模組裡來得差。第 5、6 講談合成限制時會再回到這一點。設計分割。システムを、一人が書き切れて一回のシミュレーションで動かせるモジュールへ切り分ける。分割はフロントエンドで最も影響の大きい決定であり、同時に EDA ツールが最も助けてくれない決定でもある。合成にとって重要な経験則はこうだ。すべてのブロックの出力をレジスタで受け、組合せパス全体を一つのモジュール内に収めよ。組合せパスがブロック A で始まり B を通って C のフリップフロップで終わるなら、合成器は三回に分けて、しかも各境界に人為的なバジェットを置いて最適化せざるをえず、同一モジュール内に収めた場合よりほぼ確実に悪い結果になる。第 5 講と第 6 講で合成制約を論じる際にこの話へ戻る。
Design entry in Verilog. This is where you are. You write modules that describe behaviour at the register-transfer level: what combinational function feeds each register, and when each register loads. You are not drawing gates, and you are also not writing a program — you are writing a specification of hardware that happens to look like a program, and the difference will bite you in Section 2.以 Verilog 進行設計輸入。這就是你所在的位置。你寫的模組描述的是暫存器轉移層級的行為:哪個組合函數餵進哪個暫存器、每個暫存器何時載入。你不是在畫閘,但也不是在寫程式 — 你是在寫一份碰巧長得像程式的硬體規格,而這個差別會在第 2 節咬你一口。Verilog による設計入力。ここが君のいる場所である。レジスタ転送レベルでの振る舞い——どの組合せ関数が各レジスタを駆動し、各レジスタがいつロードするか——を記述するモジュールを書く。ゲートを描いているのではないが、プログラムを書いているのでもない。たまたまプログラムに見えるハードウェアの仕様を書いているのであり、その違いは第 2 節で君に噛みつく。
Simulation and functional verification. A simulator reads your Verilog plus a testbench, builds an event queue, and tells you what the outputs do. This is pure function: no gate has a delay yet, no wire has capacitance. If the design is wrong here it is wrong everywhere, and the bug is a thousand times cheaper to find here than after tape-out. Lecture 2 is entirely about this step.模擬與功能驗證。模擬器讀入你的 Verilog 加上一份測試平台(testbench),建立事件佇列,然後告訴你輸出會怎麼動。這一步是純功能:還沒有任何閘有延遲,也還沒有任何線有電容。設計若在這裡是錯的,它在任何地方都是錯的;而在這裡找出臭蟲,比下線(tape-out)之後才找出要便宜一千倍。第 2 講整講都在談這一步。シミュレーションと機能検証。シミュレータは君の Verilog とテストベンチを読み、イベントキューを構築し、出力がどう動くかを教える。これは純粋に機能のレベルであり、まだどのゲートにも遅延はなく、どの配線にも容量はない。ここで設計が誤っていればどこでも誤っており、しかもバグをここで見つけるほうがテープアウト後に見つけるより千倍安い。第 2 講はまるごとこの段階の話である。
Design integration and verification. Modules written by different people are wired together and the whole block is re-verified. Interface mismatches — a bus declared [7:0] on one side and [0:7] on the other, a handshake with the ready and valid signals swapped — surface here, and they surface as silently wrong data rather than as a compile error, because Verilog's positional port binding is happy to connect anything to anything of the right width.設計整合與驗證。不同人寫的模組被接在一起,整個區塊重新驗證一次。介面不一致 — 一側宣告成 [7:0] 另一側卻是 [0:7] 的匯流排、ready 和 valid 接反的交握訊號 — 都在這裡浮現,而且是以「資料安靜地錯掉」而不是以編譯錯誤的形式浮現,因為 Verilog 的位置式埠連接很樂意把任何東西接到任何等寬的東西上。設計統合と検証。別々の人が書いたモジュールを結線し、ブロック全体を再検証する。インタフェースの食い違い——片側で [7:0]、もう片側で [0:7] と宣言されたバス、ready と valid が入れ替わったハンドシェイク——はここで表面化する。しかもコンパイルエラーとしてではなく、静かに誤ったデータとして表面化する。Verilog の位置指定ポート接続は、幅さえ合えば何でも何にでもつないでしまうからだ。
Pre-synthesis sign-off. A gate: the RTL is frozen, lint-clean, and passes its functional regression. Only then does it go to synthesis. Teams that synthesise unfinished RTL spend their lives chasing timing on logic that is about to be rewritten.合成前簽核。一道關卡:RTL 凍結、lint 乾淨、功能回歸測試全過,只有這時才送去合成。拿未完成的 RTL 去合成的團隊,一輩子都在追一堆即將被重寫的邏輯的時序。合成前サインオフ。一つのゲートである。RTL を凍結し、lint をクリアし、機能回帰テストを通す。そこで初めて合成へ回す。未完成の RTL を合成に回すチームは、まもなく書き直される論理のタイミングを一生追いかけることになる。
Synthesis and mapping. A synthesiser — Synopsys Design Compiler / Design Vision, or Cadence Genus (the slides name its ancestor, Encounter RTL Compiler) — reads the RTL and a standard-cell library and produces a gate-level netlist. Internally this is two phases. First elaboration and generic optimisation: your always blocks and assign statements become a technology-independent network of generic operators (adders, multiplexers, Boolean nodes, registers). Then mapping: each generic node is replaced by real cells from the library, chosen to meet the timing constraints you supplied in an SDC file. The same RTL mapped against a 40 nm library and a 180 nm library yields completely different netlists.合成與映射。合成器 — Synopsys Design Compiler/Design Vision,或 Cadence Genus(投影片提到的是它的前身 Encounter RTL Compiler)— 讀入 RTL 與一套標準元件庫(standard-cell library),產出閘級網表。這件事內部分兩個階段。先是展開與一般化最佳化(elaboration and generic optimisation):你的 always 區塊和 assign 敘述變成一張與製程無關的通用運算元網路(加法器、多工器、布林節點、暫存器)。接著是映射(mapping):每個通用節點被換成函式庫裡的實體 cell,選哪一顆則取決於你在 SDC 檔裡給的時序限制。同一份 RTL 對 40 nm 與 180 nm 函式庫映射,會得到完全不同的網表。合成とマッピング。合成器——Synopsys Design Compiler / Design Vision、あるいは Cadence Genus(スライドはその前身 Encounter RTL Compiler を挙げている)——は RTL とスタンダードセル・ライブラリを読み、ゲートレベル・ネットリストを出力する。内部は二段階だ。まずエラボレーションと汎用最適化:君の always ブロックや assign 文が、プロセス非依存な汎用演算子(加算器、マルチプレクサ、ブール節点、レジスタ)のネットワークになる。次にマッピング:各汎用節点が、SDC ファイルで与えたタイミング制約を満たすように選ばれた実在のライブラリセルに置き換わる。同じ RTL でも 40 nm ライブラリと 180 nm ライブラリにマップすれば、まったく別のネットリストになる。
Post-synthesis validation and timing verification. The netlist is simulated again — now with real cell delays — and run through static timing analysis (STA). STA does not simulate; it enumerates every path from a register output to a register input, adds up the cell and wire delays, and compares the total against the clock period. It is exhaustive where simulation is only as good as your vectors, which is why timing is signed off by STA and function is signed off by simulation.合成後驗證與時序驗證。網表再被模擬一次 — 這次帶著真實的 cell 延遲 — 並送進靜態時序分析(STA)。STA 不做模擬;它窮舉每一條從暫存器輸出到暫存器輸入的路徑,把 cell 與線路的延遲加總,再和時脈週期比較。它是窮盡的,而模擬的好壞只取決於你的測試向量 — 這正是為什麼時序由 STA 簽核、功能由模擬簽核。合成後検証とタイミング検証。ネットリストを——今度は実際のセル遅延つきで——もう一度シミュレーションし、静的タイミング解析(STA)にかける。STA はシミュレートしない。レジスタ出力からレジスタ入力までのすべてのパスを列挙し、セルと配線の遅延を足し上げ、合計をクロック周期と比べる。シミュレーションがテストベクタの質までしか良くならないのに対し、STA は網羅的である。だからこそタイミングは STA で、機能はシミュレーションでサインオフされる。
Test generation and fault simulation. Design for testability (DFT) stitches the flip-flops into a shift register (a scan chain) so that a tester can load any state into the chip and read any state out. Automatic test pattern generation (ATPG, e.g. Synopsys TetraMAX) then computes a set of input vectors that will detect manufacturing defects, modelled as nets stuck at 0 or stuck at 1. Fault simulation reports the coverage. A chip that works perfectly but cannot be tested is worthless, because you cannot tell a good die from a bad one.測試樣本產生與故障模擬。可測試性設計(DFT)把正反器串成一條移位暫存器(掃描鏈,scan chain),讓測試機台能把任意狀態載入晶片、也能把任意狀態讀出來。自動測試樣本產生(ATPG,例如 Synopsys TetraMAX)接著算出一組輸入向量,用來偵測製造缺陷 — 缺陷被模型化成某條 net 固定在 0 或固定在 1。故障模擬則回報涵蓋率。一顆功能完美但無法測試的晶片毫無價值,因為你分不出哪顆是好的晶粒。テストパターン生成と故障シミュレーション。テスト容易化設計(DFT)はフリップフロップをシフトレジスタ(スキャンチェーン)に縫い合わせ、テスタが任意の状態をチップに書き込み、任意の状態を読み出せるようにする。次に自動テストパターン生成(ATPG、たとえば Synopsys TetraMAX)が、ネットが 0 固定または 1 固定になるとモデル化した製造欠陥を検出する入力ベクタ集合を計算する。故障シミュレーションがカバレッジを報告する。完璧に動くがテストできないチップは無価値である。良品ダイと不良ダイを区別できないからだ。
Automatic place and route (APR) takes the netlist and the physical library (the actual rectangles of each cell) and does four things that matter to you as a front-end designer. It places each cell on the standard-cell rows. It inserts the clock tree, a balanced buffer network that delivers the clock to every flip-flop with as little skew as possible — this is why you must never write combinational logic on a clock in RTL, because the tool needs the clock to be a clean, single-source signal it can buffer. It routes the signal nets in metal. Then the physical and electrical design rules are checked (DRC/ERC), the parasitic resistance and capacitance of every routed wire are extracted into an SPEF file, and timing is verified one last time with real wire delays instead of the statistical estimates synthesis used. If post-layout timing fails, the netlist comes back to the front end and you may have to restructure RTL you thought was finished.自動佈局繞線(APR)拿到網表與實體函式庫(每顆 cell 真正的矩形圖形),做四件對前段設計者有意義的事。它把每顆 cell 擺放到標準元件列上。它插入時脈樹,一棵平衡的緩衝器網路,盡可能以最小的偏斜(skew)把時脈送到每顆正反器 — 這正是為什麼你在 RTL 裡絕不可以對時脈寫組合邏輯,因為工具需要時脈是一個乾淨的、單一來源的訊號才能去加緩衝。它用金屬層繞線各條訊號 net。接著檢查實體與電性設計規則(DRC/ERC),把每條繞好的線的寄生電阻與電容萃取成 SPEF 檔,最後再用真實的線路延遲(而不是合成時用的統計估計)驗證一次時序。如果佈局後時序沒過,網表就被退回前段,而你可能得重構你以為早已完工的 RTL。自動配置配線(APR)は、ネットリストと物理ライブラリ(各セルの実際の矩形群)を受け取り、フロントエンド設計者にとって意味のある四つのことを行う。各セルをスタンダードセル行の上に配置する。クロックツリーを挿入する。これはクロックをできるだけ小さいスキューで全フリップフロップへ届ける平衡バッファ網であり、RTL でクロックに組合せ論理を書いてはならない理由でもある。ツールはバッファリングできる清浄な単一ソースのクロックを必要とするからだ。信号ネットを金属層で配線する。その後、物理・電気的デザインルールを検査し(DRC/ERC)、配線済み各ネットの寄生抵抗と容量を SPEF ファイルへ抽出し、合成が用いた統計的推定ではなく実際の配線遅延でタイミングを最終確認する。レイアウト後タイミングが落ちれば、ネットリストはフロントエンドへ戻され、完成したと思っていた RTL を作り直す羽目になる。
After design sign-off, the layout becomes GDSII, the GDSII becomes masks, and the project is taped out. The name is a fossil: layouts used to be cut from coloured tape on mylar. Months later packaged parts come back and are measured on the bench with a pattern generator driving the inputs and a logic analyser capturing the outputs — which is exactly the same stimulus-and-response structure as the testbench you wrote at the start, implemented in hardware instead of in the simulator.設計簽核之後,佈局變成 GDSII,GDSII 變成光罩,專案就下線(taped out)了。這個名字是化石:早年的佈局是用彩色膠帶黏在聚酯膜上剪出來的。幾個月後封裝好的晶片送回來,在實驗桌上量測 — 用圖樣產生器驅動輸入、用邏輯分析儀擷取輸出 — 這和你一開始寫的測試平台是完全相同的「刺激與回應」結構,只是用硬體而不是用模擬器實現。設計サインオフの後、レイアウトは GDSII になり、GDSII はマスクになり、プロジェクトはテープアウトされる。この名は化石である。昔のレイアウトはマイラー上の色テープから切り出されていた。数か月後、パッケージされた部品が戻り、パターンジェネレータで入力を駆動しロジックアナライザで出力を取り込んでベンチ上で測定される。これは最初に書いたテストベンチとまったく同じ刺激と応答の構造を、シミュレータではなくハードウェアで実装したものにほかならない。
A cell-based flow means the designer never designs a transistor. The foundry (or a library vendor) supplies a few hundred pre-designed, pre-characterised logic cells — inverters, NANDs, NORs, AOIs, multiplexers, flip-flops, latches — each in several drive strengths. Every cell has the same height so it can be abutted in rows, and each is delivered in several views: a .lib timing/power model, a Verilog simulation model, a LEF abstract for routing, and a GDSII layout. Synthesis picks cells from the .lib; APR places the GDSII. Your freedom is in the structure, never in the devices.Cell-based 流程的意思是:設計者從不設計電晶體。晶圓代工廠(或函式庫供應商)提供數百顆預先設計、預先特性化的邏輯 cell — 反相器、NAND、NOR、AOI、多工器、正反器、閂鎖 — 每種又有數個驅動強度。每顆 cell 高度相同,才能在列上並排貼合;每顆都以數種視圖交付:一個 .lib 時序/功耗模型、一個 Verilog 模擬模型、一個供繞線用的 LEF 抽象,以及一份 GDSII 佈局。合成從 .lib 裡挑 cell;APR 擺放 GDSII。你的自由在於結構,絕不在於元件本身。セルベース・フローとは、設計者がトランジスタを一つも設計しないということである。ファウンドリ(あるいはライブラリベンダ)が、設計済み・特性化済みの論理セルを数百種類供給する——インバータ、NAND、NOR、AOI、マルチプレクサ、フリップフロップ、ラッチ——それぞれ複数のドライブ強度で。すべてのセルは行の中で隣接できるよう同じ高さを持ち、複数のビューで提供される。.lib のタイミング/電力モデル、Verilog シミュレーションモデル、配線用の LEF アブストラクト、そして GDSII レイアウトである。合成は .lib からセルを選び、APR が GDSII を配置する。君の自由は構造にあり、デバイスにはない。
This single fact explains most of the style rules you will meet in Lectures 5 to 7. Why must you write always @(posedge clk) in a particular shape? Because the library contains a D flip-flop cell and the synthesiser has to recognise your code as "this is that cell". Why can't you write an arbitrary analogue behaviour? Because there is no cell for it. Why do tri-state buffers need care? Because z only has meaning on a net driven by an actual tri-state cell. The library is the vocabulary and synthesis is the translator; RTL that does not translate into library vocabulary is not a design, it is a simulation.這一個事實就解釋了你在第 5 到第 7 講會遇到的大多數風格規則。為什麼 always @(posedge clk) 一定要寫成某種特定形狀?因為函式庫裡有一顆 D 型正反器 cell,而合成器必須把你的程式碼認成「這就是那顆 cell」。為什麼你不能寫任意的類比行為?因為根本沒有那種 cell。為什麼三態緩衝器需要小心?因為 z 只有在由真正的三態 cell 驅動的 net 上才有意義。函式庫是詞彙,合成是翻譯者;翻不進函式庫詞彙的 RTL 不是設計,只是一段模擬。この一つの事実が、第 5 講から第 7 講で出会うスタイル規則のほとんどを説明する。なぜ always @(posedge clk) を特定の形で書かねばならないのか。ライブラリに D フリップフロップのセルがあり、合成器が君のコードを「これはそのセルだ」と認識しなければならないからだ。なぜ任意のアナログ的振る舞いを書けないのか。そのためのセルが存在しないからだ。なぜトライステートバッファには注意が要るのか。z は実際のトライステートセルが駆動するネットの上でしか意味を持たないからだ。ライブラリが語彙であり、合成が翻訳者である。ライブラリの語彙に翻訳できない RTL は設計ではなく、単なるシミュレーションである。
The slides also sketch the research flow a graduate student actually follows: pick a direction, find a topic, model the algorithm in C/C++ or MATLAB/Simulink until the fixed-point performance is acceptable, write the Verilog, verify it with the CAD tools (Design Compiler, DFT Compiler, TetraMAX, power analysis), lay it out, fabricate through a shuttle service, solder the part onto a board, measure it, and compare against reference ICs. Verilog sits in the middle of that chain. Every stage before it is a model; every stage after it is a consequence. If the Verilog is a faithful implementation of the MATLAB model and a synthesisable description of hardware, the project works. If it is only one of the two, it does not.投影片也勾勒了一位研究生實際會走的研究流程:選定方向、找題目、用 C/C++ 或 MATLAB/Simulink 把演算法模型化到定點效能可以接受為止、寫 Verilog、用 CAD 工具驗證(Design Compiler、DFT Compiler、TetraMAX、功耗分析)、佈局、經由多專案晶圓服務下線、把晶片焊上電路板、量測,再和參考 IC 比較。Verilog 坐在這條鏈的正中央。它之前的每一階段都是模型;它之後的每一階段都是後果。如果這份 Verilog 既忠實實現了 MATLAB 模型又是可合成的硬體描述,專案就成立;如果它只做到其中一件,就不成立。スライドは、大学院生が実際にたどる研究フローも素描している。方向を選び、テーマを見つけ、固定小数点性能が許容できるまで C/C++ や MATLAB/Simulink でアルゴリズムをモデル化し、Verilog を書き、CAD ツール(Design Compiler、DFT Compiler、TetraMAX、電力解析)で検証し、レイアウトし、シャトルサービスで試作し、部品を基板に実装し、測定し、参照 IC と比較する。Verilog はこの鎖の中央に座る。その前の各段階はモデルであり、その後の各段階は帰結である。Verilog が MATLAB モデルの忠実な実装でありかつ合成可能なハードウェア記述であれば、プロジェクトは成立する。どちらか一方でしかなければ成立しない。
The slides draw a pyramid: system concept at the apex, then algorithm, architecture, register-transfer level, gate level, transistor level at the base. Climbing the pyramid increases behavioural abstraction; descending it increases detail and complexity. The pyramid shape is not decorative — it is a statement about information content. A system-level description of an OFDM receiver fits on one page. Its RTL is twenty thousand lines. Its gate netlist is two million lines. Its transistor netlist is ten million devices. Each level down multiplies the volume of data by roughly an order of magnitude, which is exactly why each step down is automated and each step must be verified against the level above rather than re-derived by hand.投影片畫了一座金字塔:頂端是系統概念,往下依序是演算法、架構、暫存器轉移層級、閘級,底部是電晶體級。往上爬,行為抽象度提高;往下走,細節與複雜度增加。金字塔的形狀不是裝飾 — 它是在陳述資訊量。一個 OFDM 接收機的系統級描述只有一頁;它的 RTL 是兩萬行;它的閘級網表是兩百萬行;它的電晶體網表是一千萬顆元件。每往下一層,資料量大約乘上一個數量級,這正是為什麼每一次往下都要自動化,而且每一層都必須對照上一層驗證,而不是用手重新推導一遍。スライドはピラミッドを描く。頂点にシステム構想、続いてアルゴリズム、アーキテクチャ、レジスタ転送レベル、ゲートレベル、底にトランジスタレベル。登れば動作の抽象度が上がり、降りれば詳細と複雑さが増す。ピラミッド形は飾りではなく、情報量についての主張である。OFDM 受信機のシステムレベル記述は一ページに収まる。その RTL は二万行。そのゲートネットリストは二百万行。そのトランジスタネットリストは一千万素子。一段下るごとにデータ量はおよそ一桁増える。だからこそ下りの各段は自動化され、各段は手で導き直すのではなく一つ上の層と突き合わせて検証されねばならない。
| Level層級レベル | What it describes描述什麼何を記述するか | Typical artifact典型產出典型的な成果物 | How it is checked如何檢查どう検証するか |
|---|---|---|---|
| System系統システム | Function blocks, interfaces, data rates功能方塊、介面、資料速率機能ブロック、インタフェース、データレート | Block diagram, pin table, timing diagram方塊圖、接腳表、時序圖ブロック図、ピン表、タイミング図 | Review, system simulation審查、系統級模擬レビュー、システムシミュレーション |
| Algorithm演算法アルゴリズム | The mathematics, in floating or fixed point數學本身,浮點或定點数学そのもの、浮動小数点または固定小数点で | C/C++ or MATLAB model, e.g. a radix-2 FFT decompositionC/C++ 或 MATLAB 模型,例如基數 2 的 FFT 分解C/C++ または MATLAB モデル、たとえば基数 2 の FFT 分解 | Bit-error-rate / SQNR simulation位元錯誤率/SQNR 模擬ビット誤り率/SQNR シミュレーション |
| Architecture架構アーキテクチャ | Which hardware resource performs which operation, and when哪個硬體資源在何時執行哪個運算どのハードウェア資源がいつどの演算を行うか | Datapath + controller diagram, scheduling table資料路徑+控制器方塊圖、排程表データパス+コントローラ図、スケジューリング表 | Cycle-accurate model週期精確模型サイクル精度モデル |
| RTLRTLRTL | Combinational functions between registers, and register loading暫存器之間的組合函數,以及暫存器何時載入レジスタ間の組合せ関数と、レジスタのロード | Verilog always / assignVerilog always/assignVerilog の always/assign | Simulation against the algorithm model對照演算法模型進行模擬アルゴリズムモデルと突き合わせたシミュレーション |
| Gate閘級ゲート | Library cells and their interconnections函式庫 cell 及其互連ライブラリセルとその相互接続 | Verilog netlist of cell instances由 cell 實例組成的 Verilog 網表セルインスタンスからなる Verilog ネットリスト | Equivalence check vs RTL; STA; gate-level sim與 RTL 的等效性檢查;STA;閘級模擬RTL との等価性検証、STA、ゲートレベル・シミュレーション |
| Transistor電晶體級トランジスタ | Devices, widths, lengths, parasitics元件、寬度、長度、寄生效應デバイス、幅、長さ、寄生成分 | SPICE netlist; layout polygonsSPICE 網表;佈局多邊形SPICE ネットリスト、レイアウト多角形 | SPICE simulation, DRC, LVSSPICE 模擬、DRC、LVSSPICE シミュレーション、DRC、LVS |
The slide's System Specification page is worth dwelling on because it shows the three artifacts of a real spec side by side: a function block diagram (a videoconferencing terminal: H.263 video codec, G.723.1 audio codec, H.225 mux/demux, V.34 modem), a pin table giving each port's direction, meaning and drive strength or output load, and a timing diagram showing when start, in, ready and out are valid relative to the clock. Those three together are what you must have before you type module. The pin table is the module header. The timing diagram is the testbench. The block diagram is the partition.投影片那頁「System Specification」值得多看一會兒,因為它把一份真實規格的三樣產出並排放在一起:一張功能方塊圖(一台視訊會議終端:H.263 視訊編解碼器、G.723.1 音訊編解碼器、H.225 多工/解多工、V.34 數據機)、一張接腳表標出每個埠的方向、意義與驅動強度或輸出負載,以及一張時序圖說明 start、in、ready、out 相對於時脈何時有效。這三樣加起來,就是你在打出 module 之前必須先有的東西。接腳表就是模組標頭;時序圖就是測試平台;方塊圖就是切割方式。スライドの System Specification のページは、実際の仕様がもつ三つの成果物を並べて見せている点で、立ち止まる価値がある。機能ブロック図(テレビ会議端末:H.263 映像コーデック、G.723.1 音声コーデック、H.225 多重/分離、V.34 モデム)、各ポートの方向・意味・ドライブ強度または出力負荷を与えるピン表、そして start、in、ready、out がクロックに対していつ有効かを示すタイミング図である。この三つが揃って初めて module とタイプできる。ピン表はモジュールヘッダであり、タイミング図はテストベンチであり、ブロック図は分割である。
The Algorithm Mapping slide makes the same point one level down, using the DFT. At system level you have the equation「Algorithm Mapping」那頁在低一層做同樣的事,用的是 DFT。在系統層級你拿到的是方程式Algorithm Mapping のスライドは、一段下のレベルで同じことを DFT を使って示す。システムレベルで手元にあるのは次の式である。
and its decimation-in-time decomposition以及它的時域抽取(decimation-in-time)分解そしてその時間間引き(decimation-in-time)分解
Nothing in those two lines says anything about hardware. The architecture step is what turns them into an input buffer, a serial-to-parallel converter, a bank of shift registers, an r-point DFT engine using distributed arithmetic, a coefficient ROM, a parallel-to-serial converter and a RAM. Two different architectures implementing the identical equation can differ by a factor of fifty in area. That choice is yours, not the tool's, and it is made before a line of Verilog is written. This is the content of Lecture 8.這兩行裡沒有一個字談到硬體。架構這一步才是把它們變成一個輸入緩衝器、一個串列轉並列轉換器、一組移位暫存器、一個用分散式算術實作的 r 點 DFT 引擎、一個係數 ROM、一個並列轉串列轉換器,以及一塊 RAM。兩種實作同一條方程式的不同架構,面積可以差到五十倍。那個選擇是你的,不是工具的,而且是在寫下第一行 Verilog 之前就做了。這是第 8 講的內容。この二行はハードウェアについて何も語っていない。それらを入力バッファ、直列並列変換器、シフトレジスタ群、分散算術による r 点 DFT エンジン、係数 ROM、並列直列変換器、そして RAM へ変えるのがアーキテクチャの段階である。同一の式を実装する二つの異なるアーキテクチャが、面積で五十倍違うこともある。その選択はツールではなく君のものであり、Verilog を一行も書かないうちに下される。これが第 8 講の内容である。
The slides present a table whose rows are abstraction levels (system, algorithm, RTL, gate, transistor) and whose columns are three model domains: abstract (what the thing does), structural (what it is made of), and physical (where the pieces are). Overlapping boxes labelled Architecture Design, Structural Design, Logic Design and Layout Design march diagonally down the table, with arrows labelled synthesis pointing right-and-down and arrows labelled verification pointing left-and-up.投影片上有一張表,列是抽象層次(系統、演算法、RTL、閘級、電晶體級),行是三個模型領域:抽象(這東西做什麼)、結構(它由什麼組成)、實體(各部件放在哪裡)。標著 Architecture Design、Structural Design、Logic Design、Layout Design 的方框彼此重疊,沿著表格對角線往下排列;標著合成的箭頭指向右下,標著驗證的箭頭指向左上。スライドには、行が抽象化レベル(システム、アルゴリズム、RTL、ゲート、トランジスタ)、列が三つのモデルドメイン——抽象(それが何をするか)、構造(何でできているか)、物理(部品がどこにあるか)——からなる表が示されている。Architecture Design、Structural Design、Logic Design、Layout Design と記された重なり合う箱が表を対角線状に下り、合成と記された矢印が右下を、検証と記された矢印が左上を指している。
That table is a flattened rendering of the Gajski–Kuhn Y-chart, the standard way this idea is drawn in the literature. The Y-chart puts the same three domains on three axes radiating from a common origin, with concentric rings for the abstraction levels. Reading it is mechanical: pick a ring, and the three points where it crosses the axes are three equivalent descriptions of the same design. Moving along an axis toward the centre is refinement within a domain; moving between axes at the same ring is a domain translation, and that translation is what the word "synthesis" means.那張表其實是 Gajski–Kuhn Y 圖攤平後的畫法,而 Y 圖才是文獻裡表達這個想法的標準方式。Y 圖把同樣的三個領域放在從同一原點輻射出去的三根軸上,再用同心圓環代表抽象層次。讀法是機械式的:挑一個圓環,它與三根軸相交的三個點,就是同一個設計的三種等價描述。沿著一根軸往圓心移動,是同一領域內的細化;在同一個圓環上跨越軸,則是領域之間的翻譯,而那個翻譯正是「合成」這個詞的意思。あの表は、文献でこの考えを描く標準的な図である Gajski–Kuhn の Y チャートを平らに引き伸ばしたものである。Y チャートは同じ三つのドメインを共通の原点から放射する三本の軸に置き、抽象化レベルを同心円で表す。読み方は機械的だ。一つの円を選べば、その円が三本の軸と交わる三点が、同じ設計の三つの等価な記述である。軸に沿って中心へ動くのはドメイン内の詳細化であり、同じ円の上で軸をまたぐのはドメイン間の翻訳であって、その翻訳こそが「合成」という語の意味である。
The lecture slide labels its first column "Abstract" where the classical Y-chart says "Behavioral", and it draws the overlapping boxes so that Architecture Design spans the system and algorithm rows, Structural Design spans algorithm and RTL, Logic Design spans RTL and gate, and Layout Design spans gate and transistor. The overlaps are the point: a design step is never confined to one row. Do not read the boxes as "architecture design happens at the system level only" — read them as "architecture design is the act of moving from a system behaviour to an algorithmic structure".課堂投影片把第一行標成「Abstract」,而古典 Y 圖寫的是「Behavioral」;它畫重疊方框的方式是:Architecture Design 橫跨系統與演算法兩列,Structural Design 橫跨演算法與 RTL,Logic Design 橫跨 RTL 與閘級,Layout Design 橫跨閘級與電晶體級。重疊本身就是重點:一個設計步驟從來不會侷限在單獨一列。不要把方框讀成「架構設計只發生在系統層級」 — 要讀成「架構設計就是從系統行為走到演算法結構這個動作」。講義スライドは第一列を「Abstract」と記すが、古典的な Y チャートでは「Behavioral」である。また重なり合う箱は、Architecture Design がシステム行とアルゴリズム行にまたがり、Structural Design がアルゴリズムと RTL に、Logic Design が RTL とゲートに、Layout Design がゲートとトランジスタにまたがるように描かれている。この重なりこそが要点である。設計の一段階が一行に収まることはない。箱を「アーキテクチャ設計はシステムレベルだけで起こる」と読んではならない。「アーキテクチャ設計とは、システムの振る舞いからアルゴリズムの構造へ移る行為そのものである」と読むべきだ。
The practical value of the chart is that it tells you what a tool is and is not allowed to change. Synthesis moves you from the behavioural axis to the structural axis at a fixed or finer ring; it may not invent a better algorithm. If your RTL says "ripple-carry adder", Design Compiler may rebuild it as a carry-look-ahead adder because both implement the same Boolean function at the same ring — but if your RTL says "compute the DFT with 1024 sequential multiplications", no synthesiser in the world will give you an FFT, because that is a move along the behavioural axis and tools do not make it. Architectural improvement is human work. This is the single most important thing the Y-chart has to teach you.這張圖的實用價值,在於它告訴你一個工具被允許改動什麼、不被允許改動什麼。合成把你從行為軸搬到結構軸,而且是在同一個或更細的圓環上;它不會替你發明更好的演算法。如果你的 RTL 寫的是「漣波進位加法器」,Design Compiler 可以把它重建成超前進位加法器,因為兩者在同一個圓環上實現同一個布林函數 — 但如果你的 RTL 寫的是「用 1024 次連續乘法算 DFT」,世界上沒有任何合成器會還你一個 FFT,因為那是沿著行為軸的移動,而工具不做這種移動。架構上的改良是人的工作。這是 Y 圖要教你的最重要一件事。このチャートの実用的な価値は、ツールが何を変えてよく何を変えてはならないかを教える点にある。合成は君を動作軸から構造軸へ移すが、それは同じ円か、より内側の円の上でである。より良いアルゴリズムを発明したりはしない。RTL が「リップルキャリー加算器」と言っているなら、Design Compiler はそれをキャリー先見加算器に組み替えてよい。どちらも同じ円の上で同じブール関数を実装するからだ。しかし RTL が「1024 回の逐次乗算で DFT を計算する」と言っているなら、世界中のどの合成器も FFT を返してはくれない。それは動作軸に沿った移動であり、ツールはその移動をしないからだ。アーキテクチャの改良は人間の仕事である。これが Y チャートが教えるもっとも重要な一点である。
Problem: For each activity, name the flow stage and say whether it is front end or back end. (a) Deciding that the FIR filter will be folded onto one multiplier used eight times per sample. (b) Running TetraMAX. (c) Discovering that net dout[3] has 0.42 pF of routing capacitance. (d) Finding that the receiver drops a symbol when valid and ready assert in the same cycle. (e) Choosing NAND2X4 instead of NAND2X1 for a heavily loaded node. (f) Balancing the clock insertion delay to every flip-flop.題目:對下列每項活動,指出它屬於流程的哪一階段,並說明是前段還是後段。(a) 決定把這個 FIR 濾波器摺疊到一顆乘法器上、每個取樣使用八次。(b) 執行 TetraMAX。(c) 發現 net dout[3] 帶有 0.42 pF 的繞線電容。(d) 發現當 valid 與 ready 在同一個週期同時成立時,接收端會掉一個符元。(e) 為某個重負載節點改選 NAND2X4 而不用 NAND2X1。(f) 平衡送到每顆正反器的時脈插入延遲。問題:次の各作業について、フローのどの段階かを答え、フロントエンドかバックエンドかを述べよ。(a) FIR フィルタを一つの乗算器に折りたたみ、1 サンプルあたり八回使うと決める。(b) TetraMAX を走らせる。(c) ネット dout[3] に 0.42 pF の配線容量があると判明する。(d) valid と ready が同一サイクルでアサートされると受信側がシンボルを落とすと分かる。(e) 負荷の重いノードに NAND2X1 ではなく NAND2X4 を選ぶ。(f) 各フリップフロップへのクロック挿入遅延を平衡させる。
(a) Architecture / design partition, front end — and note it is a move along the behavioural axis, so no tool will do it for you.(a) 架構/設計切割,前段 — 並且請注意這是沿著行為軸的移動,所以沒有工具會替你做。(a) アーキテクチャ/設計分割、フロントエンド。しかもこれは動作軸に沿った移動なので、どのツールも代わりにやってはくれない。
(b) ATPG, front end (the DFT/test lane).(b) ATPG,前段(DFT/測試這條通道)。(b) ATPG、フロントエンド(DFT/テストのレーン)。
(c) Parasitic extraction, back end. Before extraction the synthesiser only had a wire-load estimate.(c) 寄生參數萃取,後段。萃取之前,合成器手上只有 wire-load 估計值。(c) 寄生抽出、バックエンド。抽出前、合成器はワイヤロード推定値しか持っていない。
(d) Functional verification by simulation, front end. This is a protocol bug, invisible to STA.(d) 以模擬進行的功能驗證,前段。這是協定層面的臭蟲,STA 看不見。(d) シミュレーションによる機能検証、フロントエンド。これはプロトコルのバグであり、STA には見えない。
(e) Mapping / drive-strength selection inside synthesis, front end. It may be revised again during post-route optimisation in the back end.(e) 合成內部的映射/驅動強度選擇,前段。它在後段的繞線後最佳化中還可能被再次修改。(e) 合成内部のマッピング/ドライブ強度選択、フロントエンド。バックエンドの配線後最適化で再び修正されることもある。
(f) Clock-tree synthesis, back end — part of APR.(f) 時脈樹合成,後段 — 屬於 APR 的一部分。(f) クロックツリー合成、バックエンド。APR の一部である。
Problem: Place each description on the Y-chart (axis + ring), then say which single arrow connects the pair (a)→(b) and which connects (c)→(d). (a) assign sum = a + b; (b) a netlist of 16 FAX1 cells (c) the equation $X(k)=\sum_{n} x(n)W_N^{nk}$ (d) "an 8-point FFT butterfly datapath with one complex multiplier and a twiddle ROM".題目:把下列每項描述放到 Y 圖上(哪一軸+哪一環),再說出是哪一支箭頭連接 (a)→(b)、哪一支連接 (c)→(d)。(a) assign sum = a + b; (b) 由 16 顆 FAX1 cell 組成的網表 (c) 方程式 $X(k)=\sum_{n} x(n)W_N^{nk}$ (d)「一條 8 點 FFT 蝶形資料路徑,含一顆複數乘法器與一個旋轉因子 ROM」。問題:次の各記述を Y チャート上(軸+円)に配置し、(a)→(b) を結ぶ矢印と (c)→(d) を結ぶ矢印をそれぞれ答えよ。(a) assign sum = a + b; (b) 16 個の FAX1 セルからなるネットリスト (c) 式 $X(k)=\sum_{n} x(n)W_N^{nk}$ (d)「複素乗算器一つとツイドル ROM を持つ 8 点 FFT バタフライ・データパス」。
(a) Behavioural axis, RTL ring. (b) Structural axis, gate ring. (c) Behavioural axis, algorithm ring. (d) Structural axis, architecture/RTL ring.(a) 行為軸,RTL 環。(b) 結構軸,閘級環。(c) 行為軸,演算法環。(d) 結構軸,架構/RTL 環。(a) 動作軸・RTL の円。(b) 構造軸・ゲートの円。(c) 動作軸・アルゴリズムの円。(d) 構造軸・アーキテクチャ/RTL の円。
(a)→(b) is logic/RTL synthesis: same ring region, behavioural axis to structural axis, performed by Design Compiler.(a)→(b) 是邏輯/RTL 合成:同一個圓環區域,由行為軸走到結構軸,由 Design Compiler 執行。(a)→(b) は論理/RTL 合成である。同じ円の領域で動作軸から構造軸へ移る操作であり、Design Compiler が行う。
(c)→(d) is architecture synthesis — nominally the same kind of move one ring out, but in a cell-based ASIC flow it is done by a human. That asymmetry (one arrow automated, the other not) is the practical content of the chart.(c)→(d) 是架構合成 — 名義上是外面一環的同一種移動,但在 cell-based ASIC 流程裡它是由人完成的。這種不對稱(一支箭頭自動化、另一支沒有)就是這張圖的實用內涵。(c)→(d) はアーキテクチャ合成である。名目上は一つ外側の円における同種の移動だが、セルベース ASIC フローではこれを人間が行う。この非対称性(一方の矢印は自動化され、他方はされない)こそがこのチャートの実用的な中身である。
Trap: many students answer "(c)→(d) is high-level synthesis, so a tool does it". HLS tools exist, but they schedule a given algorithm onto resources; they do not replace a direct DFT with an FFT. The algorithmic choice stays on the behavioural axis.陷阱:很多學生會答「(c)→(d) 是高階合成,所以工具會做」。HLS 工具確實存在,但它們是把一個給定的演算法排程到資源上;它們不會把直接 DFT 換成 FFT。演算法的選擇始終留在行為軸上。罠:多くの学生が「(c)→(d) は高位合成だからツールがやる」と答える。HLS ツールは確かに存在するが、それは与えられたアルゴリズムを資源にスケジューリングするものであって、直接 DFT を FFT に置き換えはしない。アルゴリズムの選択は動作軸の上に残り続ける。
The slides give a list of reasons for using an HDL — hardware is hard to design directly, an HDL is cheaper for exploring options, it reduces time and cost, it supports concurrency — and the list is correct but it reads like marketing. It is worth understanding what each item really means, because the single feature that separates an HDL from a programming language, concurrency, is also the feature that produces the most confusing bugs for a student arriving from C.投影片列了一串使用 HDL 的理由 — 硬體難以直接設計、用 HDL 探索各種方案比較便宜、它縮短時間與成本、它支援並行性 — 這串理由都對,但讀起來像廣告詞。值得弄清楚每一條真正的意思,因為把 HDL 和程式語言分開來的那個唯一特徵:並行性,同時也是對一個從 C 語言過來的學生製造最多困惑臭蟲的特徵。スライドは HDL を使う理由を並べる——ハードウェアは直接設計しにくい、HDL のほうが選択肢を安く探索できる、時間とコストを削減する、並行性を扱える——。いずれも正しいが、宣伝文句のように読める。各項目が実際に何を意味するかを理解する価値はある。HDL をプログラミング言語から分かつ唯一の特徴である並行性こそ、C から来た学生に最も紛らわしいバグを生む特徴でもあるからだ。
Consider these two fragments, one in C and one in Verilog, which look nearly identical.看看下面兩段幾乎長得一模一樣的程式碼,一段是 C,一段是 Verilog。ほとんど見分けのつかない次の二つの断片を見よ。一方は C、他方は Verilog である。
int a = 1, b = 0, c; c = a & b; // c = 0 b = 1; // c is still 0 printf("%d", c); // prints 0
reg a = 1, b = 0; wire c; assign c = a & b; // a permanent connection initial begin #10 b = 1; // at t=10, c becomes 1 #0 $display(c); // prints 1 end
In C, c = a & b; is an event: it happens once, at the moment control reaches that line, and afterwards c is a dead value that remembers the past. In Verilog, assign c = a & b; is a structure: it describes an AND gate that exists for the whole simulation, re-evaluating every time a or b changes. The slide's phrase for this is "continuous assignment: static binding between LHS and RHS; no mechanism to eliminate or alter the binding". The gate is soldered in. You cannot un-solder it from inside the language.在 C 裡,c = a & b; 是一個事件:它只發生一次,就在控制流抵達那一行的瞬間,之後 c 變成一個記著過去的死值。在 Verilog 裡,assign c = a & b; 是一個結構:它描述一顆在整個模擬期間都存在的 AND 閘,每當 a 或 b 變動就重新求值。投影片對此的說法是「連續指定:LHS 與 RHS 之間的靜態繫結;語言中沒有任何機制可以消除或改變這個繫結」。這顆閘是焊上去的,你無法在語言內部把它拆焊。C では c = a & b; はイベントである。制御がその行に達した瞬間に一度だけ起こり、以後 c は過去を覚えているだけの死んだ値になる。Verilog では assign c = a & b; は構造である。シミュレーション中ずっと存在し、a か b が変わるたびに再評価される AND ゲートを記述している。スライドの言い方では「連続代入:左辺と右辺の静的な束縛。その束縛を取り除いたり変えたりする機構は存在しない」。ゲートははんだ付けされている。言語の内側からはんだを外すことはできない。
Every assign, every gate primitive, and every always block in every module in your design is a separate concurrent process. The order in which you write them in the file has no effect whatsoever on behaviour. A student who writes你設計裡每個模組中的每一條 assign、每一顆閘基元、每一個 always 區塊,都是一個各自獨立的並行行程。你在檔案裡把它們寫成什麼順序,對行為毫無影響。一個學生寫下設計中のあらゆるモジュールにある assign の一つ一つ、ゲートプリミティブの一つ一つ、always ブロックの一つ一つが、それぞれ独立した並行プロセスである。ファイル内でどの順に書いたかは振る舞いにまったく影響しない。次のように書いて
assign y = t & d; // uses t assign t = a | b; // defines t — order does NOT matter
and worries that t is "used before it is defined" has not yet made the mental switch. There is no "before". Both statements are wires and gates that coexist. The simulator discovers the dependency itself through its event queue: when a changes it schedules an update of t; when t changes it schedules an update of y. That mechanism is the subject of Lecture 2.然後擔心 t「在定義之前就被使用」,這表示他還沒完成腦中的切換。這裡沒有「之前」。兩條敘述都是同時存在的線與閘。模擬器會自己透過事件佇列發現相依關係:a 變動時它排程一次 t 的更新;t 變動時它排程一次 y 的更新。那套機制正是第 2 講的主題。t が「定義より先に使われている」と心配する学生は、まだ頭の切り替えができていない。「先」など存在しない。どちらの文も同時に存在する配線とゲートである。シミュレータは依存関係をイベントキューによって自ら見つける。a が変われば t の更新をスケジュールし、t が変われば y の更新をスケジュールする。その仕組みが第 2 講の主題である。
Verilog source text is not a sequence of instructions. It is a list of simultaneously existing hardware objects. The only place where ordering means anything is inside a begin…end block using blocking assignments, and even there the ordering is a modelling device, not a description of time passing in the circuit.Verilog 原始碼不是一串指令序列,而是一份同時存在的硬體物件清單。唯一讓順序有意義的地方,是在 begin…end 區塊內部使用阻隔式指定時;而即使在那裡,順序也只是一種建模手段,不是電路中時間流逝的描述。Verilog のソーステキストは命令の列ではない。同時に存在するハードウェア要素のリストである。順序が意味を持つ唯一の場所は、ブロッキング代入を使う begin…end ブロックの内側だけであり、そこでさえ順序はモデリングの道具であって、回路内で時間が流れることの記述ではない。
Beyond concurrency, the slides list several features. Each is worth one sentence of translation into practice.除了並行性之外,投影片還列了好幾項特性。每一項都值得用一句話翻譯成實務上的意義。並行性のほかにも、スライドはいくつかの特徴を挙げる。どれも実務の言葉へ一文で翻訳しておく価値がある。
if, case, for and arithmetic operators and let the synthesiser build the multiplexers, priority encoders and adders. Writing a 32-bit comparator as a gate netlist takes an afternoon and contains at least one typo; writing assign gt = (a > b); takes four seconds and contains none.高階程式構造。你可以寫 if、case、for 和算術運算子,讓合成器去搭出多工器、優先編碼器與加法器。把一個 32 位元比較器寫成閘級網表要花一個下午,而且至少會有一個筆誤;寫 assign gt = (a > b); 只要四秒,而且一個筆誤也沒有。高級なプログラミング構文。if、case、for や算術演算子を書けば、マルチプレクサ、プライオリティエンコーダ、加算器は合成器が組み立てる。32 ビット比較器をゲートレベル・ネットリストとして書けば午後一杯かかり、誤記が少なくとも一つ混じる。assign gt = (a > b); と書けば四秒で済み、誤記は一つもない。$readmemh. Without this you could never verify anything incrementally.抽象層次可以自由混用。你可以模擬一顆晶片,其中正在設計的區塊是 RTL、它的鄰居是閘級網表、記憶體則是用 $readmemh 的行為模型。沒有這一點,你永遠無法逐步驗證任何東西。抽象化レベルの自由な混在。設計中のブロックは RTL、その隣はゲートネットリスト、メモリは $readmemh を使う動作モデル——そんなチップをまとめてシミュレーションできる。これなしには、何一つ段階的に検証できない。#5, and from SDF back-annotation after layout), so the very same netlist can be simulated as pure logic before synthesis and with real cell delays afterwards. Lecture 3 covers the delay model in detail.功能與時序兼備。Verilog 可以攜帶延遲標註(#5,以及佈局後由 SDF 反標註而來的延遲),所以同一份網表既能在合成前當成純邏輯模擬,也能在合成後帶著真實 cell 延遲模擬。第 3 講會詳細講延遲模型。機能とタイミングの両方。Verilog は遅延注釈(#5、およびレイアウト後の SDF バックアノテーション)を担えるので、まったく同じネットリストを合成前は純論理として、合成後は実セル遅延つきでシミュレーションできる。遅延モデルは第 3 講で詳しく扱う。| Verilog (used in this course)Verilog(本課程使用)Verilog(本科目で使用) | VHDLVHDLVHDL | |
|---|---|---|
| Origin起源起源 | Gateway Design Automation, 1984; released to the public domain by Cadence in 1990 and given to Open Verilog InternationalGateway Design Automation,1984 年;1990 年由 Cadence 釋出為公共領域,並交給 Open Verilog InternationalGateway Design Automation、1984 年。1990 年に Cadence がパブリックドメインへ公開し、Open Verilog International へ移管 | US DoD VHSIC programme, early 1980s美國國防部 VHSIC 計畫,1980 年代初米国防総省の VHSIC 計画、1980 年代初頭 |
| Standard標準規格 | IEEE 1364-1995, revised 2001 and 2005; absorbed into IEEE 1800 (SystemVerilog) from 2009IEEE 1364-1995,2001 與 2005 年修訂;2009 年起併入 IEEE 1800(SystemVerilog)IEEE 1364-1995、2001 年と 2005 年に改訂。2009 年以降は IEEE 1800(SystemVerilog)に統合 | IEEE 1076-1987, revised 1993/2002/2008; IEEE 1164 (1993) adds the nine-value std_logic typeIEEE 1076-1987,1993/2002/2008 年修訂;IEEE 1164(1993)另加入九值的 std_logic 型別IEEE 1076-1987、1993/2002/2008 年に改訂。IEEE 1164(1993)が九値の std_logic 型を追加 |
| Ancestry of syntax語法的血統構文の系譜 | C / C++C/C++C/C++ | Ada / PascalAda/PascalAda/Pascal |
| Type system型別系統型システム | Few predefined types, weak checking, easy to start with預定義型別少、檢查寬鬆、容易上手定義済みの型が少なく、検査が緩く、始めやすい | User-defined types, strong checking, more verbose but catches more at compile time可自訂型別、檢查嚴格、較囉嗦但在編譯期就攔下更多錯誤ユーザ定義型があり、検査が厳格。冗長だがコンパイル時に多くを捕まえる |
| Traditional strength傳統強項伝統的な得意分野 | Gate and transistor level; switch-level primitives and strength modelling are built in閘級與電晶體級;開關級基元與驅動強度建模是內建的ゲートレベルとトランジスタレベル。スイッチレベル・プリミティブと強度モデリングが組み込み | System level; packages, generics and configurations make large structured designs tidier系統層級;package、generic 與 configuration 讓大型結構化設計更整齊システムレベル。package、generic、configuration が大規模な構造的設計を整理する |
| Where it hurts痛處在哪痛いところ | Silent width mismatches; reg/wire confusion; no package mechanism in 1364-1995位寬不符卻不出聲;reg 與 wire 容易混淆;1364-1995 沒有 package 機制幅の不一致が黙って通る。reg と wire の混同。1364-1995 には package 機構がない | Verbosity; conversion functions between std_logic_vector, signed and integer囉嗦;std_logic_vector、signed 與 integer 之間要寫一堆轉換函式冗長さ。std_logic_vector、signed、integer の間の変換関数の多さ |
The honest summary is the slide's: they are equally effective and the choice is preference or employer policy. For a Verilog beginner, though, one asymmetry matters. VHDL's strong typing rejects at compile time a large class of mistakes — connecting a 4-bit signal to an 8-bit port, assigning an integer to a bit vector — that Verilog accepts silently, zero-extends or truncates, and turns into a functional bug you find three days later in a waveform. Verilog's low ceremony is a real productivity advantage and a real hazard, and the lint tools in the front-end flow exist largely to recover the checking that the language declined to do.最誠實的總結就是投影片講的:兩者同樣有效,選哪個是偏好或雇主政策的問題。不過對 Verilog 初學者而言,有一個不對稱很要緊。VHDL 的強型別在編譯期就擋下一大類錯誤 — 把 4 位元訊號接到 8 位元的埠、把整數指定給位元向量 — 而 Verilog 會安靜地接受、自動零延伸或截斷,然後變成一個三天後才在波形上發現的功能臭蟲。Verilog 的「少儀式」既是真正的生產力優勢,也是真正的危險;前段流程裡的 lint 工具之所以存在,很大程度上就是為了把語言不肯做的檢查補回來。正直なまとめはスライドの通りである。両者は同程度に有効で、選択は好みか雇用主の方針による。ただし Verilog の初学者には一つの非対称が重要だ。VHDL の強い型付けは、4 ビット信号を 8 ビットのポートにつなぐ、整数をビットベクタに代入する、といった広い範囲の誤りをコンパイル時に拒否する。Verilog はそれを黙って受け入れ、ゼロ拡張または切り詰め、三日後に波形の中で見つかる機能バグに変える。Verilog の儀式の少なさは本物の生産性であると同時に本物の危険であり、フロントエンドの lint ツールは、言語がやらないことにした検査を取り戻すために存在していると言ってよい。
The conclusion slide calls Verilog "a platform", and that is the right word, because the same file format plays three quite different roles.總結那頁投影片把 Verilog 稱為「一個平台」,這個詞用得對,因為同一種檔案格式扮演三種相當不同的角色。まとめのスライドは Verilog を「プラットフォーム」と呼ぶ。同じファイル形式がかなり異なる三つの役割を演じるのだから、これは適切な語である。
| Role角色役割 | Who reads it誰會讀它誰が読むか | Constraints限制制約 |
|---|---|---|
| Design (RTL)設計(RTL)設計(RTL) | Simulator and synthesiser模擬器與合成器シミュレータと合成器 | Restricted to the synthesisable subset; must infer only cells the library has僅限可合成子集;只能推斷出函式庫裡真的有的 cell合成可能サブセットに限られる。ライブラリに存在するセルしか推論してはならない |
| Testbench測試平台テストベンチ | Simulator only只有模擬器シミュレータのみ | No constraints at all — #delays, $display, file I/O, real, initial, everything is allowed完全沒有限制 — #delays、$display、檔案 I/O、real、initial,什麼都可以制約は一切ない。#delays、$display、ファイル I/O、real、initial、何でも許される |
| Netlist網表ネットリスト | Simulator, STA, APR, formal equivalence模擬器、STA、APR、形式等效性檢查シミュレータ、STA、APR、形式的等価性検証 | Pure structure: instances of library cells and nets, written by the tool, read by you only when debugging純結構:函式庫 cell 的實例與 net,由工具寫出,只有除錯時你才會去讀純粋な構造。ライブラリセルのインスタンスとネットのみ。ツールが書き、君が読むのはデバッグのときだけ |
Confusing these roles is the commonest novice error in the course. Code that works beautifully in simulation but contains #5 or initial will either be ignored by the synthesiser (producing a netlist whose behaviour differs from your simulation) or rejected outright. Code written with synthesis in mind but used in a testbench is merely tedious. The badge not synthesisable appears throughout these guides on constructs that belong strictly to the testbench column.把這三種角色搞混,是本課程最常見的新手錯誤。在模擬裡跑得漂漂亮亮、卻含有 #5 或 initial 的程式碼,要不是被合成器忽略(產出一份行為與你的模擬不同的網表),就是被直接拒收。反過來說,抱著合成心態寫出來卻用在測試平台裡的程式碼,頂多只是囉嗦。本系列指南中凡是嚴格屬於測試平台那一欄的語法,都會掛上 不可合成 的標記。この三つの役割を混同するのが、本科目で最も多い初学者の誤りである。シミュレーションでは見事に動くが #5 や initial を含むコードは、合成器に無視される(君のシミュレーションとは振る舞いの違うネットリストが出る)か、そのまま拒否される。逆に合成を意識して書いたコードをテストベンチで使うのは、単に冗長なだけだ。本ガイドでは、テストベンチの列にだけ属する構文に 合成不可 のバッジを付けている。
IEEE 1364 defines what a simulator must do with every construct. It says nothing about what a synthesiser must do, because a synthesiser is not obliged to exist. The synthesisable subset is defined separately (IEEE 1364.1) and, in practice, by what Design Compiler accepts. The boundary is not arbitrary: a construct is synthesisable if and only if its meaning can be realised by a fixed network of library cells whose behaviour does not depend on wall-clock time.IEEE 1364 定義的是模擬器對每一種語法必須做什麼。它完全沒說合成器必須做什麼,因為合成器沒有存在的義務。可合成子集是另外定義的(IEEE 1364.1),而實務上是由 Design Compiler 接受什麼來定義的。這條界線並不隨意:一個語法可合成,若且唯若它的意義能用一張固定的函式庫 cell 網路實現,而那張網路的行為不依賴真實世界的時鐘。IEEE 1364 が定めるのは、各構文に対してシミュレータが何をすべきかである。合成器が何をすべきかは何も述べていない。合成器には存在する義務がないからだ。合成可能サブセットは別に(IEEE 1364.1 として)定義され、実務上は Design Compiler が受理するものによって定義される。この境界は恣意的ではない。ある構文が合成可能であるのは、その意味が、実時間に依存しない振る舞いを持つライブラリセルの固定したネットワークで実現できるとき、かつそのときに限る。
assign y = sel ? a : b;
always @(posedge clk)
q <= d;
always @(*)
case (op)
2'b00: r = a + b;
2'b01: r = a - b;
default: r = a;
endcase
for (k = 0; k < 8; k = k + 1)
s[k] = a[k] ^ b[k]; // unrolled#5 y = a; // delay control initial q = 0; // power-on value wait (ready == 1); // level-sensitive wait forever #10 clk = ~clk; real gain; // floating point $display("q = %b", q); // system task while (x != 0) // bound unknown at x = x - 1; // elaboration time
Look at the right-hand column and ask, for each line, "what cell would this be?". There is no cell that waits five nanoseconds and then copies a value; delay in real hardware is a consequence of the gates you chose, not something you request. There is no cell that sets itself to 0 at power-on unless it is a flip-flop with a reset pin — which is why reset is a port, written explicitly, rather than an initial block. There is no cell that loops an unknown number of times; a for loop is synthesisable only when the tool can unroll it at elaboration time into a fixed amount of hardware, which requires constant bounds.看著右邊那一欄,對每一行問自己:「這會是哪一顆 cell?」沒有哪顆 cell 會等五奈秒再複製一個值;真實硬體裡的延遲是你所選的閘造成的後果,不是你可以點菜的東西。也沒有哪顆 cell 會在上電時把自己設成 0,除非它是一顆帶 reset 腳的正反器 — 這正是為什麼 reset 是一個明確寫出來的埠,而不是一個 initial 區塊。更沒有哪顆 cell 會迴圈不確定的次數;for 迴圈只有在工具能於展開期把它攤成固定數量的硬體時才可合成,而那需要常數的上下界。右の列を見て、各行について「これはどのセルになるのか」と自問せよ。五ナノ秒待ってから値を写すセルなど存在しない。実在のハードウェアにおける遅延は、選んだゲートの帰結であって、注文できるものではない。電源投入時に自分を 0 にするセルも存在しない——リセットピンを持つフリップフロップでない限りは。だからこそリセットは initial ブロックではなく、明示的に書かれたポートなのである。不定回数ループするセルも存在しない。for ループが合成可能なのは、ツールがエラボレーション時にそれを固定量のハードウェアへ展開できるときだけであり、そのためには境界が定数である必要がある。
Most synthesisers do not reject delays — they ignore them, with a warning you will not read. So always @(posedge clk) q <= #3 d; simulates with a 3-unit delay and synthesises without one. If you used that delay to hide a race in your testbench, the race is still there in the netlist. The symptom at post-synthesis gate-level simulation is a signal that settles one delta-cycle differently from the RTL run, and a mismatch report that points at the flip-flop rather than at the #3. Treat every "delay ignored" warning as an error.大多數合成器並不拒收延遲 — 它們忽略延遲,並附上一行你不會去讀的警告。所以 always @(posedge clk) q <= #3 d; 在模擬時帶著 3 個時間單位的延遲,合成後卻一點延遲也沒有。如果你是靠那個延遲來遮住測試平台裡的一個競爭,那個競爭在網表裡依然健在。它在合成後閘級模擬中的症狀,是某條訊號比 RTL 版本晚一個 delta cycle 才穩定,而不符報告指的是那顆正反器,不是那個 #3。請把每一條「delay ignored」的警告當成錯誤看待。多くの合成器は遅延を拒否しない。無視する。しかも君が読まない警告を一行出すだけだ。したがって always @(posedge clk) q <= #3 d; はシミュレーションでは 3 単位の遅延つき、合成後は遅延なしになる。その遅延でテストベンチの競合を隠していたのなら、競合はネットリストの中にそのまま残っている。合成後のゲートレベル・シミュレーションでの症状は、ある信号が RTL 実行とデルタサイクル一つ分だけ違って落ち着くことであり、ミスマッチ報告は #3 ではなくフリップフロップを指す。「delay ignored」という警告はすべてエラーとして扱え。
Problem: A student writes the module on the left and complains that y is always x in simulation. Explain what happened, and give the two ways to fix it.題目:某學生寫了左邊這個模組,抱怨模擬中 y 永遠是 x。請解釋發生了什麼事,並給出兩種修法。問題:ある学生が左のモジュールを書き、シミュレーションで y がずっと x のままだと訴えている。何が起きたかを説明し、二通りの直し方を示せ。
module bad_order (y, a, b, d); input a, b, d; output y; reg t, y; always @(a or b) t = a | b; always @(t or d) y = t & d; endmodule
Nothing is wrong with the ordering — the two always blocks are concurrent and the second is correctly sensitive to t, so a change on a updates t, which wakes the second block, which updates y. If y is permanently x the cause is elsewhere: neither block has ever executed. An always @(list) block only runs when something in its list changes. If the testbench drives a, b and d with their initial values and never changes them, no event is ever scheduled, t and y keep their power-on value x, and the waveform is a flat red line from time 0.順序沒有任何問題 — 兩個 always 區塊是並行的,第二個也正確地對 t 敏感,所以 a 一變就更新 t,t 一變就喚醒第二個區塊,進而更新 y。如果 y 永遠是 x,原因在別處:這兩個區塊從來沒有執行過。always @(list) 區塊只有在清單裡某個東西改變時才會跑。如果測試平台只是把 a、b、d 設成初始值而從不改動它們,就永遠不會排程任何事件,t 和 y 一直保持上電值 x,波形從時間 0 起就是一條平平的紅線。順序には何の問題もない。二つの always ブロックは並行であり、二つ目は正しく t に反応するので、a が変われば t が更新され、t が変われば二つ目のブロックが起きて y を更新する。y が永久に x なら原因は別にある。どちらのブロックも一度も実行されていないのだ。always @(list) ブロックはリスト中の何かが変化したときにしか走らない。テストベンチが a、b、d を初期値のまま置いて一度も変えなければ、イベントは一つもスケジュールされず、t と y は電源投入時の値 x のままで、波形は時刻 0 から平らな赤い線になる。
Fix 1 (testbench): make the stimulus actually transition — drive the inputs from x to a defined value at t = 0 and then change them. This is the correct fix when the RTL is a true combinational block, because the real gates in silicon also only change when their inputs change.修法 1(測試平台):讓刺激真的發生轉態 — 在 t = 0 把輸入從 x 驅動到一個確定值,之後再改變它們。當 RTL 真的是一個組合區塊時,這是正確的修法,因為矽上的真實閘也只有在輸入改變時才會改變。直し方 1(テストベンチ):刺激に本当に遷移を起こさせる。t = 0 で入力を x から確定値へ駆動し、その後変化させる。RTL が本当に組合せブロックであるなら、これが正しい直し方である。シリコン上の実際のゲートもまた、入力が変わるときにしか変わらないからだ。
Fix 2 (RTL, and the better habit): use continuous assignments, assign t = a | b; assign y = t & d;. Continuous assignments are evaluated once at time 0 regardless of whether their operands have changed, so y is defined from the start. More generally, do not model combinational logic with always blocks and hand-written sensitivity lists when an assign will do; see Section 5.修法 2(RTL,也是比較好的習慣):改用連續指定,assign t = a | b; assign y = t & d;。連續指定在時間 0 就會被求值一次,不管運算元有沒有改變過,所以 y 從一開始就是有定義的。更一般地說:當一條 assign 就夠用時,不要用 always 區塊加手寫敏感清單去描述組合邏輯;見第 5 節。直し方 2(RTL。こちらが良い習慣):連続代入を使う。assign t = a | b; assign y = t & d;。連続代入はオペランドが変化したかどうかに関係なく時刻 0 で一度評価されるので、y は最初から定義される。より一般には、assign で済むところを always ブロックと手書きの感度リストで組合せ論理を書いてはならない。第 5 節を参照。
Related trap: if the student had written always @(t) y = t & d; — leaving d out of the list — simulation would show y failing to follow d, while synthesis would ignore the incomplete list and build a plain AND gate. That is a genuine simulation/synthesis mismatch and the reason Lecture 5 insists on always @(*).相關陷阱:如果那位學生寫的是 always @(t) y = t & d; — 把 d 漏在清單外 — 模擬會顯示 y 不跟著 d 走,而合成卻會忽略這份不完整的清單,直接搭出一顆單純的 AND 閘。那就是一個如假包換的模擬/合成不一致,也是第 5 講堅持要寫 always @(*) 的理由。関連する罠:もしその学生が always @(t) y = t & d; と書いて d をリストから落としていたら、シミュレーションでは y が d に追従しない一方、合成は不完全なリストを無視してただの AND ゲートを作る。これこそ正真正銘のシミュレーション/合成ミスマッチであり、第 5 講が always @(*) を強く求める理由である。
Verilog has exactly one unit of design: the module, delimited by the keywords module and endmodule. Everything else — gates, assignments, procedural blocks, instances of other modules — is a module item that lives inside one. Three rules follow, and the slides state all three.Verilog 只有一種設計單位:模組(module),由關鍵字 module 與 endmodule 界定。其他一切 — 閘、指定、程序區塊、其他模組的實例 — 都是住在某個模組裡的模組項目(module item)。由此推出三條規則,投影片三條都講了。Verilog の設計単位はただ一つ、キーワード module と endmodule で区切られるモジュールである。それ以外のすべて——ゲート、代入、手続きブロック、他モジュールのインスタンス——は、そのいずれかの内側に住むモジュール項目である。ここから三つの規則が導かれ、スライドはその三つすべてを述べている。
module Add_half (…); … endmodule defines a type, much like a class definition. It does not by itself put any hardware in your chip.模組靠宣告而誕生。寫下 module Add_half (…); … endmodule 定義的是一個型別,很像類別定義。它本身並不會在你的晶片裡放進任何硬體。モジュールは宣言によって生まれる。module Add_half (…); … endmodule と書くことは、クラス定義によく似た型の定義である。それだけではチップに何のハードウェアも置かれない。module declaration inside another module. Hierarchy is built by instantiating one module inside another, which is a different act — it is the difference between defining a type and allocating an object of that type. This is Section 7.模組不能巢狀。你不可以在一個 module 裡面再寫一個 module 宣告。階層是靠在一個模組裡實例化另一個模組建立起來的,那是另一回事 — 就像定義一個型別和配置一個該型別的物件之間的差別。這是第 7 節的內容。モジュールは入れ子にできない。module 宣言を別の module の内側に書くことはできない。階層は、あるモジュールの中で別のモジュールをインスタンス化することで作られる。これは別の行為であり、型を定義することと、その型のオブジェクトを確保することの違いに当たる。第 7 節の話である。The last rule has a corollary worth stating explicitly: the top module of a design is simply the module that nothing else instantiates. The simulator finds it by elaboration, not by a keyword. If you accidentally leave two modules un-instantiated you get two top modules and two independent circuits, both simulating, usually with one of them producing nothing but x.最後這條規則有一個值得明講的推論:一個設計的頂層模組,不過就是沒有被任何其他模組實例化的那個模組。模擬器是靠展開(elaboration)找到它的,不是靠某個關鍵字。如果你不小心讓兩個模組都沒被實例化,你就會得到兩個頂層模組、兩個互不相干的電路同時在模擬,而且通常其中一個只會吐出 x。最後の規則には明言しておく価値のある系がある。設計のトップモジュールとは、他のどこからもインスタンス化されていないモジュールにすぎない。シミュレータはそれをキーワードではなくエラボレーションによって見つける。うっかり二つのモジュールをインスタンス化し忘れれば、トップモジュールが二つ、互いに独立な回路が二つ同時にシミュレーションされ、たいていは片方が x しか出さない。
A module's ports are listed in parentheses after its name and their direction is declared inside the body with input, output or inout. This is the Verilog-1995 style used throughout these slides:一個模組的埠列在名稱後面的括號裡,方向則在本體內以 input、output 或 inout 宣告。這是本系列投影片通篇採用的 Verilog-1995 風格:モジュールのポートは名前の後の括弧に並べ、方向は本体の中で input、output、inout によって宣言する。これはスライド全体で用いられる Verilog-1995 のスタイルである。
module Add_half (sum, c_out, a, b); // port list: names only input a, b; // port modes declared separately output sum, c_out; wire c_out_bar; // internal signal // ... body ... endmodule
| Mode方向モード | Direction流向向き | Inside the module it is…在模組內部它是…モジュール内部では… | Outside it must be connected to…在模組外部必須接到…モジュール外部で接続すべきもの… |
|---|---|---|---|
inputinputinput | Driven from outside由外部驅動外から駆動される | a net (always); read-only inside一定是 net;內部只能讀常にネット。内部では読み取り専用 | a net or a reg (anything that can drive)net 或 reg(任何能驅動的東西)ネットまたは reg(駆動できるものなら何でも) |
outputoutputoutput | Driven from inside由內部驅動内から駆動する | a net by default; may be re-declared reg to be driven procedurally預設是 net;可以再宣告成 reg 以便用程序敘述驅動既定ではネット。手続き的に駆動するため reg と再宣言してもよい | a net only — never a reg只能是 net — 絕不可是 regネットのみ。決して reg であってはならない |
inoutinoutinout | Bidirectional雙向双方向 | must be a net; must be driven by a tri-state driver必須是 net;必須由三態驅動器驅動ネットでなければならず、トライステート・ドライバで駆動する必要がある | a net only只能是 netネットのみ |
Inside a module, an output may be a reg. Outside, the signal connected to an output port must be a net. The mirror rule applies to the testbench: the signals you connect to the DUT's input ports must be reg (because your initial block assigns them procedurally), and the signals you connect to its output ports must be wire. Getting this backwards gives "illegal left-hand side" or "output port connected to a register" errors that the compiler reports at the instantiation line, not at the declaration, which is why they look mysterious.在模組內部,output 可以是 reg。在模組外部,接到 output 埠的訊號必須是 net。對測試平台則適用鏡像規則:你接到待測物 input 埠的訊號必須是 reg(因為你的 initial 區塊是用程序敘述指定它們),而接到 output 埠的訊號必須是 wire。把這件事弄反,會得到「illegal left-hand side」或「output port connected to a register」這類錯誤,而且編譯器報的是實例化那一行、不是宣告那一行,這就是它們看起來很神祕的原因。モジュールの内側では output が reg であってもよい。外側では、output ポートにつなぐ信号は ネットでなければならない。テストベンチには鏡像の規則が当てはまる。DUT の input ポートにつなぐ信号は reg でなければならず(initial ブロックが手続き的に代入するからだ)、output ポートにつなぐ信号は wire でなければならない。これを逆にすると「illegal left-hand side」や「output port connected to a register」というエラーになる。しかもコンパイラは宣言行ではなくインスタンス化の行を指すので、原因が謎めいて見える。
Verilog-2001 added the ANSI-style header, in which direction, type and width are all written once in the port list: module Add_half (output sum, output c_out, input a, input b);. It is shorter and removes a whole class of typo. This course's slides use the 1995 style, and you should be fluent in reading it because every legacy IP core and every synthesised netlist you ever open is written that way. Write new code in whichever style your group has standardised on, but never mix the two within one module — that is a syntax error.Verilog-2001 加入了 ANSI 風格的標頭,方向、型別與位寬全部在埠列裡寫一次:module Add_half (output sum, output c_out, input a, input b);。它比較短,也消滅了一整類筆誤。這門課的投影片用的是 1995 風格,而你應該要能流利地閱讀它,因為你將來打開的每一個舊 IP 核、每一份合成出來的網表,都是那樣寫的。新程式碼用你們團隊標準化的那種風格即可,但千萬別在同一個模組裡混用兩種 — 那是語法錯誤。Verilog-2001 は ANSI 形式のヘッダを追加した。方向・型・幅をすべてポートリストに一度だけ書く形式である。module Add_half (output sum, output c_out, input a, input b);。短く、まるごと一種類の誤記を消し去る。本科目のスライドは 1995 形式を使っており、君はそれを読みこなせる必要がある。これから開くレガシー IP コアも合成済みネットリストも、すべてその形式で書かれているからだ。新しいコードは所属グループが標準としている形式で書けばよいが、一つのモジュール内で両者を混ぜてはならない。それは構文エラーである。
A net models a physical connection. The defining property, in the slides' words, is that a net "doesn't store value, just a connection". It has no memory. At every instant its value is whatever its driver or drivers are forcing onto it, and if nothing drives it, it is z — floating — not 0 and not its last value.net 模擬的是一條實體連接。用投影片的話說,它的定義性質是 net「不儲存值,只是一條連接」。它沒有記憶。在任何一瞬間,它的值就是它的驅動器(可能不只一個)正在逼它成為的值;如果沒有任何東西驅動它,它就是 z — 浮接 — 既不是 0,也不是它上一刻的值。ネットは物理的な接続をモデル化する。スライドの言葉を借りれば、定義的な性質はネットが「値を保持せず、接続にすぎない」ことである。記憶を持たない。任意の瞬間におけるその値は、ドライバ(複数のこともある)が押し付けている値そのものであり、何も駆動していなければ値は z——フローティング——であって、0 でもなければ直前の値でもない。
The net types are wire, wand, wor, tri, triand, trior, tri0, tri1, trireg, supply0 and supply1. Ninety-nine per cent of synthesisable RTL uses only wire. The others exist to describe what happens when a net has more than one driver:net 的型別有 wire、wand、wor、tri、triand、trior、tri0、tri1、trireg、supply0 與 supply1。可合成 RTL 有百分之九十九只用 wire。其餘那些的存在,是為了描述當一條 net 有多於一個驅動器時會發生什麼事:ネット型には wire、wand、wor、tri、triand、trior、tri0、tri1、trireg、supply0、supply1 がある。合成可能な RTL の九十九パーセントは wire しか使わない。残りは、ネットに複数のドライバがあるとき何が起こるかを記述するために存在する。
| Net typenet 型別ネット型 | Resolution when several drivers disagree多個驅動器不一致時如何裁決複数ドライバが食い違うときの解決 | Physical analogue實體對應物物理的な対応物 |
|---|---|---|
wire / triwire/triwire/tri | Conflict → x (unless one driver is z, in which case the other wins)衝突 → x(除非其中一個驅動器是 z,那就由另一個勝出)衝突は x(片方のドライバが z なら他方が勝つ) | Two gate outputs shorted together — a real short circuit兩個閘的輸出短接在一起 — 一個真正的短路二つのゲート出力の短絡。文字どおりのショート |
wand / triandwand/triandwand/triand | Wired-AND: any 0 wins線接 AND:任何一個 0 勝出ワイヤード AND。0 が一つでもあれば 0 | Open-drain outputs with a pull-up開汲極輸出加提升電阻オープンドレイン出力とプルアップ |
wor / triorwor/triorwor/trior | Wired-OR: any 1 wins線接 OR:任何一個 1 勝出ワイヤード OR。1 が一つでもあれば 1 | Open-source / emitter-follower bus開源極/射極隨耦器匯流排オープンソース/エミッタフォロワ・バス |
supply0 / supply1supply0/supply1supply0/supply1 | Permanently 0 / 1 with supply strength恆為 0/1,且具電源級驅動強度常に 0/1。電源強度を持つ | A tie to ground or VDD直接接到地或 VDDグラウンドまたは VDD への固定接続 |
tri0 / tri1tri0/tri1tri0/tri1 | z resolves to 0 / 1z 被解析成 0/1z が 0/1 に解決される | A net with a weak pull-down / pull-up resistor帶弱下拉/弱上拉電阻的 net弱いプルダウン/プルアップ抵抗を持つネット |
The slides' three operational rules for nets are:投影片給 net 的三條操作規則是:スライドが挙げるネットの運用規則は三つである。
assign. A net's value comes from a continuous assignment, from a gate primitive output, or from a module instance output port.用 assign 驅動。net 的值來自連續指定、來自閘基元的輸出,或來自模組實例的輸出埠。assign で駆動する。ネットの値は連続代入、ゲートプリミティブの出力、またはモジュールインスタンスの出力ポートから来る。wire. If you declare input a; and nothing else, a is a 1-bit wire. You rarely need to write wire a; as well — the slide's Add_rca_16 example does redeclare its ports as wires, which is legal but redundant.埠預設就是 wire。如果你只宣告 input a; 而沒寫別的,a 就是一條 1 位元的 wire。你很少需要再寫一次 wire a; — 投影片的 Add_rca_16 範例確實把埠又宣告成 wire 一次,那是合法的,但多餘。ポートの既定は wire。input a; とだけ宣言すれば、a は 1 ビットの wire である。重ねて wire a; と書く必要はまずない。スライドの Add_rca_16 の例はポートを wire として再宣言しているが、これは合法でありながら冗長である。always block. This is the rule students hit first and understand last. See §3.5.net 永遠不能成為 always 區塊裡指定敘述的目標。這是學生最早撞到、卻最晚真正理解的一條規則。見 §3.5。ネットは always ブロック内の代入の左辺には決してなれない。学生が最初にぶつかり、最後まで腑に落ちない規則である。§3.5 を参照。A variable — Verilog-1995 calls it a register, and the keyword is reg — does store a value. It holds whatever was last procedurally assigned to it, until something assigns again. The register data types are reg, integer, time and real.變數 — Verilog-1995 稱它為 register,關鍵字是 reg — 是真的會儲存值的。它保有最後一次被程序指定的內容,直到下一次有東西再指定它為止。暫存器類資料型別有 reg、integer、time 與 real。変数——Verilog-1995 はこれをレジスタと呼び、キーワードは reg である——は実際に値を保持する。最後に手続き的に代入されたものを、次の代入まで保ち続ける。レジスタ系のデータ型は reg、integer、time、real である。
| Type型別型 | Width / content位寬/內容幅/内容 | Synthesisable?可合成?合成可能か | Typical use典型用途典型的な用途 |
|---|---|---|---|
regregreg | 1 bit, or a declared range; four-valued1 位元,或宣告的範圍;四值1 ビット、または宣言された範囲。四値 | Yes可以可 | Everything in RTL that is assigned procedurallyRTL 中所有以程序敘述指定的東西RTL で手続き的に代入されるものすべて |
integerintegerinteger | At least 32 bits, signed, two-valued in arithmetic至少 32 位元、有號,算術時為二值最低 32 ビット、符号付き。算術では二値 | Loop counters only (unrolled away)僅限迴圈計數器(會被展開掉)ループカウンタのみ(展開されて消える) | for loop indices, testbench countersfor 迴圈索引、測試平台的計數器for ループの添字、テストベンチのカウンタ |
timetimetime | At least 64 bits, unsigned至少 64 位元、無號最低 64 ビット、符号なし | No testbench only不可 僅限測試平台不可 テストベンチ専用 | Capturing $time for timing checks擷取 $time 做時序檢查タイミング検査のため $time を取り込む |
realrealreal | Double-precision floating point倍精度浮點數倍精度浮動小数点 | No testbench only不可 僅限測試平台不可 テストベンチ専用 | Reference models, computing ideal values to compare against參考模型,算出理想值以供比對参照モデル。比較用の理想値を計算する |
The slide's side-by-side comparison is exactly the right picture to memorise:投影片那張並排比較,正是最該背起來的一幅圖:スライドの並列比較は、まさに記憶に焼き付けるべき図である。
module test (a, b, c, d); input a, b; output c, d; wire c, d; assign c = a | b; assign d = a & b; endmodule
module test (a, b, c, d); input a, b; output c, d; reg c, d; always @ (a or b) begin // sensitivity list c = a | b; d = a & b; end endmodule
These two modules produce identical hardware: one OR gate and one AND gate. Not similar hardware — identical. Run both through Design Compiler and the netlists are the same. The difference is purely in how you asked for it: the left-hand module declares a structural binding, the right-hand module describes a procedure that recomputes the outputs whenever an input changes. The synthesiser reduces both to the same truth table.這兩個模組產生完全相同的硬體:一顆 OR 閘和一顆 AND 閘。不是相似的硬體,是相同的。把兩者都丟進 Design Compiler,網表一模一樣。差別純粹在於你是怎麼要求的:左邊的模組宣告了一組結構性繫結,右邊的模組描述了一段每當輸入改變就重算輸出的程序。合成器把兩者都化簡成同一張真值表。この二つのモジュールはまったく同一のハードウェアを生む。OR ゲート一つと AND ゲート一つ。似たハードウェアではなく、同一である。両方を Design Compiler に通せばネットリストは一致する。違いはひとえに要求の仕方にある。左のモジュールは構造的な束縛を宣言し、右のモジュールは入力が変わるたびに出力を再計算する手続きを記述している。合成器はどちらも同じ真理値表へ還元する。
reg is not a register3.5 reg 並不是正反器3.5 reg はレジスタではないreg does not mean "flip-flop". It means "a variable that holds its value in the simulator between assignments". Whether it becomes a flip-flop in silicon depends entirely on whether your code requires it to remember something across a clock edge. In the right-hand module above, c and d are reg and synthesise to plain combinational gates, because every time the block runs it assigns both of them from current input values — nothing is remembered.reg 不等於「正反器」。它的意思是「一個在模擬器中、於兩次指定之間保有其值的變數」。它在矽上會不會變成一顆正反器,完全取決於你的程式碼是否要求它跨越一個時脈邊緣記住某件事。在上面右邊那個模組裡,c 和 d 是 reg,卻合成成單純的組合閘,因為那個區塊每次執行時都會用當下的輸入值把兩者都重新指定一次 — 沒有任何東西被記住。reg は「フリップフロップ」を意味しない。「代入と代入の間、シミュレータ内で値を保つ変数」を意味する。それがシリコン上でフリップフロップになるかどうかは、ひとえにそのコードがクロックエッジをまたいで何かを覚えることを要求しているかで決まる。上の右のモジュールでは c と d は reg だが、合成結果はただの組合せゲートである。ブロックが走るたびに現在の入力値から両方を代入し直しており、何も記憶していないからだ。
The slide's own wording — "use of reg data type is not exactly synthesized to a really register" — says this, somewhat telegraphically. Here is the rule stated the way synthesis actually applies it.投影片自己的說法 — use of reg data type is not exactly synthesized to a really register — 講的就是這件事,只是講得有點像電報。底下是這條規則按合成實際適用的方式重新陳述一次。スライド自身の文言——use of reg data type is not exactly synthesized to a really register——はこのことを、やや電文調に述べている。以下は、この規則を合成が実際に適用する形で言い直したものである。
The tool elaborates each always block into a truth table of "what does this block assign, as a function of what it reads". Then:工具把每個 always 區塊展開成一張真值表:「這個區塊指定了什麼,作為它所讀到的東西的函數」。然後:ツールは各 always ブロックを「このブロックは、読んだ値の関数として何を代入するか」という真理値表へ展開する。そのうえで、
posedge or negedge of a signal, every variable assigned in it becomes an edge-triggered flip-flop clocked by that signal.如果這個區塊對某個訊號的 posedge 或 negedge 敏感,區塊中被指定的每一個變數都會變成由該訊號驅動時脈的邊緣觸發正反器。ブロックがある信号の posedge または negedge に反応するなら、その中で代入される変数はすべて、その信号をクロックとするエッジトリガ・フリップフロップになる。Note what is absent from that list: the word reg. The keyword plays no part in the decision.注意這份清單裡缺了什麼:reg 這個字。這個關鍵字在這個決定裡沒有任何戲份。この一覧に欠けているものに注意せよ。reg という語である。このキーワードは判断に一切関与しない。
Why does the language insist on the distinction at all, then? Because of how the simulator is built. A continuous assignment and a gate are drivers: the simulator keeps, for each net, a list of drivers and a resolution function, and recomputes the net whenever any driver changes. A procedural assignment is a store: it writes a value into a variable's storage location and that value sits there. These are two different data structures inside the simulator, and a signal has to be one or the other. Hence: nets are driven by assign and by instance outputs; variables are written by procedural statements inside always, initial, tasks and functions. Mixing them is not a style preference, it is a type error.那麼語言為何還堅持要有這個區分?因為模擬器是這樣造出來的。連續指定和閘都是驅動器:模擬器為每一條 net 保存一份驅動器清單與一個裁決函數,只要任一驅動器改變就重算這條 net。程序指定則是一次儲存:它把一個值寫進變數的儲存位置,那個值就待在那裡。這是模擬器內部兩種不同的資料結構,而一個訊號只能是其中之一。因此:net 由 assign 和實例輸出驅動;變數由 always、initial、task 和 function 裡的程序敘述寫入。把兩者混用不是風格偏好,是型別錯誤。では、なぜ言語はこの区別に固執するのか。シミュレータの作りがそうなっているからである。連続代入とゲートはドライバである。シミュレータは各ネットについてドライバのリストと解決関数を保持し、いずれかのドライバが変わるたびにネットを再計算する。手続き的代入は記憶である。変数の記憶場所へ値を書き込み、その値はそこに居座る。これはシミュレータ内部の二つの異なるデータ構造であり、信号はどちらか一方でしかありえない。ゆえに、ネットは assign とインスタンス出力が駆動し、変数は always、initial、タスク、ファンクション内の手続き文が書き込む。両者を混ぜるのはスタイルの好みではなく、型エラーである。
module m (y, a, b); input a, b; output y; wire y; always @(a or b) y = a & b; // ERROR: illegal // left-hand side endmodule
module m (y, a, b); input a, b; output y; reg y; assign y = a & b; // ERROR: cannot // continuously assign // to a variable endmodule
Both of these are compile-time errors, which is the good case. The bad case is subtler: a signal that you meant to drive but forgot to, which stays at z and then makes everything downstream x. Section 4.3 traces how that looks in a waveform.這兩種都是編譯期錯誤,而那是好的情況。糟糕的情況更隱微:一條你打算驅動卻忘了驅動的訊號,它停在 z,然後把下游一切變成 x。4.3 節會追蹤那在波形上長什麼樣子。どちらもコンパイル時エラーであり、それは良いほうの事例である。悪いほうはもっと微妙だ。駆動するつもりだったのに忘れた信号は z のまま留まり、下流のすべてを x にする。それが波形でどう見えるかは 4.3 節で追う。
wire or reg?練習 4 — 該用 wire 還是 reg?練習 4 — wire か reg かProblem: For each signal, say whether it must be declared wire, must be declared reg, or either works. (a) An input port of a design module. (b) An output port assigned inside always @(posedge clk). (c) An output port driven by assign. (d) In a testbench, the signal connected to the DUT's clk input and toggled by always #5 clk = ~clk;. (e) In a testbench, the signal connected to the DUT's data_out. (f) An internal signal that is the output of an instantiated submodule. (g) A loop index in a synthesisable for.題目:對下列每個訊號,說明它必須宣告成 wire、必須宣告成 reg,還是兩者皆可。(a) 設計模組的一個 input 埠。(b) 在 always @(posedge clk) 裡被指定的 output 埠。(c) 由 assign 驅動的 output 埠。(d) 在測試平台中,接到待測物 clk 輸入、並由 always #5 clk = ~clk; 翻轉的那條訊號。(e) 在測試平台中,接到待測物 data_out 的那條訊號。(f) 某個內部訊號,它是被實例化的子模組的輸出。(g) 可合成 for 迴圈的索引。問題:次の各信号について、wire と宣言しなければならないか、reg と宣言しなければならないか、どちらでもよいかを答えよ。(a) 設計モジュールの input ポート。(b) always @(posedge clk) の中で代入される output ポート。(c) assign で駆動される output ポート。(d) テストベンチで DUT の clk 入力につながれ、always #5 clk = ~clk; でトグルされる信号。(e) テストベンチで DUT の data_out につながれる信号。(f) インスタンス化した下位モジュールの出力である内部信号。(g) 合成可能な for ループの添字。
(a) wire — and it is the default, so you normally write nothing. An input can never be a reg; it is driven from outside.(a) wire — 而且這是預設值,所以通常你什麼都不必寫。input 永遠不可能是 reg;它是由外部驅動的。(a) wire。しかも既定なので、ふつうは何も書かない。input が reg になることはありえない。外から駆動されるからだ。
(b) reg — it is the target of a procedural assignment. (In Verilog-2001 ANSI style you write output reg q.)(b) reg — 它是程序指定的目標。(在 Verilog-2001 的 ANSI 風格中寫成 output reg q。)(b) reg。手続き的代入の左辺だからである。(Verilog-2001 の ANSI 形式では output reg q と書く。)
(c) wire — default, nothing to write.(c) wire — 預設值,什麼都不必寫。(c) wire。既定なので書くものはない。
(d) reg — the testbench assigns it procedurally. This is the classic case where reg models something that is emphatically not a flip-flop.(d) reg — 測試平台是以程序敘述指定它的。這正是 reg 描述的東西絕對不是正反器的經典案例。(d) reg。テストベンチが手続き的に代入するからである。reg がフリップフロップでは断じてないものをモデル化する典型例である。
(e) wire — it is driven by the DUT's output port. Declaring it reg is an error at the instantiation.(e) wire — 它是由待測物的輸出埠驅動的。把它宣告成 reg 會在實例化處產生錯誤。(e) wire。DUT の出力ポートが駆動する。reg と宣言すればインスタンス化の箇所でエラーになる。
(f) wire — a module instance output is a driver, so the signal it drives must be a net.(f) wire — 模組實例的輸出是一個驅動器,所以它驅動的訊號必須是 net。(f) wire。モジュールインスタンスの出力はドライバなので、それが駆動する信号はネットでなければならない。
(g) integer (a variable type). A reg of sufficient width also works but integer is idiomatic. The loop is unrolled at elaboration, so the index generates no hardware at all.(g) integer(一種變數型別)。位寬足夠的 reg 也行,但 integer 才是慣用寫法。這個迴圈在展開期就被攤開,所以索引完全不會產生任何硬體。(g) integer(変数型の一つ)。十分な幅の reg でも動くが、慣用は integer である。ループはエラボレーションで展開されるので、添字はハードウェアを一切生まない。
Problem: What is y in each case, at the point where both statements are active?題目:在兩條敘述都生效的時點上,下列各情況中 y 是什麼?問題:二つの文がともに有効な時点で、各場合の y は何になるか。
// case (a) wire y; assign y = 1'b1; assign y = 1'b0; // case (b) wire y; assign y = 1'b1; assign y = 1'bz; // case (c) wand y; assign y = 1'b1; assign y = 1'b0; // case (d) reg y; always @(a) y = 1'b1; always @(b) y = 1'b0;
(a) y = x. Two equal-strength drivers in conflict on a wire resolve to unknown. In silicon this is a short from VDD to ground through two output stages — the kind of mistake that heats a package. Synthesis flags it as a multiple-driver error.(a) y = x。在一條 wire 上,兩個等強度的驅動器互相衝突,裁決結果是未知。在矽上這是一條穿過兩級輸出電路、從 VDD 直通到地的短路 — 會把封裝加熱的那種錯誤。合成會把它標成多重驅動錯誤。(a) y = x。wire 上で同じ強度の二つのドライバが衝突すれば、解決結果は不定である。シリコン上では、これは二つの出力段を通って VDD からグラウンドへ抜ける短絡——パッケージを熱くする類の誤りだ。合成は多重ドライブのエラーとして報告する。
(b) y = 1. A z driver contributes nothing, so the other driver wins outright. This is the whole basis of tri-state bus modelling.(b) y = 1。z 驅動器什麼也不貢獻,所以另一個驅動器完全勝出。這正是三態匯流排建模的全部基礎。(b) y = 1。z のドライバは何も寄与しないので、もう一方が完全に勝つ。これがトライステート・バスのモデリング全体の基礎である。
(c) y = 0. On a wired-AND net any 0 pulls the net low, exactly like open-drain outputs sharing a pull-up.(c) y = 0。在線接 AND 的 net 上,任何一個 0 都會把這條 net 拉低,就像共用一個提升電阻的開汲極輸出一樣。(c) y = 0。ワイヤード AND のネットでは、0 が一つでもあればネットを引き下げる。プルアップを共有するオープンドレイン出力とまったく同じである。
(d) Not a multiple-driver situation at all and not an error: a reg is a storage location, and two procedural blocks are simply two writers. The last write wins, so y is 1 if a changed last and 0 if b did. If a and b change in the same time step the result depends on the simulator's arbitrary ordering of the two blocks — a genuine race condition, non-deterministic and legal. Two simulators may legitimately give different answers. Never write to one variable from two always blocks; synthesis rejects it outright, and simulation does something worse than rejecting it.(d) 這根本不是多重驅動的情況,而且不是錯誤:reg 是一個儲存位置,兩個程序區塊不過就是兩個寫入者。最後寫的人勝出,所以如果 a 最後改變則 y 是 1,如果 b 最後改變則是 0。如果 a 和 b 在同一個時間步改變,結果就取決於模擬器對這兩個區塊的任意排序 — 一個如假包換的競爭狀態(race condition),不具決定性而且合法。兩套模擬器給出不同答案,都算合理。永遠不要從兩個 always 區塊寫同一個變數;合成會直接拒收它,而模擬做的事比拒收更糟。(d) そもそも多重ドライブの状況ではなく、エラーでもない。reg は記憶場所であり、二つの手続きブロックは単に二人の書き手にすぎない。最後に書いたほうが勝つので、a が最後に変われば y は 1、b なら 0 である。a と b が同じ時間ステップで変われば、結果は二つのブロックに対するシミュレータの任意の順序づけに依存する——正真正銘のレース条件であり、非決定的であり、しかも合法だ。二つのシミュレータが異なる答えを出しても、どちらも正当でありうる。一つの変数を二つの always ブロックから書いてはならない。合成はこれをきっぱり拒否し、シミュレーションは拒否よりたちの悪いことをする。
Boolean algebra has two values. Verilog has four: 0, 1, x (or X — the case of the value letter does not matter) and z (or Z). The two extra values are not a mathematical extension of Boolean logic; they are engineering admissions about what a simulator can honestly claim.布林代數有兩個值。Verilog 有四個:0、1、x(或 X — 值字母的大小寫無關)與 z(或 Z)。多出來的那兩個值不是布林邏輯的數學延伸;它們是工程上對模擬器究竟能誠實宣稱什麼所做的坦白。ブール代数は二値である。Verilog は四値を持つ。0、1、x(または X——値を表す文字の大小は問わない)、z(または Z)である。追加の二値はブール論理の数学的拡張ではない。シミュレータが正直に主張できることは何かについての、工学的な告白である。
x means the simulator does not know. It is not a third logic level in silicon. The physical node is at some voltage, and that voltage will be read as a 0 or a 1 by whatever gate is downstream. x says "the model cannot tell you which, and if your design depends on it you have a bug". It arises from uninitialised storage, from conflicting drivers, from reading a memory location never written, and from timing-check violations in gate-level simulation.x 的意思是模擬器不知道。它不是矽上的第三個邏輯位準。那個實體節點一定處在某個電壓,而下游的閘一定會把那個電壓讀成 0 或 1。x 說的是「模型無法告訴你是哪一個,而如果你的設計依賴它,那你就有臭蟲」。它來自未初始化的儲存、來自互相衝突的驅動器、來自讀取從未寫過的記憶體位置,以及來自閘級模擬中的時序檢查違規。x はシミュレータが知らないという意味である。シリコン上の第三の論理レベルではない。物理ノードは何らかの電圧にあり、その電圧は下流のゲートによって 0 か 1 として読まれる。x は「モデルはどちらかを言えない。設計がそれに依存しているなら、それはバグだ」と述べている。未初期化の記憶、衝突するドライバ、一度も書かれていないメモリ位置の読み出し、そしてゲートレベル・シミュレーションにおけるタイミングチェック違反から生じる。z means no driver — the node is floating, high impedance. Unlike x, z is a genuine physical state and a useful design element: it is how a tri-state buffer gets off a shared bus.z 的意思是沒有驅動器 — 該節點浮接,呈高阻抗。和 x 不同,z 是一個貨真價實的實體狀態,也是有用的設計元素:三態緩衝器正是靠它從共用匯流排上退下來的。z はドライバがないという意味である。ノードはフローティングで高インピーダンスだ。x と違い z は本物の物理状態であり、有用な設計要素でもある。トライステートバッファが共有バスから降りるのは z によってである。The reason this matters more than it looks: four-value logic is the mechanism by which a Verilog simulation tells you about a class of bug that no amount of testbench cleverness would otherwise find. If reset is missing from a flip-flop, the flop starts at x, the x propagates through the datapath, and within a few cycles the output is a solid red band in the waveform viewer. Two-valued simulation (which is what most FPGA tools and all formal engines use internally) would have randomised the flop to 0 or 1 and you might never have noticed. X-propagation is a feature. Students who "fix" an x by writing initial q = 0; have hidden the bug rather than fixed it, because the silicon has no initial block.這件事比看起來更重要的理由是:四值邏輯正是 Verilog 模擬告訴你一類再怎麼聰明的測試平台也找不到的臭蟲的機制。如果某顆正反器少了 reset,它從 x 開始,x 沿著資料路徑傳播,幾個週期之內輸出就在波形檢視器裡變成一整片紅帶。二值模擬(大多數 FPGA 工具與所有形式驗證引擎內部用的就是它)會把那顆正反器隨機設成 0 或 1,而你可能永遠不會注意到。X 傳播是一項功能。用 initial q = 0; 來「修好」一個 x 的學生,是把臭蟲藏起來而不是修掉,因為矽上沒有 initial 區塊。これが見かけ以上に重要な理由はこうだ。四値論理こそ、Verilog のシミュレーションがどれほど巧妙なテストベンチでも見つけられない種類のバグを教えてくれる仕組みなのである。あるフリップフロップにリセットが欠けていれば、それは x から始まり、x がデータパスを伝播し、数サイクルのうちに出力は波形ビューアで一面の赤い帯になる。二値シミュレーション(多くの FPGA ツールとすべての形式検証エンジンが内部で使う)なら、そのフリップフロップを 0 か 1 にランダム初期化してしまい、君は何にも気づかなかったかもしれない。X 伝播は機能である。initial q = 0; と書いて x を「直した」学生は、直したのではなく隠したのだ。シリコンに initial ブロックは存在しない。
Every bitwise and gate-level operator is defined over all four values. The tables are not arbitrary; each entry is the answer to "given this much knowledge about the inputs, how much can I claim about the output?"每一個位元運算子與閘級運算子都在全部四個值上有定義。這些表並不隨意;每一格都是在回答「已知輸入的資訊只有這麼多,我對輸出能宣稱到什麼程度?」ビット演算子とゲートレベル演算子はすべて四つの値すべてに対して定義されている。表は恣意的ではない。各欄は「入力についてこれだけの知識しかないとき、出力について何をどこまで主張できるか」への答えである。
&, and the and primitiveAND — &,以及 and 基元AND — & と and プリミティブ| & | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | x | x |
| x | 0 | x | x | x |
| z | 0 | x | x | x |
|, and the or primitiveOR — |,以及 or 基元OR — | と or プリミティブ| | | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | x |
| 1 | 1 | 1 | 1 | 1 |
| x | x | 1 | x | x |
| z | x | 1 | x | x |
Read the AND table along its first row and first column: a 0 on either input forces the output to 0 whatever the other input is — including x and z. This is the controlling value of the AND gate, and it is physically true: with one NMOS in the stack turned off, the pull-down path is broken regardless of the other. Symmetrically, 1 is the controlling value of OR. Everywhere else, an x or z input produces x.沿著 AND 表的第一列與第一行讀:任一輸入上的 0 都會把輸出壓成 0,不論另一個輸入是什麼 — 包括 x 與 z。這就是 AND 閘的控制值(controlling value),而且它在物理上為真:堆疊中只要有一顆 NMOS 關閉,下拉路徑就斷了,和另一顆無關。對稱地,1 是 OR 的控制值。除此之外的每一格,只要有 x 或 z 輸入,輸出就是 x。AND 表を第一行と第一列に沿って読め。どちらかの入力の 0 は、もう一方が何であろうと——x でも z でも——出力を 0 に固定する。これが AND ゲートの支配値であり、物理的にも正しい。スタック中の NMOS が一つでもオフなら、もう一方に関係なくプルダウン経路は切れている。対称的に、OR の支配値は 1 である。それ以外の欄では、x または z の入力は出力を x にする。
Notice what the tables do not contain: the value z. A logic gate never outputs z. Its output stage is always driving. z appears on a net only when that net has no driver at all, or when its driver is a tri-state primitive (bufif0, bufif1, notif0, notif1) whose enable is deasserted. Gate inputs treat an incoming z as "I cannot tell", i.e. as x — which is exactly what the last row and column of each table encode.注意這些表裡沒有什麼:值 z。邏輯閘永遠不會輸出 z,它的輸出級一直在驅動。z 只有在一條 net 根本沒有驅動器時,或它的驅動器是一顆 enable 被解除的三態基元(bufif0、bufif1、notif0、notif1)時,才會出現在 net 上。閘的輸入把進來的 z 當成「我分辨不出」,也就是當成 x — 那正是每張表最後一列與最後一行所編碼的內容。表にないものに注意せよ。値 z である。論理ゲートが z を出力することはない。その出力段は常に駆動している。z がネット上に現れるのは、そのネットにドライバがまったくないか、ドライバがイネーブルを落としたトライステート・プリミティブ(bufif0、bufif1、notif0、notif1)であるときだけだ。ゲートの入力は入ってきた z を「判別できない」、すなわち x として扱う。各表の最終行・最終列はまさにそれを符号化している。
XOR is harsher still: it has no controlling value, so a single unknown input makes the output unknown unconditionally.XOR 更嚴苛:它沒有控制值,所以只要有一個未知輸入,輸出就無條件未知。XOR はさらに厳しい。支配値を持たないので、不定の入力が一つあれば出力は無条件に不定になる。
^XOR — ^XOR — ^| ^ | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | x |
| 1 | 1 | 0 | x | x |
| x | x | x | x | x |
| z | x | x | x | x |
~NOT — ~NOT — ~| input輸入入力 | 0 | 1 | x | z |
|---|---|---|---|---|
~ | 1 | 0 | x | x |
Because XOR has no controlling value, an adder built from XORs turns a single unknown carry into a completely unknown sum — which is why one uninitialised flip-flop can black out an entire datapath within one clock cycle.因為 XOR 沒有控制值,由 XOR 組成的加法器會把一個未知的進位變成一個完全未知的和 — 這正是為什麼單單一顆未初始化的正反器,可以在一個時脈週期之內讓整條資料路徑全黑。XOR には支配値がないので、XOR で組んだ加算器は不定のキャリー一つを完全に不定の和へ変える。未初期化のフリップフロップが一つあるだけで、1 クロックのうちにデータパス全体が暗転しうるのはこのためである。
The relational and equality operators do not all follow the propagation rule.關係運算子與等值運算子並非全都遵循前述的傳播規則。関係演算子と等価演算子はすべてが伝播規則に従うわけではない。
== and != are logical equality: if either operand contains an x or z in a bit position that matters, the result is 1'bx, not 0 and not 1.== 與 != 是邏輯等值:只要任一運算元在某個有影響的位元位置上含有 x 或 z,結果就是 1'bx,不是 0 也不是 1。== と != は論理等価である。いずれかのオペランドが結果を左右するビット位置に x や z を含んでいれば、結果は 0 でも 1 でもなく 1'bx になる。=== and !== are case equality: they compare x against x and z against z literally, and always return a clean 0 or 1. not synthesisable — there is no cell that can see the difference between a physical 0 and an x. These belong in testbench checks, where if (dut_out === expected) is exactly what you want.=== 與 !== 是全等(case)等值:它們會按字面把 x 和 x 比、把 z 和 z 比,而且永遠回傳乾淨的 0 或 1。不可合成 — 沒有哪顆 cell 能看出實體的 0 和 x 有什麼不同。這兩個運算子屬於測試平台的檢查,在那裡 if (dut_out === expected) 正是你要的寫法。=== と !== はケース等価である。x と x、z と z を文字どおり比較し、常に 0 か 1 をきれいに返す。合成不可——物理的な 0 と x の違いを見分けられるセルは存在しない。これらはテストベンチの検査に属し、そこでは if (dut_out === expected) こそが欲しい書き方である。The classic bug: writing if (state == 4'bx) in a testbench to detect an unknown. That condition evaluates to x, which an if treats as false, so it never fires. Use ===.經典臭蟲:在測試平台裡寫 if (state == 4'bx) 來偵測未知值。那個條件求值結果是 x,而 if 把 x 當成假,所以它永遠不會觸發。請改用 ===。典型的なバグ:不定値を検出しようとテストベンチに if (state == 4'bx) と書くこと。この条件は x に評価され、if は x を偽として扱うので、決して成立しない。=== を使え。
x propagates, and what it looks like4.3 x 如何傳播,以及它長什麼樣子4.3 x はどう伝播し、どう見えるかWork through a concrete trace. Take the full adder $S = A \oplus B \oplus C_I$, $C_O = AB + AC_I + BC_I$ and feed it $A=1$, $B=0$, $C_I=x$.我們來做一次具體的推演。取全加器 $S = A \oplus B \oplus C_I$、$C_O = AB + AC_I + BC_I$,餵給它 $A=1$、$B=0$、$C_I=x$。具体的に追ってみよう。全加算器 $S = A \oplus B \oplus C_I$、$C_O = AB + AC_I + BC_I$ に、$A=1$、$B=0$、$C_I=x$ を与える。
if and case: if (x) p = 1; else p = 0; takes the else branch and produces a clean 0, hiding the unknown. Both effects matter when you are debugging gate-level simulations after synthesis.悲觀性。考慮 $y = a \;\&\; \sim a$,其中 $a = x$。在數學上,不論 $a$ 取哪個值答案都是 0。Verilog 算出的卻是 $x \;\&\; x = x$,因為它獨立地求值各個閘,已經忘記這兩個輸入是相關的。這叫做 X 悲觀(X-pessimism),是四值模擬的已知限制,不是你設計裡的臭蟲。相反的錯誤 X 樂觀(X-optimism)則發生在 if 與 case 裡:if (x) p = 1; else p = 0; 會走 else 分支,產生一個乾淨的 0,把未知藏起來。當你在合成後除錯閘級模擬時,這兩種效應都很要緊。悲観性。$a = x$ のとき $y = a \;\&\; \sim a$ を考える。数学的には $a$ がどちらの値でも答えは 0 である。しかし Verilog は $x \;\&\; x = x$ と計算する。ゲートを独立に評価し、二つの入力が相関していることを忘れているからだ。これを X 悲観と呼び、四値シミュレーションの既知の限界であって、設計のバグではない。逆の誤りである X 楽観は if と case で起きる。if (x) p = 1; else p = 0; は else 側を通り、きれいな 0 を生んで不定を隠す。合成後のゲートレベル・シミュレーションをデバッグするときは、どちらの効果も重要になる。In a waveform viewer, x is conventionally drawn as a red band at mid-level and z as a grey or blue band on the centre line. The debugging procedure is always the same and always works: find the earliest time at which the red appears, then trace backwards through the drivers to the first signal that is red at that time. One of four things will be true at the source.在波形檢視器裡,x 慣例上畫成中間高度的紅色帶狀,z 則畫成中線上的灰色或藍色帶狀。除錯程序永遠一樣,而且永遠有效:找出紅色最早出現的時刻,然後沿著驅動器往回追,追到在那個時刻第一個變紅的訊號。在那個源頭,下列四件事之一會成立。波形ビューアでは、慣例として x は中央の高さに赤い帯で、z は中心線上に灰色または青の帯で描かれる。デバッグ手順はいつも同じで、いつも有効である。赤が現れる最も早い時刻を見つけ、ドライバをさかのぼって、その時刻に赤くなっている最初の信号まで追う。その源では、次の四つのうちどれかが成り立っている。
| What you find你看到的現象見つかる現象 | Cause成因原因 | Fix修法対処 |
|---|---|---|
| A flip-flop output red from time 0, never clearing某顆正反器的輸出從時間 0 起就是紅的,而且從不消退あるフリップフロップの出力が時刻 0 から赤いまま、いつまでも消えない | No reset, or reset never asserted by the testbench沒有 reset,或測試平台從未拉起 resetリセットがない、またはテストベンチが一度もアサートしていない | Assert reset for several cycles at the start of every simulation每次模擬一開始就讓 reset 維持數個週期すべてのシミュレーションの冒頭で数サイクル分リセットをアサートする |
| A net red from time 0 with no driver某條 net 從時間 0 起就是紅的,而且沒有驅動器あるネットが時刻 0 から赤く、ドライバがない | Typo in a signal name created an implicit 1-bit wire (Verilog auto-declares undeclared nets)訊號名稱打錯字,造成一條隱式的 1 位元 wire(Verilog 會自動宣告未宣告的 net)信号名の誤記が暗黙の 1 ビット wire を作った(Verilog は未宣言のネットを自動宣言する) | `default_nettype none at the top of every file — this turns the typo into a compile error在每個檔案開頭加上 `default_nettype none — 這會讓那個錯字變成編譯錯誤各ファイルの先頭に `default_nettype none を置く。誤記がコンパイルエラーになる |
| A net that goes red exactly when two blocks both drive it某條 net 恰好在兩個區塊同時驅動它時變紅二つのブロックが同時に駆動する瞬間にだけ赤くなるネット | Multiple drivers in conflict (§3.5, Practice 5a)多重驅動衝突(§3.5、練習 5a)ドライバの衝突(§3.5、練習 5a) | Remove one driver, or make them tri-state拿掉其中一個驅動器,或把它們改成三態片方のドライバを外すか、両方をトライステートにする |
| A net red only in gate-level simulation, clean in RTL某條 net 只在閘級模擬時變紅,在 RTL 模擬時乾乾淨淨ゲートレベル・シミュレーションでだけ赤くなり、RTL ではきれいなネット | A setup or hold timing check failed and the flop model emitted xsetup 或 hold 時序檢查失敗,正反器模型吐出了 xセットアップまたはホールドのタイミングチェックが失敗し、フリップフロップのモデルが x を出した | Real timing bug — fix the path, do not disable the check真正的時序臭蟲 — 去修那條路徑,不要關掉檢查本物のタイミングバグ。パスを直すこと。チェックを無効にしてはならない |
By default, Verilog creates a 1-bit wire for any undeclared identifier used in a port connection or on the right of an expression. Write Add_full G1 (sum[0], c_in2, a[0], b[0], c_ln); with a lower-case L instead of an i and you do not get an error — you get a brand-new, undriven net called c_ln sitting at z, and a carry chain that produces x. Put `default_nettype none at the top of the file and `default_nettype wire at the bottom, and the compiler catches it in a second.預設情況下,只要有未宣告的識別字出現在埠連接中或運算式右側,Verilog 就會替它造一條 1 位元的 wire。把 Add_full G1 (sum[0], c_in2, a[0], b[0], c_ln); 裡的 i 打成小寫 L,你不會得到錯誤 — 你會得到一條全新的、沒人驅動的 net 叫 c_ln,停在 z,以及一條吐出 x 的進位鏈。在檔案開頭放 `default_nettype none、結尾放 `default_nettype wire,編譯器一秒就抓到它。既定では、ポート接続や式の右辺に現れた未宣言の識別子に対して、Verilog は 1 ビットの wire を作る。Add_full G1 (sum[0], c_in2, a[0], b[0], c_ln); の i を小文字の L と打ち間違えてもエラーにはならない。c_ln という真新しい、誰も駆動しないネットが z のまま現れ、キャリーチェーンが x を吐くだけだ。ファイルの先頭に `default_nettype none、末尾に `default_nettype wire を置けば、コンパイラが一秒で捕まえる。
z, tri-state and why you will rarely use it4.4 z、三態,以及為何你很少會用到它4.4 z、トライステート、そしてそれをめったに使わない理由A high-impedance output is produced by a tri-state driver. The four Verilog primitives are bufif1, bufif0, notif1 and notif0; in RTL you write it with a conditional whose false branch is z:高阻抗輸出由三態驅動器產生。Verilog 的四個基元是 bufif1、bufif0、notif1 與 notif0;在 RTL 裡你是用一個「假分支為 z」的條件運算式寫出來的:高インピーダンス出力はトライステート・ドライバが生む。Verilog のプリミティブは bufif1、bufif0、notif1、notif0 の四つである。RTL では偽側の枝を z にした条件式で書く。
module bus_driver (bus, data, oe);
input [7:0] data;
input oe;
output [7:0] bus;
assign bus = oe ? data : 8'bz; // tri-state: releases the bus when oe = 0
endmodule
Several such modules can drive one wire, and as long as at most one has oe = 1 at a time the resolution works out: the single non-z driver wins. If two assert simultaneously you get x on the bus in simulation and a short circuit in silicon.數個這樣的模組可以驅動同一條 wire,只要在任何時刻至多只有一個的 oe = 1,裁決就會成立:唯一那個非 z 的驅動器勝出。如果有兩個同時拉起,模擬裡匯流排上就是 x,矽上則是一條短路。こうしたモジュールを複数同じ wire につないでも、どの時点でも oe = 1 が高々一つであれば解決は成立する。z でない唯一のドライバが勝つ。二つが同時にアサートすれば、シミュレーションではバス上に x が、シリコン上には短絡が生じる。
In modern on-chip design this is almost always the wrong structure. Internal tri-state buses are hard to test (a floating node has no defined value for ATPG to observe), they are prone to crowbar currents during the overlap when one driver turns off and another turns on, and the library cells for them are large. Nearly every design replaces them with a multiplexer, which costs more gates and is enormously better behaved. Tri-state survives at the chip boundary — bidirectional I/O pads for an SRAM interface, an I²C line — and that is where inout ports belong. Expect to see this again in Lecture 7 when the synthesisable subset is discussed in detail.在現代晶片內部設計中,這幾乎總是錯誤的結構。晶片內部的三態匯流排很難測試(浮接節點沒有確定值可供 ATPG 觀測),在一個驅動器關閉與另一個開啟的重疊期間容易產生貫穿電流,而且對應的函式庫 cell 很大。幾乎每個設計都會改用多工器取代它:多花一些閘,但行為好太多了。三態只在晶片邊界存活下來 — SRAM 介面的雙向 I/O pad、一條 I²C 線 — 而那正是 inout 埠該出現的地方。第 7 講詳細討論可合成子集時,你會再遇到這個主題。現代のオンチップ設計では、これはほぼ常に誤った構造である。内部のトライステートバスはテストしにくく(浮いたノードには ATPG が観測できる確定値がない)、一方のドライバが切れ他方が入る重なりの間に貫通電流を生みやすく、対応するライブラリセルも大きい。ほとんどの設計はこれをマルチプレクサで置き換える。ゲート数は増えるが、振る舞いははるかに素直である。トライステートが生き残るのはチップ境界——SRAM インタフェースの双方向 I/O パッド、I²C の信号線——であり、そこが inout ポートの居場所である。合成可能サブセットを詳しく扱う第 7 講で再会することになる。
| Field欄位フィールド | Meaning意義意味 | Notes備註備考 |
|---|---|---|
sizesizesize | Number of bits the value occupies這個值佔用的位元數値が占めるビット数 | A plain decimal integer, always — 8'h2F means eight bits, not eight hex digits. Optional.永遠是一個單純的十進位整數 — 8'h2F 指的是八個位元,不是八個十六進位數字。可省略。常に素の十進整数である。8'h2F は 8 ビットの意味であって、16 進数字 8 桁ではない。省略可。 |
''' | Separator (an apostrophe)分隔符號(一個單引號)区切り記号(アポストロフィ) | Mandatory when a base is given只要給了基底就必須寫基数を書くときは必須 |
basebasebase | b/B binary, o/O octal, d/D decimal, h/H hexadecimalb/B 二進位、o/O 八進位、d/D 十進位、h/H 十六進位b/B 2 進、o/O 8 進、d/D 10 進、h/H 16 進 | Case-insensitive. Verilog-2001 adds s for signed, e.g. 8'sd200.不分大小寫。Verilog-2001 另加 s 表示有號,例如 8'sd200。大小文字を区別しない。Verilog-2001 は符号付きを表す s を追加した。例:8'sd200。 |
numbernumbernumber | The digits, in that base該基底下的各個數字その基数での数字列 | May contain x, z and ? (a synonym for z) in any base except decimal. Underscores may be inserted freely for readability: 16'b0011_1010_1011_0000.除十進位以外的任何基底都可以含有 x、z 與 ?(z 的同義字)。底線可以隨意插入以增加可讀性:16'b0011_1010_1011_0000。10 進以外のどの基数でも x、z、?(z の別名)を含められる。可読性のためアンダースコアを自由に挿入してよい。16'b0011_1010_1011_0000。 |
The size is counted in bits, and that is the single most common misreading. One octal digit is three bits; one hex digit is four. So 5'O36 is five bits wide although octal 36 nominally needs six — and the answer is 11110, the low five bits of 011110, with the leading 0 dropped by truncation. And 16'H3AB is sixteen bits although only three hex digits were written — the value is zero-extended to 0000_0011_1010_1011.位寬是用位元計算的,而這正是最常被讀錯的一點。一個八進位數字是三個位元;一個十六進位數字是四個位元。所以 5'O36 寬五個位元,儘管八進位的 36 名目上需要六個位元 — 答案是 11110,也就是 011110 的低五位,最前面那個 0 被截斷掉了。而 16'H3AB 是十六個位元,儘管只寫了三個十六進位數字 — 值被零延伸成 0000_0011_1010_1011。サイズはビットで数える。ここが最もよく読み違えられる。8 進の 1 桁は 3 ビット、16 進の 1 桁は 4 ビットである。したがって 5'O36 は 5 ビット幅であり、8 進の 36 は名目上 6 ビットを要するにもかかわらずそうなる。答えは 011110 の下位 5 ビット、すなわち 11110 で、先頭の 0 は切り捨てられる。また 16'H3AB は 16 進数字を 3 桁しか書いていないが 16 ビットであり、値はゼロ拡張されて 0000_0011_1010_1011 になる。
Three rules cover everything, and the slides' example table is a direct application of all three.三條規則就涵蓋了一切,而投影片的範例表正是這三條規則的直接應用。三つの規則ですべてが尽くされ、スライドの例の表はその三つの直接の適用である。
0 if the leftmost written digit is 0 or 1; extend with x if the leftmost digit is x; extend with z if it is z. (In Verilog-2001, a signed literal sign-extends instead.)如果常數比宣告的位寬窄,就在左側延伸。若最左邊寫出來的數字是 0 或 1,就用 0 延伸;若最左邊是 x,就用 x 延伸;若是 z,就用 z 延伸。(在 Verilog-2001 中,有號常數改為符號延伸。)リテラルが宣言された幅より狭ければ、左側へ拡張する。書かれた最左の桁が 0 か 1 なら 0 で、x なら x で、z なら z で拡張する。(Verilog-2001 では符号付きリテラルは符号拡張になる。)3'b5 is a syntax error because 5 is not a binary digit — this is one of the few places Verilog actually refuses to guess.每一個數字都必須在所宣告的基底下合法。3'b5 是語法錯誤,因為 5 不是二進位數字 — 這是 Verilog 少數真的拒絕猜測的地方之一。どの桁も、指定した基数で合法でなければならない。3'b5 は構文エラーである。5 は 2 進の数字ではないからだ。Verilog が推測を拒む数少ない場所の一つである。| Literal常數寫法リテラル | Stored bits實際存入的位元格納されるビット | Which rule適用哪條規則適用される規則 |
|---|---|---|
6'b010111 | 01_0111 | Exact fit, nothing happens剛好合身,什麼也不會發生ちょうど収まり、何も起きない |
8'b0110 | 0000_0110 | Rule 1, zero-extend規則 1,零延伸規則 1、ゼロ拡張 |
4'bx01 | xx01 | Rule 1 — leftmost written digit is x, so the pad is x, not 0規則 1 — 最左邊寫出來的數字是 x,所以補的是 x 而不是 0規則 1。書かれた最左の桁が x なので、詰め物は 0 ではなく x |
16'H3AB | 0000_0011_1010_1011 | Rule 1; 3 hex digits = 12 bits, padded to 16規則 1;3 個十六進位數字 = 12 位元,補到 16 位元規則 1。16 進 3 桁 = 12 ビット、16 ビットへ詰める |
12'd24 | 0000_0001_1000 | Rule 1 on a decimal literal對十進位常數套用規則 110 進リテラルへの規則 1 の適用 |
5'O36 | 11110 | Rule 2 — octal 36 = 011_110 (6 bits) truncated to 5規則 2 — 八進位的 36 = 011_110(6 位元),截斷成 5 位元規則 2。8 進の 36 = 011_110(6 ビット)を 5 ビットへ切り詰め |
16'Hx | xxxx_xxxx_xxxx_xxxx | Rule 1; one x hex digit is four x bits, then x-extended規則 1;一個 x 十六進位數字就是四個 x 位元,然後再做 x 延伸規則 1。16 進の x 1 桁は x 4 ビットであり、さらに x で拡張される |
8'hz | zzzz_zzzz | Rule 1 with z padding規則 1,以 z 補位規則 1、z で詰める |
3'b5 | illegal不合法不正 | Rule 3規則 3規則 3 |
A bare decimal number such as 24 is an unsized, signed, 32-bit (at least) integer. A literal with a base but no size such as 'hA is unsized and unsigned, also at least 32 bits, zero-extended.一個裸的十進位數字,例如 24,是一個無位寬、有號、(至少)32 位元的整數。有基底但沒有位寬的常數,例如 'hA,是無位寬且無號,同樣至少 32 位元,以零延伸。素の 10 進数、たとえば 24 は、サイズなし・符号付き・(最低)32 ビットの整数である。基数つきでサイズなしのリテラル、たとえば 'hA はサイズなし・符号なしで、やはり最低 32 ビット、ゼロ拡張される。
Neither is wrong, but both participate in Verilog's expression-width rules, which resize every operand of an expression to the width of the widest operand, including the left-hand side. Two consequences:兩者都不算錯,但它們都會參與 Verilog 的運算式位寬規則,而那套規則會把運算式中的每一個運算元,連同左側目標在內,一起調整成最寬運算元的位寬。由此有兩個後果:どちらも誤りではないが、どちらも Verilog の式幅規則に巻き込まれる。この規則は左辺を含む式のすべてのオペランドを、最も広いオペランドの幅に揃える。帰結は二つある。
assign y = a + 1; with a 4 bits and y 4 bits does the addition in 4 bits and loses the carry out. If y were 5 bits the addition would be done in 5 bits and the carry would appear. The width of the target changes the arithmetic. This is unlike every programming language you know.assign y = a + 1; 在 a 是 4 位元、y 也是 4 位元時,加法會以 4 位元進行,進位輸出就丟了。如果 y 是 5 位元,加法就會以 5 位元進行,進位就出現了。目標的位寬會改變算術本身。這和你熟悉的任何一種程式語言都不同。a が 4 ビット、y も 4 ビットのとき、assign y = a + 1; は 4 ビットで加算し、キャリー出力を失う。y が 5 ビットなら加算は 5 ビットで行われ、キャリーが現れる。代入先の幅が算術そのものを変えるのだ。君の知るどのプログラミング言語とも違う。if (count == 32'hFFFF_FFFF) compares in 32 bits; if (count == -1) also works because -1 sign-extends to all ones — but if (count == 'hFFFFFFFF) with count declared 40 bits compares count against a 40-bit zero-extended value and silently fails for values with any of the top 8 bits set.if (count == 32'hFFFF_FFFF) 以 32 位元比較;if (count == -1) 也可行,因為 -1 會符號延伸成全 1 — 但若 count 宣告為 40 位元,if (count == 'hFFFFFFFF) 比較的是 count 與一個 40 位元的零延伸值,於是只要最高 8 位元中有任何一位為 1,比較就安靜地失敗。if (count == 32'hFFFF_FFFF) は 32 ビットで比較する。if (count == -1) も機能する。-1 は符号拡張されて全 1 になるからだ。しかし count を 40 ビットと宣言したうえで if (count == 'hFFFFFFFF) と書けば、count は 40 ビットへゼロ拡張された値と比較され、上位 8 ビットのどれかが立っている値では黙って成立しなくなる。The habit that removes the whole class of problem: always write the size. 4'd0, not 0. 16'h0000, not 'h0. It costs three characters and it makes the width visible in the source instead of hidden in a rule.能一次消掉這整類問題的習慣是:永遠把位寬寫出來。寫 4'd0,不要寫 0。寫 16'h0000,不要寫 'h0。這只花你三個字元,卻把位寬從一條規則裡搬到原始碼上,看得見。この種の問題をまとめて消す習慣はこうだ。常にサイズを書け。0 ではなく 4'd0。'h0 ではなく 16'h0000。三文字の手間で、幅が規則の中に隠れるのではなくソース上に見えるようになる。
Problem: Give the stored bits, or say why the literal is illegal. (a) 10'h2F (b) 6'o57 (c) 8'bz1 (d) 4'd20 (e) 'hA (f) 4'hxF (g) 12'o7777 (h) 2'b1_0 (i) 8'd4'b1.題目:寫出實際存入的位元,或說明這個常數為何不合法。(a) 10'h2F (b) 6'o57 (c) 8'bz1 (d) 4'd20 (e) 'hA (f) 4'hxF (g) 12'o7777 (h) 2'b1_0 (i) 8'd4'b1。問題:格納されるビットを示すか、リテラルが不正な理由を述べよ。(a) 10'h2F (b) 6'o57 (c) 8'bz1 (d) 4'd20 (e) 'hA (f) 4'hxF (g) 12'o7777 (h) 2'b1_0 (i) 8'd4'b1。
(a) 00_0010_1111. Hex 2F is 8 bits (0010_1111), zero-extended to 10.(a) 00_0010_1111。十六進位的 2F 是 8 位元(0010_1111),零延伸到 10 位元。(a) 00_0010_1111。16 進の 2F は 8 ビット(0010_1111)で、10 ビットへゼロ拡張される。
(b) 101_111. Octal 57 = 101_111, exactly 6 bits.(b) 101_111。八進位的 57 = 101_111,正好 6 位元。(b) 101_111。8 進の 57 = 101_111 で、ちょうど 6 ビット。
(c) zzzz_zzz1. The written value is z1: binary digit z then 1, i.e. 2 bits, and the leftmost is z, so rule 1 pads with z.(c) zzzz_zzz1。寫出來的值是 z1:二進位數字 z 接著 1,也就是 2 位元,而最左邊是 z,所以規則 1 用 z 補位。(c) zzzz_zzz1。書かれた値は z1、すなわち 2 進の z と 1 で 2 ビット。最左が z なので規則 1 は z で詰める。
(d) 0100. Decimal 20 is 1_0100, 5 bits; truncated from the left to 4 bits gives 0100 = 4. Most tools warn; the value is silently wrong if you ignore the warning.(d) 0100。十進位的 20 是 1_0100,5 位元;從左截斷成 4 位元得到 0100 = 4。多數工具會警告;如果你忽略警告,值就安靜地錯了。(d) 0100。10 進の 20 は 1_0100 で 5 ビット。左から切り詰めて 4 ビットにすると 0100 = 4 になる。多くのツールは警告するが、警告を無視すれば値は黙って誤る。
(e) At least 32 bits: 0000…0000_1010. Unsized, unsigned, zero-extended.(e) 至少 32 位元:0000…0000_1010。無位寬、無號、零延伸。(e) 最低 32 ビット:0000…0000_1010。サイズなし・符号なし・ゼロ拡張。
(f) 1111. The written value is two hex digits, x then F, i.e. 8 bits xxxx_1111; rule 2 truncates the four most significant bits away, and with them the unknown. This is a nasty one: the literal looks partly unknown and is not. If you want four unknown bits, write 4'hx.(f) 1111。寫出來的值是兩個十六進位數字,x 接著 F,也就是 8 位元的 xxxx_1111;規則 2 把最高的四個位元截掉,未知也一起截掉了。這一題很陰險:這個常數看起來有一部分是未知的,其實並沒有。如果你要的是四個未知位元,請寫 4'hx。(f) 1111。書かれた値は 16 進 2 桁、x と F、すなわち 8 ビットの xxxx_1111 である。規則 2 が上位 4 ビットを切り捨て、不定もろとも消える。これは意地が悪い。リテラルは一部が不定に見えるのに、そうではない。不定 4 ビットが欲しいなら 4'hx と書け。
(g) 111_111_111_111. Four octal digits = 12 bits exactly, all ones.(g) 111_111_111_111。四個八進位數字正好是 12 位元,全為 1。(g) 111_111_111_111。8 進 4 桁はちょうど 12 ビットで、すべて 1。
(h) 10. The underscore is a readability separator and is simply ignored; it may appear anywhere except as the first character of the digit string.(h) 10。底線只是增加可讀性的分隔符號,會被直接忽略;除了不能當數字串的第一個字元以外,它可以出現在任何位置。(h) 10。アンダースコアは可読性のための区切りで、単に無視される。数字列の先頭文字になること以外、どこに置いてもよい。
(i) Illegal. A literal has one base specifier. 8'd4'b1 is a syntax error.(i) 不合法。一個常數只能有一個基底指定符。8'd4'b1 是語法錯誤。(i) 不正。リテラルの基数指定子は一つだけである。8'd4'b1 は構文エラー。
Problem: Evaluate each expression. (a) 1'b0 & 1'bx (b) 1'b1 & 1'bz (c) 1'b1 | 1'bx (d) 1'b0 | 1'bz (e) 4'b10xz & 4'b1111 (f) 4'b10xz | 4'b0101 (g) 1'bx ^ 1'bx (h) 4'b11x0 == 4'b11x0 (i) 4'b11x0 === 4'b11x0 (j) & 4'b111x (reduction AND) (k) | 4'b000x (reduction OR) (l) & 4'b110x.題目:求出下列各運算式的值。(a) 1'b0 & 1'bx (b) 1'b1 & 1'bz (c) 1'b1 | 1'bx (d) 1'b0 | 1'bz (e) 4'b10xz & 4'b1111 (f) 4'b10xz | 4'b0101 (g) 1'bx ^ 1'bx (h) 4'b11x0 == 4'b11x0 (i) 4'b11x0 === 4'b11x0 (j) & 4'b111x(縮減 AND)(k) | 4'b000x(縮減 OR)(l) & 4'b110x。問題:次の各式を評価せよ。(a) 1'b0 & 1'bx (b) 1'b1 & 1'bz (c) 1'b1 | 1'bx (d) 1'b0 | 1'bz (e) 4'b10xz & 4'b1111 (f) 4'b10xz | 4'b0101 (g) 1'bx ^ 1'bx (h) 4'b11x0 == 4'b11x0 (i) 4'b11x0 === 4'b11x0 (j) & 4'b111x(リダクション AND)(k) | 4'b000x(リダクション OR)(l) & 4'b110x。
(a) 0 — controlling 0. (b) x — z is read as unknown. (c) 1 — controlling 1 for OR. (d) x.(a) 0 — 控制值 0。(b) x — z 被讀成未知。(c) 1 — OR 的控制值 1。(d) x。(a) 0——支配値 0。(b) x——z は不定として読まれる。(c) 1——OR の支配値 1。(d) x。
(e) 10xx. Bit by bit: 1&1=1, 0&1=0, x&1=x, z&1=x. Note the z becomes x, not z.(e) 10xx。逐位元看:1&1=1、0&1=0、x&1=x、z&1=x。注意那個 z 變成了 x,不是 z。(e) 10xx。ビットごとに 1&1=1、0&1=0、x&1=x、z&1=x。z が z ではなく x になる点に注意。
(f) 1,1,x,1 → 11x1. Working right to left: z|1=1, x|0=x, 0|1=1, 1|0=1.(f) 1、1、x、1 → 11x1。由右往左算:z|1=1、x|0=x、0|1=1、1|0=1。(f) 1、1、x、1 → 11x1。右から左へ:z|1=1、x|0=x、0|1=1、1|0=1。
(g) x. XOR has no controlling value, and the simulator does not notice that both operands are the same signal. (X-pessimism.)(g) x。XOR 沒有控制值,而且模擬器不會注意到兩個運算元其實是同一條訊號。(X 悲觀。)(g) x。XOR には支配値がなく、しかもシミュレータは両オペランドが同じ信号であることに気づかない。(X 悲観。)
(h) 1'bx. Logical equality returns x when any compared bit is x or z, even if the two sides are textually identical.(h) 1'bx。只要任何被比較的位元是 x 或 z,邏輯等值就回傳 x,即使兩邊字面上一模一樣也一樣。(h) 1'bx。比較されるビットに x や z が一つでもあれば、論理等価は x を返す。両辺が文字どおり同一でも同じである。
(i) 1'b1. Case equality compares x to x literally. This is why testbench self-checks use !==.(i) 1'b1。全等比較會按字面把 x 和 x 比。這正是測試平台自我檢查要用 !== 的原因。(i) 1'b1。ケース等価は x と x を文字どおり比較する。テストベンチの自己検査が !== を使うのはこのためである。
(j) x — reduction AND of 111x: no controlling 0 anywhere, one unknown.(j) x — 對 111x 做縮減 AND:任何位置都沒有控制值 0,而有一個未知。(j) x——111x のリダクション AND。支配値 0 がどこにもなく、不定が一つある。
(k) x — reduction OR of 000x: no controlling 1, one unknown.(k) x — 對 000x 做縮減 OR:沒有控制值 1,而有一個未知。(k) x——000x のリダクション OR。支配値 1 がなく、不定が一つある。
(l) 0 — there is a 0 in bit 1, and 0 controls AND, so the unknown is irrelevant. Contrast with (j): the same unknown, a different answer, purely because of what accompanies it.(l) 0 — 第 1 位元上有一個 0,而 0 是 AND 的控制值,所以那個未知無關緊要。拿它和 (j) 對照:同樣的未知,答案卻不同,純粹是因為陪在它旁邊的是什麼。(l) 0——ビット 1 に 0 があり、0 は AND の支配値なので、不定は無関係になる。(j) と比べよ。同じ不定でも答えが違うのは、ひとえに何が隣にいるかによる。
Verilog offers three ways to say the same thing, and the lecture insists on all three because each one teaches something the others hide. Structural description teaches you that a design is a graph of connected objects. RTL description teaches you to think in terms of functions between registers. Behavioral description teaches you that the tool, not you, is responsible for building the gates. In production RTL you will mostly write the middle one, with behavioral always blocks for sequential logic and structural instantiation only for hierarchy — but you must be able to read all three, because synthesis output is pure structure and legacy IP is full of all of them.Verilog 提供三種說同一件事的方式,而這一講堅持三種都要講,因為每一種都教了另外兩種藏起來的東西。結構化描述教你:一個設計是一張由物件連接而成的圖。RTL 描述教你用「暫存器之間的函數」來思考。行為描述教你:負責把閘搭出來的是工具,不是你。在實務 RTL 裡你大多會寫中間那一種,順序邏輯用行為式 always 區塊,結構化實例化則只用在階層上 — 但三種你都必須讀得懂,因為合成的輸出是純結構,而舊 IP 裡三種都滿滿都是。Verilog は同じことを言う方法を三つ用意しており、この講義は三つすべてを扱うことにこだわる。どれもが、他の二つが隠してしまうことを教えるからだ。構造記述は、設計とは接続された対象のグラフであることを教える。RTL 記述は、レジスタ間の関数として考えることを教える。動作記述は、ゲートを組み立てる責任が君ではなくツールにあることを教える。実務の RTL ではたいてい真ん中を書き、順序論理には動作記述の always ブロックを、構造的インスタンス化は階層にだけ使う——が、三つとも読めなければならない。合成の出力は純粋な構造であり、レガシー IP には三つとも溢れているからだ。
| Style風格スタイル | Verilog constructs用到的 Verilog 語法用いる Verilog 構文 | What you specify你指定什麼君が指定すること | What the tool decides工具決定什麼ツールが決めること |
|---|---|---|---|
| Structural結構化構造記述 | Gate primitives (and, or, xor, nand, not, …), module instances, wire declarations閘基元(and、or、xor、nand、not…)、模組實例、wire 宣告ゲートプリミティブ(and、or、xor、nand、not ほか)、モジュールインスタンス、wire 宣言 | Every gate and every connection每一顆閘、每一條連接すべてのゲートとすべての接続 | Which library cell implements each primitive, and its drive strength每個基元由哪一顆函式庫 cell 實現,以及它的驅動強度各プリミティブをどのライブラリセルで実装するか、およびそのドライブ強度 |
| RTL / dataflowRTL/資料流RTL/データフロー | assign, operators, concatenation, conditional ?:assign、運算子、串接、條件運算子 ?:assign、演算子、連結、条件演算子 ?: | The Boolean/arithmetic function of each signal每條訊號的布林/算術函數各信号のブール関数・算術関数 | The whole gate network, including factoring and sharing整張閘網路,包括因式分解與資源共用ゲート網全体。因数分解や共有も含む |
| Behavioral行為描述動作記述 | always, if/else, case, for, while, blocking and non-blocking assignment, named blocks, disablealways、if/else、case、for、while、阻隔式與非阻隔式指定、具名區塊、disablealways、if/else、case、for、while、ブロッキング代入とノンブロッキング代入、名前付きブロック、disable | An algorithm, with control flow一套帶有控制流程的演算法制御フローを伴うアルゴリズム | Everything: control flow becomes multiplexers and priority encoders一切:控制流程會變成多工器與優先編碼器すべて。制御フローはマルチプレクサとプライオリティエンコーダになる |
A structural description is a netlist written by hand. The built-in primitives relevant to this lecture are the multi-input combinational gates: and, nand, or, nor, xor, xnor, plus the single-input buf and not. Their instantiation syntax has one quirk you must internalise:結構化描述就是一份用手寫出來的網表。與本講相關的內建基元是多輸入組合閘:and、nand、or、nor、xor、xnor,再加上單輸入的 buf 與 not。它們的實例化語法有一個你必須內化的怪癖:構造記述とは、手で書いたネットリストである。この講義に関係する組み込みプリミティブは多入力の組合せゲート——and、nand、or、nor、xor、xnor——と、単入力の buf、not である。そのインスタンス化構文には、身体で覚えるべき癖が一つある。
gate_type [instance_name] ( output, input1, input2, …, inputN );gate_type [instance_name] ( 輸出, 輸入1, 輸入2, …, 輸入N );gate_type [instance_name] ( 出力, 入力1, 入力2, …, 入力N );
The output is always the first terminal. This is the opposite of the C convention y = f(a,b) you might expect to see mirrored, and it is the opposite of how assign reads. The and, or, xor, nand, nor and xnor primitives accept any number of inputs ≥ 2, so or (y, a, b, c, d); is a four-input OR. buf and not are the mirror image: they take one input, which is last, and may have several outputs before it — not (y1, y2, a); is two inverters both driven by a.輸出永遠是第一個端子。這和你可能期待被沿用的 C 慣例 y = f(a,b) 相反,也和 assign 的讀法相反。and、or、xor、nand、nor、xnor 這些基元接受任意 ≥ 2 個輸入,所以 or (y, a, b, c, d); 是一顆四輸入 OR。buf 與 not 則是鏡像:它們只有一個輸入,而且放在最後,前面可以有好幾個輸出 — not (y1, y2, a); 是兩顆都由 a 驅動的反相器。出力は常に最初の端子である。これは、そのまま引き継がれていそうな C の慣習 y = f(a,b) とは逆であり、assign の読み方とも逆である。and、or、xor、nand、nor、xnor は 2 個以上いくつでも入力を取るので、or (y, a, b, c, d); は 4 入力 OR である。buf と not は鏡像で、入力は一つだけ、しかも最後に置かれ、その前に複数の出力を並べられる。not (y1, y2, a); は、ともに a で駆動される二つのインバータである。
The instance name is optional for primitives and mandatory for module instances. That asymmetry is in the slides' Language Conventions page ("instance of a module must be named") and it appears in the full-adder example, where Add_half M1 (…) is named and or (c_out, w2, w3); is not.實例名稱對基元是選用的,對模組實例則是必要的。這個不對稱寫在投影片的 Language Conventions 那頁(「instance of a module must be named」),而且就出現在全加器範例裡:Add_half M1 (…) 有名字,or (c_out, w2, w3); 沒有。インスタンス名はプリミティブでは省略可、モジュールインスタンスでは必須である。この非対称はスライドの Language Conventions のページ(「instance of a module must be named」)にあり、全加算器の例にそのまま現れる。Add_half M1 (…) には名があり、or (c_out, w2, w3); にはない。
Unnamed primitive instances get synthesised names like U17, and when post-layout timing reports a violating path through U17 you have no idea which line of source it came from. Naming primitives costs nothing and makes every downstream report readable. The same argument applies to generate blocks and to the loop labels in Lecture 7.沒有命名的基元實例,合成後會拿到像 U17 這樣的名字;當佈局後時序報告指出一條違規路徑經過 U17 時,你完全不知道它來自原始碼的哪一行。替基元命名不花任何成本,卻讓下游每一份報告都讀得懂。同樣的論證也適用於 generate 區塊,以及第 7 講的迴圈標籤。名前のないプリミティブ・インスタンスは合成後に U17 のような名前になる。レイアウト後のタイミング報告が U17 を通る違反パスを指しても、それがソースのどの行から来たのか見当もつかない。プリミティブに名前を付けるのはただであり、下流のあらゆる報告が読めるようになる。同じ論法は generate ブロックにも、第 7 講のループラベルにも当てはまる。
The RTL style rests on one construct, and the slides' two forms of it are worth separating carefully.RTL 風格建立在一個語法構造上,而投影片給的兩種寫法值得仔細區分開來。RTL スタイルは一つの構文の上に立つ。スライドが示すその二形式は、丁寧に区別しておく価値がある。
module nand2_1 (y, x1, x2);
input x1, x2;
output y;
assign y = ~(x1 & x2);
endmodulemodule nand2_2 (y, x1, x2);
input x1, x2;
output y;
wire y = ~(x1 & x2);
endmoduleThe second form combines the declaration of the net and its continuous assignment into one statement. It is legal, it means exactly the same thing, and it may appear only once per net (you cannot declare a net twice). It is slightly terse for a complicated expression but very convenient for a one-line intermediate signal.第二種寫法把 net 的宣告和它的連續指定併成一條敘述。它合法,意思完全相同,而且每條 net 只能出現一次(你不能把同一條 net 宣告兩次)。對複雜運算式來說它稍嫌擁擠,但對一行搞定的中間訊號非常方便。二つ目の形式は、ネットの宣言とその連続代入を一文にまとめる。合法であり、意味もまったく同じで、ネット一本につき一度しか書けない(同じネットを二度宣言はできない)。複雑な式にはやや窮屈だが、一行で済む中間信号にはとても便利である。
The lecture slide writes both versions as y = x1 ~& x2;. There is no binary ~& operator in Verilog. The token ~& exists only as a unary reduction NAND, applied to a single vector: ~& bus means "NAND all the bits of bus together". A two-operand NAND must be written ~(x1 & x2), or structurally as nand (y, x1, x2);. Any simulator will reject the slide's form with a syntax error at &. The same applies to the non-existent ~| and ~^ as binary operators — as unary reductions they are NOR and XNOR, as binary operators they do not exist (binary XNOR is written ^~ or ~^, which is legal, making that one a genuine ambiguity the parser resolves by operand count).課堂投影片把兩種版本都寫成 y = x1 ~& x2;。Verilog 沒有二元的 ~& 運算子。記號 ~& 只存在於單元縮減 NAND,作用在單一向量上:~& bus 的意思是「把 bus 的所有位元一起做 NAND」。兩個運算元的 NAND 必須寫成 ~(x1 & x2),或結構化地寫成 nand (y, x1, x2);。任何模擬器都會在 & 處對投影片那種寫法報語法錯誤。同理,根本不存在的二元 ~| 和 ~^ 也一樣 — 當單元縮減時它們是 NOR 與 XNOR,當二元運算子時它們不存在(二元 XNOR 寫成 ^~ 或 ~^,那是合法的,所以這一個是貨真價實的歧義,由剖析器依運算元個數來消解)。講義スライドはどちらの版も y = x1 ~& x2; と書いている。Verilog に二項の ~& 演算子は存在しない。トークン ~& は単一のベクタに作用する単項リダクション NAND としてのみ存在し、~& bus は「bus の全ビットを NAND でまとめる」という意味である。二項の NAND は ~(x1 & x2) と書くか、構造的に nand (y, x1, x2); と書かねばならない。どのシミュレータもスライドの形を & の位置で構文エラーにする。存在しない二項の ~| と ~^ にも同じことが言える。単項リダクションとしては NOR と XNOR だが、二項演算子としては存在しない(二項 XNOR は ^~ または ~^ と書き、これは合法なので、ここだけは本物の曖昧さであり、パーサはオペランド数で解決する)。
Four semantic properties of continuous assignment, each with a consequence:連續指定有四個語意性質,每一個都帶出一個後果:連続代入には四つの意味的性質があり、それぞれに帰結がある。
reg. (§3.5.)目標必須是 net。絕不可以是 reg。(§3.5。)代入先はネットでなければならない。reg は不可。(§3.5。)assign. Compare this with a procedural assignment, which only takes effect when its block runs.它是靜態繫結。用投影片的話說:「沒有任何機制可以消除或改變這個繫結」。沒有任何語法構造能停用一條 assign。把它和程序指定對照:程序指定只有在它所屬的區塊執行時才生效。それは静的な束縛である。スライドの言葉では「その束縛を取り除いたり変えたりする機構は存在しない」。assign を無効化する構文はない。手続き的代入と対比せよ。あちらは所属ブロックが走るときにだけ効く。if, case, or a delay other than an inertial delay specification.右側可以放任何非程序性的東西 — 運算子、函式呼叫、位元選取、部分選取、串接、重複,以及條件運算子。它不能含有 if、case,也不能有慣性延遲規格以外的延遲。右辺には手続き的でないものなら何でも置ける。演算子、関数呼び出し、ビット選択、部分選択、連結、繰り返し、条件演算子。if や case は置けず、慣性遅延指定以外の遅延も置けない。The conditional operator deserves special note, because it is the workhorse of RTL:條件運算子值得特別提一下,因為它是 RTL 的主力:条件演算子は特筆に値する。RTL の主力だからである。
assign y = sel ? a : b; // a 2:1 multiplexer assign bus = oe ? data : 8'bz; // a tri-state driver assign y4 = s[1] ? (s[0] ? d3 : d2) : (s[0] ? d1 : d0); // a 4:1 mux as a tree
With four-value operands the conditional operator does something clever: if sel is x or z, the result is computed bit by bit — each bit position where a and b agree keeps that value, and each position where they differ becomes x. So 1'bx ? 4'b1100 : 4'b1010 gives 4'b1xx0, not 4'bxxxx. This is a small piece of X-accuracy that the if statement does not have.面對四值運算元,條件運算子會做一件聰明的事:如果 sel 是 x 或 z,結果是逐位元算出來的 — a 與 b 相同的每個位元位置保留那個值,兩者不同的每個位置則變成 x。所以 1'bx ? 4'b1100 : 4'b1010 得到 4'b1xx0,而不是 4'bxxxx。這是一小塊 if 敘述所沒有的 X 精確度。四値オペランドに対して、条件演算子は気の利いたことをする。sel が x や z のとき、結果はビットごとに計算される。a と b が一致するビット位置はその値を保ち、食い違う位置は x になる。したがって 1'bx ? 4'b1100 : 4'b1010 は 4'bxxxx ではなく 4'b1xx0 になる。if 文が持たないささやかな X 精度である。
A behavioral description puts procedural statements inside an always block. The slides summarise its key features as procedural constructs for conditional, if-else, case and looping operations; arithmetic, logical, bitwise and reduction operators in expressions; and timing control.行為描述把程序敘述放進 always 區塊裡。投影片把它的主要特色歸納為:用於條件、if-else、case 與迴圈操作的程序性語法構造;運算式中的算術、邏輯、位元與縮減運算子;以及時序控制。動作記述は always ブロックの中に手続き文を置く。スライドはその主な特徴を、条件・if-else・case・ループのための手続き構文、式の中の算術/論理/ビット/リダクション演算子、そしてタイミング制御、とまとめている。
The always block has a sensitivity list (the slide calls it the "sensitive list"), which is the set of events that cause the block to run. Three forms matter:always 區塊有一份敏感清單(sensitivity list)(投影片稱為 sensitive list),也就是會讓這個區塊執行的事件集合。有三種形式值得注意:always ブロックは感度リスト(スライドは sensitive list と呼ぶ)を持つ。ブロックを走らせるイベントの集合である。重要な形式は三つある。
| Form形式形式 | Meaning意義意味 | Infers推斷出什麼何を推論するか |
|---|---|---|
always @ (a or b or c)always @ (a or b or c)always @ (a or b or c) | Run whenever a, b or c changes to any value只要 a、b 或 c 變成任何值就執行a、b、c のいずれかが何らかの値に変化するたびに実行 | Combinational logic, if the list is complete and every output is assigned on every path組合邏輯,前提是清單完整且每條路徑上每個輸出都被指定組合せ論理。ただしリストが完全で、全経路で全出力が代入される場合に限る |
always @ (*) or @*always @ (*) 或 @*always @ (*) または @* | Verilog-2001: run whenever anything read inside the block changes — the tool builds the listVerilog-2001:只要區塊內讀到的任何東西改變就執行 — 清單由工具產生Verilog-2001:ブロック内で読まれるものが変化するたびに実行。リストはツールが作る | The same, but the list cannot be incomplete同上,但清單不可能不完整同じだが、リストが不完全になりえない |
always @ (posedge clk)always @ (posedge clk)always @ (posedge clk) | Run only on the rising transition of clk只在 clk 的上升轉態時執行clk の立ち上がり遷移でのみ実行 | Edge-triggered flip-flops for every variable assigned被指定的每個變數都得到一顆邊緣觸發正反器代入される変数ごとにエッジトリガ・フリップフロップ |
Consider a 2:1 mux written three ways, and suppose sel is accidentally left out of the list.看看一個用三種寫法寫出來的 2:1 多工器,並假設 sel 不小心被漏在清單外。三通りに書いた 2:1 マルチプレクサを見てみよう。そして sel がうっかりリストから漏れたとする。
always @ (a or b) // sel is MISSING
if (sel) y = a;
else y = b;
In simulation: the block does not run when sel changes. y keeps the value it had under the old sel until the next time a or b moves. The waveform shows a multiplexer that switches late — sometimes many nanoseconds late, sometimes never.在模擬中:sel 改變時這個區塊不會執行。y 會保持它在舊的 sel 之下的值,直到下一次 a 或 b 動了為止。波形上看起來就是一顆切換得很晚的多工器 — 有時晚好幾奈秒,有時永遠不切。シミュレーションでは:sel が変わってもブロックは走らない。y は古い sel のもとでの値を、次に a か b が動くまで保つ。波形には切り替わりの遅いマルチプレクサが現れる——数ナノ秒遅れることもあれば、永久に切り替わらないこともある。
In synthesis: the tool reads the body, sees that y is assigned on every path, concludes "combinational", builds a correct 2:1 mux, and emits a warning that the sensitivity list was incomplete. The netlist is right.在合成中:工具讀本體,看到 y 在每條路徑上都被指定,於是判定「這是組合邏輯」,搭出一顆正確的 2:1 多工器,並發出一條「敏感清單不完整」的警告。網表是對的。合成では:ツールは本体を読み、y が全経路で代入されていることを見て「組合せ」と判断し、正しい 2:1 マルチプレクサを作り、感度リストが不完全だという警告を出す。ネットリストは正しい。
Result: RTL simulation and gate-level simulation disagree, and the RTL one is the wrong one. You will spend a day deciding which to trust. Use always @(*) and the problem cannot occur. Lecture 5 treats this as its central cautionary tale.結果:RTL 模擬與閘級模擬彼此不一致,而且錯的那個是 RTL。你會花一整天決定該相信哪一個。只要用 always @(*),這個問題就不可能發生。第 5 講把這件事當成它的核心警世故事。結果:RTL シミュレーションとゲートレベル・シミュレーションが食い違い、しかも誤っているのは RTL のほうである。どちらを信じるかで一日を費やすことになる。always @(*) を使えばこの問題は起こりえない。第 5 講はこれを中心的な教訓として扱う。
Two further details from the slides. First, always @ (A or B) and always @ (A, B) are identical — the comma form was added in Verilog-2001 and the or keyword here has nothing to do with the OR operator. Second, the slides' algorithmic AND example uses a named block and disable:投影片還有兩個細節。第一,always @ (A or B) 與 always @ (A, B) 完全相同 — 逗號形式是 Verilog-2001 加入的,而這裡的 or 關鍵字和 OR 運算子毫無關係。第二,投影片那個演算法式 AND 的範例用到了具名區塊與 disable:スライドからもう二点。第一に、always @ (A or B) と always @ (A, B) は同一である。カンマ形式は Verilog-2001 で加わったもので、ここの or キーワードは OR 演算子とは何の関係もない。第二に、スライドのアルゴリズム的 AND の例は名前付きブロックと disable を使っている。
always @ (x_in) begin: and_loop // a named block y_out = 1; for (k = 0; k <= 3; k = k + 1) if (x_in[k] == 0) begin y_out = 0; disable and_loop; // early exit, like C's "break" end end
disable <block_name> terminates that named block immediately. It is Verilog's break, and it requires the block to be named, which is the only reason and_loop has a label. Many synthesis tools accept disable when it exits a loop with constant bounds, but some do not; it is on the edge of the subset and is best avoided in RTL you intend to ship.disable <block_name> 會立刻終止那個具名區塊。它是 Verilog 的 break,而且要求區塊必須有名字 — 這就是 and_loop 之所以有標籤的唯一理由。許多合成工具在 disable 用來跳出一個有常數邊界的迴圈時會接受它,但有些不接受;它位在可合成子集的邊緣,在你打算交付的 RTL 中最好避開。disable <block_name> はその名前付きブロックを直ちに終了させる。Verilog の break であり、ブロックに名前があることを要求する。and_loop にラベルがある唯一の理由がそれだ。定数境界のループを抜ける用途なら disable を受理する合成ツールは多いが、受理しないものもある。サブセットの縁にある構文であり、出荷する RTL では避けるのが賢い。
module fa_bhv (S, CO, A, B, CI); output S, CO; input A, B, CI; reg S, CO; // required to hold values between events always @ (A or B or CI) begin S = A ^ B ^ CI; // procedural CO = A & B | A & CI | B & CI; // procedural end endmodule
module fa_rtl (S, CO, A, B, CI); output S, CO; input A, B, CI; assign S = A ^ B ^ CI; // continuous assign CO = A & B | A & CI | B & CI; // continuous endmodule
module full_add (S, CO, A, B, CI); output S, CO; input A, B, CI; wire N1, N2, N3; or P1 (CO, N3, N2); half_add HA1 (N1, N2, A, B), // one statement, HA2 (S, N3, N1, CI); // two instances endmodule
module half_add (S, C, X, Y); output S, C; input X, Y; xor (S, X, Y); and (C, X, Y); endmodule
Three things to extract from this comparison.從這個比較可以萃取出三件事。この比較から取り出すべきことが三つある。
The comma form. half_add HA1 (…), HA2 (…); is a single instantiation statement that creates two instances. The module name is written once; each instance gets its own name and its own port list, separated by commas, with one semicolon at the end. Students frequently mis-copy this as two statements and then wonder why the second one errors on a missing module name.逗號形式。half_add HA1 (…), HA2 (…); 是一條實例化敘述,卻建立了兩個實例。模組名稱只寫一次;每個實例有自己的名字與自己的埠列,以逗號分隔,最後只有一個分號。學生常常把它誤抄成兩條敘述,然後納悶第二條為什麼會報缺少模組名稱的錯。カンマ形式。half_add HA1 (…), HA2 (…); は一つのインスタンス化文でありながら二つのインスタンスを作る。モジュール名は一度だけ書き、各インスタンスは自分の名前と自分のポートリストを持ち、カンマで区切られ、末尾のセミコロンは一つである。学生はこれを二つの文に書き写し、二つ目がモジュール名なしでエラーになる理由をよく不思議がる。
Declaration order does not matter. In full_add the or primitive that computes CO from N3 and N2 is written before the half adders that produce N2 and N3. This is perfectly fine — concurrency again. The simulator sorts the dependencies out itself.宣告順序無關緊要。在 full_add 裡,那顆由 N3 與 N2 算出 CO 的 or 基元,寫在產生 N2 與 N3 的半加器之前。這完全沒問題 — 又是並行性。模擬器會自己把相依關係理清楚。宣言順序はどうでもよい。full_add では、N3 と N2 から CO を計算する or プリミティブが、N2 と N3 を生む半加算器より前に書かれている。まったく問題ない——再び並行性である。シミュレータが依存関係を自分で整理する。
Why the carry needs an OR and not an XOR. In a full adder built from two half adders, $C_O = (A\oplus B)\,C_I + AB$. The two terms are mutually exclusive — if $AB=1$ then $A \oplus B = 0$ — so they can never both be 1, which means OR and XOR would both work here. The lecture uses OR because it is the cheaper cell and because the Boolean identity $C_O = AB + AC_I + BC_I$ is the one in the textbook. Knowing that the terms are disjoint is what lets you prove the two-half-adder construction equals the three-term majority function; it is a standard exam question.為什麼進位需要 OR 而不是 XOR。在由兩個半加器搭成的全加器中,$C_O = (A\oplus B)\,C_I + AB$。這兩項是互斥的 — 若 $AB=1$ 則 $A \oplus B = 0$ — 所以它們不可能同時為 1,這表示這裡用 OR 或 XOR 都行。本講用 OR,是因為它是比較便宜的 cell,也因為教科書上的布林恆等式寫的是 $C_O = AB + AC_I + BC_I$。正因為知道這兩項互斥,你才能證明兩個半加器的構造等於三項的多數函數;這是標準考題。なぜキャリーには XOR ではなく OR が要るのか。半加算器二つで組んだ全加算器では $C_O = (A\oplus B)\,C_I + AB$ である。二つの項は互いに排反で、$AB=1$ なら $A \oplus B = 0$ だから、両方が 1 になることはない。つまりここでは OR でも XOR でも動く。講義が OR を使うのは、そちらが安いセルであり、教科書のブール恒等式が $C_O = AB + AC_I + BC_I$ だからである。二項が排反だと知っていることが、半加算器二つの構成が三項の多数決関数に等しいことの証明を可能にする。定番の試験問題だ。
The lecture's half adder is a small enough circuit to look at every level simultaneously. The schematic: a and b feed an XOR producing sum, and a NAND producing an intermediate c_out_bar, which an inverter turns into c_out.本講的半加器小到可以同時從每一個層次來看。電路圖是:a 與 b 餵進一顆 XOR 產生 sum,同時餵進一顆 NAND 產生中間訊號 c_out_bar,再由一顆反相器把它變成 c_out。この講義の半加算器は、すべての階層を同時に眺められるほど小さい。回路図はこうだ。a と b が XOR に入って sum を生み、同時に NAND に入って中間信号 c_out_bar を生み、それをインバータが c_out に変える。
module Add_half (sum, c_out, a, b); // name, ports input a, b; // port modes output sum, c_out; wire c_out_bar; // internal signal xor (sum, a, b); // primitives nand (c_out_bar, a, b); not (c_out, c_out_bar); endmodule
module Add_half (sum, c_out, a, b);
input a, b;
output sum, c_out;
reg sum, c_out;
always @ (a or b)
begin
sum = a ^ b; // exclusive or
c_out = a & b; // and
end
endmodulemodule add_half (sum, c_out, a, b); input a, b; output sum, c_out; assign {c_out, sum} = a + b; // concatenation as an assignment target endmodule
This last one repays study. a and b are 1-bit, so a + b is an addition whose natural result range is 0 to 2 — two bits. The left-hand side {c_out, sum} is a concatenation of two 1-bit nets, forming a 2-bit target with c_out as the more significant bit. Verilog's expression-width rule then sizes the addition to 2 bits, so the carry lands in c_out instead of vanishing. Change the left side to just sum and the addition is done in 1 bit, 1 + 1 gives 0, and the carry is silently lost. The width of the target determines the width of the arithmetic. Internalise this now; it is the root cause of more silent functional bugs than any other feature of the language.最後這一種值得細讀。a 與 b 都是 1 位元,所以 a + b 的自然結果範圍是 0 到 2 — 兩個位元。左側的 {c_out, sum} 是兩條 1 位元 net 的串接,構成一個 2 位元的目標,其中 c_out 是較高位。於是 Verilog 的運算式位寬規則把這個加法定成 2 位元,進位就落進 c_out 而不會消失。把左側改成只有 sum,加法就以 1 位元進行,1 + 1 得到 0,進位安靜地不見了。目標的位寬決定算術的位寬。現在就把這句話內化;它是這個語言中造成最多安靜功能臭蟲的根源,沒有之一。最後のものは精読に値する。a と b は 1 ビットなので、a + b の自然な結果の範囲は 0 から 2——2 ビットである。左辺の {c_out, sum} は 1 ビットのネット二本の連結で、c_out を上位とする 2 ビットの代入先を作る。すると Verilog の式幅規則が加算を 2 ビットに決めるので、キャリーは消えずに c_out へ落ちる。左辺を sum だけにすれば加算は 1 ビットで行われ、1 + 1 は 0 になり、キャリーは黙って失われる。代入先の幅が算術の幅を決める。いま身体に入れておけ。この言語のどの機能より多くの、静かな機能バグの根源である。
The same idiom scales. For $n$-bit operands:同樣的慣用寫法可以放大。對 $n$ 位元運算元:同じ書き方はそのまま拡張できる。$n$ ビットのオペランドに対しては:
module adder_n #(parameter N = 16) (sum, c_out, a, b, c_in);
input [N-1:0] a, b;
input c_in;
output [N-1:0] sum;
output c_out;
assign {c_out, sum} = a + b + c_in; // N+1 bit target → N+1 bit addition
endmodule
This is the adder you should write in practice: one line, no carry chain to get wrong, and the synthesiser is free to choose a ripple-carry, carry-select, carry-look-ahead or parallel-prefix implementation according to the timing constraint you gave it. Section 7 builds the same function structurally from 16 full adders, which is instructive and also strictly worse hardware — writing out the ripple-carry structure by hand forbids the tool from choosing anything faster.這才是你實務上該寫的加法器:一行、沒有會寫錯的進位鏈,而且合成器可以依你給的時序限制自由選擇漣波進位、進位選擇、超前進位或並行前綴的實作。第 7 節會用 16 顆全加器把同一個功能結構化地搭出來,那很有教學意義,但也是嚴格意義上更差的硬體 — 把漣波進位結構親手寫出來,等於禁止工具挑選任何更快的做法。実務で書くべき加算器はこれである。一行で済み、間違えるキャリーチェーンもなく、与えたタイミング制約に応じて合成器がリップルキャリー、キャリーセレクト、キャリー先見、並列プレフィックスのどれでも自由に選べる。第 7 節は同じ機能を全加算器 16 個で構造的に組み立てる。教育的ではあるが、厳密に劣るハードウェアでもある。リップルキャリー構造を手書きすることは、より速い実装を選ぶことをツールに禁じることだからだ。
The slides show a 4-input AND three ways, which is the cleanest demonstration of the style spectrum in the whole lecture.投影片用三種寫法呈現一顆四輸入 AND,這是整講中對風格光譜最乾淨的一次示範。スライドは 4 入力 AND を三通りで示す。講義全体を通じて、スタイルのスペクトルを最も明快に見せる場面である。
module and4_rtl (y_out, x_in); input [3:0] x_in; output y_out; assign y_out = & x_in; // reduction AND // alternative, bitwise: // assign y_out = x_in[3] & x_in[2] // & x_in[1] & x_in[0]; endmodule
module and4_alg (y_out, x_in);
input [3:0] x_in;
output y_out;
reg y_out;
integer k;
always @ (x_in)
begin: and_loop
y_out = 1;
for (k = 0; k <= 3; k = k + 1)
if (x_in[k] == 0)
begin
y_out = 0;
disable and_loop;
end
end
endmoduleThe crucial distinction is between the unary reduction operator and the binary bitwise operator, which are spelled with the same character.關鍵的區分在於單元縮減運算子與二元位元運算子,兩者用同一個字元書寫。決定的な区別は単項リダクション演算子と二項ビット演算子の間にあり、両者は同じ文字で書かれる。
| Expression運算式式 | Operator運算子演算子 | Operands運算元オペランド | Result width結果位寬結果の幅 | Meaning意義意味 |
|---|---|---|---|---|
& x_in | Unary reduction AND單元縮減 AND単項リダクション AND | One 4-bit vector一個 4 位元向量4 ビットのベクタ一つ | 1 bit1 位元1 ビット | AND all four bits together把四個位元全部 AND 起來4 ビットすべてを AND でまとめる |
a & b | Binary bitwise AND二元位元 AND二項ビット AND | Two vectors兩個向量ベクタ二つ | max(width a, width b)max(a 的位寬, b 的位寬)max(a の幅, b の幅) | AND bit 0 with bit 0, bit 1 with bit 1, …第 0 位元和第 0 位元 AND、第 1 位元和第 1 位元 AND、…ビット 0 どうし、ビット 1 どうし…を AND |
a && b | Logical AND邏輯 AND論理 AND | Two expressions兩個運算式式二つ | 1 bit1 位元1 ビット | "Is a non-zero AND b non-zero" — collapses each side to a truth value first「a 非零且 b 非零嗎」 — 先把兩側各自塌縮成一個真假值「a が非ゼロかつ b が非ゼロか」。まず両辺をそれぞれ真偽値へ畳む |
The parser distinguishes the first two by counting operands. The third, &&, is a different token entirely and a different operation: 4'b0010 & 4'b0001 is 4'b0000, while 4'b0010 && 4'b0001 is 1'b1, because both operands are non-zero. Mixing these up gives code that is correct for 1-bit signals and wrong the moment someone widens a bus — a classic maintenance bug. The complete reduction family is & (AND), ~& (NAND), | (OR), ~| (NOR), ^ (XOR, i.e. odd parity), ~^ or ^~ (XNOR, even parity). Reduction XOR is the idiomatic way to compute a parity bit: assign parity = ^ data;.剖析器靠數運算元個數來區分前兩者。第三個 && 完全是另一個記號、另一種運算:4'b0010 & 4'b0001 是 4'b0000,而 4'b0010 && 4'b0001 是 1'b1,因為兩個運算元都非零。把它們搞混,寫出來的程式碼對 1 位元訊號是對的,而在有人把匯流排加寬的那一刻就錯了 — 經典的維護期臭蟲。完整的縮減家族是 &(AND)、~&(NAND)、|(OR)、~|(NOR)、^(XOR,也就是奇同位)、~^ 或 ^~(XNOR,偶同位)。縮減 XOR 是算同位位元的慣用寫法:assign parity = ^ data;。パーサは最初の二つをオペランド数で区別する。三つ目の && はまったく別のトークンであり別の演算である。4'b0010 & 4'b0001 は 4'b0000 だが、4'b0010 && 4'b0001 は両辺とも非ゼロなので 1'b1 になる。これを取り違えたコードは 1 ビット信号では正しく、誰かがバスを広げた瞬間に誤る——典型的な保守バグだ。リダクションの一族は &(AND)、~&(NAND)、|(OR)、~|(NOR)、^(XOR、奇パリティ)、~^ または ^~(XNOR、偶パリティ)である。パリティビットの計算にはリダクション XOR が慣用である。assign parity = ^ data;。
All three descriptions of and4 synthesise to the same thing: a 4-input AND, probably realised as a NAND4 plus an inverter, or as two NAND2s and a NOR2, depending on the library. But they are not equally good source code. The reduction form is one token and obviously correct. The bitwise form is explicit and easy to mistype (x_in[1] written twice is a bug the compiler cannot see). The loop form is twelve lines, uses a construct on the edge of the synthesisable subset, and would still be twelve lines if the vector were 64 bits wide — which is its only genuine advantage, and one that a parameterised reduction does not need.and4 的三種描述合成出來是同一個東西:一顆四輸入 AND,大概會實現成一顆 NAND4 加一顆反相器,或兩顆 NAND2 加一顆 NOR2,視函式庫而定。但它們作為原始碼並不一樣好。縮減形式只有一個記號,而且明擺著是對的。位元形式很明確,也很容易打錯(把 x_in[1] 寫兩次,是編譯器看不出來的臭蟲)。迴圈形式有十二行,用到位於可合成子集邊緣的語法,而且就算向量寬 64 位元它還是十二行 — 那是它唯一真正的優勢,而這個優勢在參數化的縮減寫法面前根本不需要。and4 の三つの記述は同じものに合成される。4 入力 AND であり、ライブラリ次第で NAND4 とインバータ、あるいは NAND2 二つと NOR2 として実現されるだろう。しかしソースコードとしての出来は同じではない。リダクション形式はトークン一つで、見るからに正しい。ビット形式は明示的だが打ち間違えやすい(x_in[1] を二度書く誤りはコンパイラに見えない)。ループ形式は十二行あり、合成可能サブセットの縁にある構文を使い、ベクタが 64 ビットでもやはり十二行である——それが唯一の本当の利点だが、パラメータ化したリダクションにはその利点すら要らない。
Problem: The function is $y = (a \oplus b)\,c + \bar a \bar b$. (a) Write it in RTL style. (b) Write it structurally, using only xor, and, nor and or primitives, naming every instance. (c) Write it behaviorally with an always block, using a correct sensitivity list. (d) State what the three synthesise to. (e) Now suppose the behavioral version is written always @(a or b) — what is the simulated and the synthesised behaviour?題目:函數為 $y = (a \oplus b)\,c + \bar a \bar b$。(a) 用 RTL 風格寫出來。(b) 只用 xor、and、nor、or 基元結構化地寫出來,並替每個實例命名。(c) 用 always 區塊以行為描述寫出來,敏感清單要正確。(d) 說明三者各自合成成什麼。(e) 現在假設行為版本寫成 always @(a or b) — 模擬行為與合成行為各是什麼?問題:関数は $y = (a \oplus b)\,c + \bar a \bar b$ である。(a) RTL スタイルで書け。(b) xor、and、nor、or プリミティブだけを使い、各インスタンスに名前を付けて構造記述せよ。(c) 正しい感度リストを持つ always ブロックで動作記述せよ。(d) 三つがそれぞれ何に合成されるかを述べよ。(e) 動作記述版を always @(a or b) と書いたとき、シミュレーションと合成の振る舞いはどうなるか。
(a)(a)(a)
assign y = ((a ^ b) & c) | (~a & ~b);
(b) Note ~a & ~b is exactly nor(a,b) by De Morgan, which saves two inverters:(b) 注意由德摩根定律,~a & ~b 正好就是 nor(a,b),這省掉兩顆反相器:(b) ド・モルガンにより ~a & ~b はちょうど nor(a,b) であり、インバータを二つ節約できる点に注意。
module f_str (y, a, b, c); input a, b, c; output y; wire t1, t2, t3; xor G1 (t1, a, b); // a ^ b and G2 (t2, t1, c); // (a^b) & c nor G3 (t3, a, b); // ~a & ~b or G4 (y, t2, t3); endmodule
(c)(c)(c)
reg y;
always @ (*) // or @(a or b or c)
y = ((a ^ b) & c) | (~a & ~b);
(d) All three give the same netlist. The synthesiser flattens each to a truth table over $(a,b,c)$ — minterms 000, 001, 011, 101 — and then re-factors it for the target library and timing constraint. It is entirely free to produce a structure that looks nothing like (b); if the library has a compact AOI or MUX cell that covers the function, it will use it. You specified the function, not the gates, in (a) and (c); in (b) you specified gates, and the tool is still allowed to re-map them unless you tell it not to with a dont_touch attribute.(d) 三者給出同一份網表。合成器把每一種都攤平成 $(a,b,c)$ 上的真值表 — 最小項 000、001、011、101 — 然後再針對目標函式庫與時序限制重新因式分解。它完全有權產生一個和 (b) 毫不相似的結構;如果函式庫裡有一顆緊湊的 AOI 或 MUX cell 能覆蓋這個函數,它就會用。在 (a) 與 (c) 裡你指定的是函數,不是閘;在 (b) 裡你指定了閘,但除非你用 dont_touch 屬性叫它別動,工具仍然可以重新映射它們。(d) 三つとも同じネットリストになる。合成器はそれぞれを $(a,b,c)$ 上の真理値表——最小項 000、001、011、101——へ平坦化し、ターゲットライブラリとタイミング制約に合わせて因数分解し直す。(b) とまるで似ない構造を出しても構わない。関数を覆う小さな AOI セルや MUX セルがライブラリにあれば、それを使う。(a) と (c) で君が指定したのは関数であってゲートではない。(b) ではゲートを指定したが、dont_touch 属性で禁じない限り、ツールは依然としてそれを再マッピングしてよい。
(e) Simulation: y fails to update when c changes, so it holds a stale value until the next a or b transition — a late or missing edge in the waveform. Synthesis: the tool sees y assigned unconditionally on the only path through the block, infers combinational logic, builds the correct function, and warns about the incomplete list. RTL and netlist disagree. This exact mismatch is the reason always @(*) exists.(e) 模擬:c 改變時 y 不會更新,所以它一直抓著舊值,直到下一次 a 或 b 轉態 — 波形上就是遲到或根本不出現的邊緣。合成:工具看到 y 在穿過這個區塊的唯一路徑上被無條件指定,推斷出組合邏輯,搭出正確的函數,並對不完整的清單發出警告。RTL 與網表彼此不一致。正是這個不一致,造就了 always @(*) 的存在。(e) シミュレーション:c が変わっても y は更新されず、次に a か b が遷移するまで古い値を抱え続ける——波形には遅れたエッジ、あるいは出てこないエッジが現れる。合成:ツールはブロックを通る唯一の経路で y が無条件に代入されているのを見て組合せ論理を推論し、正しい関数を作り、不完全なリストについて警告する。RTL とネットリストが食い違う。この食い違いこそ always @(*) が存在する理由である。
The comparator is the lecture's set-piece, and it earns its place because it is the smallest circuit for which the three styles produce visibly different source code and identical hardware. Work through it slowly; almost every exam question about Lecture 1 is a variation on it.比較器是本講的重頭戲,而它之所以配得上這個位置,是因為它是三種風格會產生明顯不同的原始碼、卻產生相同硬體的最小電路。請慢慢讀過去;幾乎每一道關於第 1 講的考題都是它的變形。比較器はこの講義の目玉であり、その地位にふさわしい。三つのスタイルが目に見えて異なるソースを生みながら同一のハードウェアを生む、最小の回路だからである。ゆっくり読み通すこと。第 1 講に関する試験問題のほとんどはこれの変形である。
Compare two 2-bit unsigned numbers $A = \{A_1, A_0\}$ and $B = \{B_1, B_0\}$ and produce three outputs: A_lt_B ($A < B$), A_gt_B ($A > B$) and A_eq_B ($A = B$). The block has four inputs and three outputs, so the truth table has 16 rows and the three outputs are mutually exclusive and jointly exhaustive — exactly one of them is 1 in every row. That property is not an accident of the specification; it is a theorem about a total order, and it is what makes the structural implementation in §6.3 possible.比較兩個 2 位元無號數 $A = \{A_1, A_0\}$ 與 $B = \{B_1, B_0\}$,產生三個輸出:A_lt_B($A < B$)、A_gt_B($A > B$)與 A_eq_B($A = B$)。這個區塊有四個輸入、三個輸出,所以真值表有 16 列,而三個輸出彼此互斥且聯合窮盡 — 每一列中恰好有一個是 1。那個性質不是規格上的偶然;它是關於全序的一條定理,也正是 §6.3 的結構化實作之所以可行的原因。2 ビットの符号なし数 $A = \{A_1, A_0\}$ と $B = \{B_1, B_0\}$ を比較し、三つの出力 A_lt_B($A < B$)、A_gt_B($A > B$)、A_eq_B($A = B$)を出す。入力四つ、出力三つなので真理値表は 16 行であり、三つの出力は互いに排反かつ全体を尽くす——どの行でもちょうど一つだけが 1 になる。この性質は仕様の偶然ではない。全順序についての定理であり、§6.3 の構造的実装を可能にしているものである。
| $A_1A_0$ | $B_1B_0$ | $A$ | $B$ | ltltlt | gtgtgt | eqeqeq |
|---|---|---|---|---|---|---|
| 00 | 00 | 0 | 0 | 0 | 0 | 1 |
| 00 | 01 | 0 | 1 | 1 | 0 | 0 |
| 00 | 10 | 0 | 2 | 1 | 0 | 0 |
| 00 | 11 | 0 | 3 | 1 | 0 | 0 |
| 01 | 00 | 1 | 0 | 0 | 1 | 0 |
| 01 | 01 | 1 | 1 | 0 | 0 | 1 |
| 01 | 10 | 1 | 2 | 1 | 0 | 0 |
| 01 | 11 | 1 | 3 | 1 | 0 | 0 |
| 10 | 00 | 2 | 0 | 0 | 1 | 0 |
| 10 | 01 | 2 | 1 | 0 | 1 | 0 |
| 10 | 10 | 2 | 2 | 0 | 0 | 1 |
| 10 | 11 | 2 | 3 | 1 | 0 | 0 |
| 11 | 00 | 3 | 0 | 0 | 1 | 0 |
| 11 | 01 | 3 | 1 | 0 | 1 | 0 |
| 11 | 10 | 3 | 2 | 0 | 1 | 0 |
| 11 | 11 | 3 | 3 | 0 | 0 | 1 |
You can read the equations off a K-map, but the structural derivation is more memorable and generalises to any width.你可以從卡諾圖上把式子讀出來,但結構式的推導更好記,而且能推廣到任意位寬。カルノー図から式を読み取ってもよいが、構造的に導くほうが記憶に残り、任意の幅へ一般化できる。
A_lt_B from first principles從第一原理推導 A_lt_BA_lt_B を第一原理から導くNote the symmetry: A_gt_B is A_lt_B with $A$ and $B$ swapped, exactly as you would expect from $A > B \iff B < A$. And A_eq_B is the sum of the four minterms where $A$ and $B$ match, which factors beautifully:注意這個對稱性:A_gt_B 就是把 A_lt_B 裡的 $A$ 與 $B$ 對調,正如你從 $A > B \iff B < A$ 所預期的。而 A_eq_B 是 $A$ 與 $B$ 相符的那四個最小項之和,它可以漂亮地因式分解:対称性に注意せよ。A_gt_B は A_lt_B の $A$ と $B$ を入れ替えたものであり、$A > B \iff B < A$ から予想されるとおりである。そして A_eq_B は $A$ と $B$ が一致する四つの最小項の和であり、見事に因数分解できる。
That is the form the structural implementation uses — two XNOR gates and an AND — and it is linear in the word width, whereas the four-minterm sum is exponential. For a 32-bit comparator the minterm form has $2^{32}$ terms and the XNOR form has 32 gates. Finding a factorisation like this is exactly what a synthesis tool does automatically, and seeing by hand how much it buys you is the point of the exercise.那正是結構化實作所採用的形式 — 兩顆 XNOR 加一顆 AND — 而且它對字長是線性的,而四個最小項的和則是指數的。對一個 32 位元比較器,最小項形式有 $2^{32}$ 項,XNOR 形式則只有 32 顆閘。找到這樣一個因式分解,正是合成工具自動在做的事;親手看一次它省下多少,就是這個練習的意義。それが構造的実装の採る形——XNOR 二つと AND 一つ——であり、語長に対して線形である。一方、四つの最小項の和は指数的だ。32 ビット比較器なら最小項形は $2^{32}$ 項、XNOR 形は 32 ゲートである。こうした因数分解を見つけるのはまさに合成ツールが自動で行うことであり、それがどれだけ得かを手で確かめることがこの演習の眼目である。
module compare_2_str (A_lt_B, A_gt_B, A_eq_B, A0, A1, B0, B1); input A0, A1, B0, B1; output A_lt_B, A_gt_B, A_eq_B; wire w1, w2, w3, w4, w5, w6, w7; or (A_lt_B, w1, w2, w3); // sum of the three lt terms nor (A_gt_B, A_lt_B, A_eq_B); // neither less nor equal and (A_eq_B, w4, w5); // (A1 == B1) AND (A0 == B0) and (w1, w6, B1); // ~A1 · B1 and (w2, w6, w7, B0); // ~A1 · ~A0 · B0 and (w3, w7, B0, B1); // ~A0 · B1 · B0 not (w6, A1); // w6 = ~A1 not (w7, A0); // w7 = ~A0 xnor (w4, A1, B1); // A1 == B1 xnor (w5, A0, B0); // A0 == B0 endmodule
Three observations that students regularly miss.三個學生經常漏看的觀察。学生が決まって見落とす三点。
The output-first rule again. In and (w2, w6, w7, B0); the first terminal w2 is the output and the remaining three are inputs. Read any of these lines right-to-left and you get the wrong circuit.又是輸出在前的規則。在 and (w2, w6, w7, B0); 裡,第一個端子 w2 是輸出,其餘三個是輸入。把這幾行裡的任何一行由右往左讀,你得到的就是錯的電路。またしても出力優先の規則。and (w2, w6, w7, B0); では最初の端子 w2 が出力で、残りの三つが入力である。この手の行を右から左に読めば、別の回路ができあがる。
A_gt_B costs one gate, not three. Instead of building the three-term sum-of-products for greater-than, the design exploits mutual exclusivity: exactly one of the three outputs is 1, so $gt = \overline{lt + eq}$, which is a single NOR. This is a genuine optimisation — it replaces one NOT, three ANDs and one OR with one NOR — and it is the kind of thing a human notices from the specification and a synthesiser notices from the truth table. They arrive at the same place by different routes.A_gt_B 只花一顆閘,不是三顆。這個設計沒有替「大於」搭出三項的積之和,而是利用互斥性:三個輸出中恰好有一個是 1,所以 $gt = \overline{lt + eq}$,就是單單一顆 NOR。這是一個貨真價實的最佳化 — 它用一顆 NOR 取代了一顆 NOT、三顆 AND 和一顆 OR — 而且是人從規格看出來、合成器從真值表看出來的那種東西。兩者由不同路徑抵達同一個地方。A_gt_B はゲート三つではなく一つで済む。この設計は「より大きい」に三項の積和を組むのではなく、排反性を利用する。三つの出力のうちちょうど一つが 1 なので $gt = \overline{lt + eq}$、すなわち NOR 一つである。これは本物の最適化であり——NOT 一つ、AND 三つ、OR 一つを NOR 一つで置き換える——人間が仕様から気づき、合成器が真理値表から気づく類のものだ。両者は別の道で同じ場所に着く。
The module's ports are declared in the order A0, A1, B0, B1, while the RTL version in §6.4 declares them A1, A0, B1, B0. If you instantiate both with the same positional port list, one of them will compare the wrong bits and you will see a comparator that is right for 00 and 11 and wrong for 01 and 10. Nothing in the language warns you. This is the single strongest argument for named port binding (§7.2).這個模組的埠是以 A0, A1, B0, B1 的順序宣告的,而 §6.4 的 RTL 版本宣告的順序是 A1, A0, B1, B0。如果你用同一份位置式埠列去實例化兩者,其中一個就會比較到錯的位元,而你會看到一個對 00 與 11 正確、對 01 與 10 錯誤的比較器。語言裡沒有任何東西會警告你。這是支持具名埠連接(§7.2)最強的一個論證。このモジュールのポートは A0, A1, B0, B1 の順に宣言されているのに対し、§6.4 の RTL 版は A1, A0, B1, B0 の順で宣言する。同じ位置指定ポートリストで両方をインスタンス化すれば、片方は誤ったビットを比較し、00 と 11 では正しく 01 と 10 では誤る比較器ができあがる。言語は何も警告しない。これが名前指定ポート接続(§7.2)を支持する最強の論拠である。
module compare_2a (A_lt_B, A_gt_B, A_eq_B, A1, A0, B1, B0);
input A1, A0, B1, B0;
output A_lt_B, A_gt_B, A_eq_B;
assign A_lt_B = (~A1) & B1 | (~A1) & (~A0) & B0 | (~A0) & B1 & B0;
assign A_gt_B = A1 & (~B1) | A0 & (~B1) & (~B0) | A1 & A0 & (~B0);
assign A_eq_B = (~A1) & (~A0) & (~B1) & (~B0) | (~A1) & A0 & (~B1) & B0
| A1 & A0 & B1 & B0 | A1 & (~A0) & B1 & (~B0);
endmodule
This is a direct transcription of the three equations, and it relies on Verilog's operator precedence: & binds tighter than |, so a & b | c & d parses as (a & b) | (c & d), the sum-of-products reading you want. The parenthesised (~A1) is not necessary — ~ binds tighter than everything — but it is good practice, because Verilog's precedence table has 15 levels and nobody remembers all of them. The one you genuinely must know is that the comparison and equality operators bind more loosely than the bitwise operators, which is why if (a & b == c) means if (a & (b == c)) and almost never what the author intended.這是三條方程式的直接轉寫,而它依賴 Verilog 的運算子優先順序:& 比 | 結合得更緊,所以 a & b | c & d 會剖析成 (a & b) | (c & d),也就是你要的積之和讀法。加括號的 (~A1) 其實不必要 — ~ 比任何東西都結合得緊 — 但這是好習慣,因為 Verilog 的優先順序表有 15 層,沒有人記得住全部。你真正必須記住的那一條是:比較與等值運算子比位元運算子結合得更鬆,這就是為什麼 if (a & b == c) 的意思是 if (a & (b == c)),而那幾乎從來不是作者想要的。これは三つの式をそのまま書き写したもので、Verilog の演算子優先順位に依存している。& は | より強く結合するので、a & b | c & d は (a & b) | (c & d) と解析され、望みどおりの積和の読みになる。括弧付きの (~A1) は不要だが——~ は何よりも強く結合する——良い習慣である。Verilog の優先順位表は 15 段あり、全部を覚えている者はいない。本当に覚えるべき一点は、比較演算子と等価演算子はビット演算子より弱く結合することだ。だから if (a & b == c) は if (a & (b == c)) を意味し、それは書き手の意図とまず一致しない。
module compare_2b (A_lt_B, A_gt_B, A_eq_B,
A1, A0, B1, B0);
input A1, A0, B1, B0;
output A_lt_B, A_gt_B, A_eq_B;
assign A_lt_B = ({A1, A0} < {B1, B0});
assign A_gt_B = ({A1, A0} > {B1, B0});
assign A_eq_B = ({A1, A0} == {B1, B0});
endmodulemodule compare_2ca (A_lt_B, A_gt_B, A_eq_B, A, B); input [1:0] A, B; output A_lt_B, A_gt_B, A_eq_B; assign A_lt_B = (A < B); assign A_gt_B = (A > B); assign A_eq_B = (A == B); endmodule
These are where the abstraction finally pays. Approach 1 keeps the scalar interface of the slides' earlier versions but builds a 2-bit vector on the fly with the concatenation operator { } and hands it to the relational operators. Approach 2 declares the ports as vectors in the first place, which is what you would actually do.抽象到這裡終於開始回本。做法 1 保留了投影片先前版本的純量介面,但用串接運算子 { } 當場組出一個 2 位元向量,交給關係運算子處理。做法 2 則一開始就把埠宣告成向量,那才是你實務上真正會做的。抽象がようやく元を取るのがここである。方法 1 はスライドの旧版と同じスカラなインタフェースを保ちつつ、連結演算子 { } でその場で 2 ビットのベクタを組み、関係演算子に渡す。方法 2 は最初からポートをベクタとして宣言する。実務で実際にそうするのはこちらだ。
Both are four-line modules that are completely width-independent in spirit. To make compare_2ca an 8-bit comparator you change exactly one character set: input [7:0] A, B;. Nothing else moves. Compare that with the structural version, where going to 8 bits means re-deriving the Boolean equations, drawing a new schematic, and writing about forty gate instantiations, every one of which is an opportunity for a typo. This is the entire argument for RTL as a design methodology, compressed into one example.兩者都是精神上完全與位寬無關的四行模組。要把 compare_2ca 變成 8 位元比較器,你只需要改動一組字元:input [7:0] A, B;。其他什麼都不必動。拿它和結構化版本比較:要做成 8 位元,意味著重新推導布林方程式、畫一張新的電路圖、寫大約四十個閘實例化,而每一個都是一次打錯字的機會。這就是「以 RTL 作為設計方法論」的全部論證,壓縮成一個例子。どちらも精神において完全に幅非依存な四行のモジュールである。compare_2ca を 8 ビット比較器にするには、変えるのは一組の文字だけだ。input [7:0] A, B;。ほかは何も動かない。構造記述版と比べよ。8 ビットにするとは、ブール式を導き直し、新しい回路図を描き、四十ほどのゲートをインスタンス化することであり、その一つ一つが誤記の機会である。設計方法論としての RTL を支持する論証のすべてが、この一例に凝縮されている。
If either operand of <, >, <=, >= contains an x or z, the result is 1'bx — not 0, not 1. So a comparator whose inputs are partly unknown drives all three outputs to x, which is honest. The structural version does not behave this way: because of controlling values, some of its gates resolve and others do not, so with $A = 1x$ and $B = 00$ the structural A_gt_B may come out a definite 1 (correct, since $A \geq 2 > 0$ regardless of the unknown bit) while compare_2ca reports x. The gate-level model is less pessimistic than the RTL model here. That is a real and slightly uncomfortable fact about four-value simulation, and it is one reason post-synthesis gate-level simulation sometimes shows fewer X's than RTL rather than more.如果 <、>、<=、>= 的任一運算元含有 x 或 z,結果就是 1'bx — 不是 0,也不是 1。所以一個輸入部分未知的比較器會把三個輸出全部驅動成 x,而那是誠實的。結構化版本不是這樣:由於控制值的存在,它有些閘會解析出結果、有些不會,所以在 $A = 1x$、$B = 00$ 時,結構化的 A_gt_B 可能得出確定的 1(這是對的,因為不論那個未知位元是什麼,$A \geq 2 > 0$),而 compare_2ca 卻回報 x。在這裡,閘級模型比 RTL 模型更不悲觀。這是四值模擬一個真實而略微令人不安的事實,也是合成後閘級模擬有時顯示的 X 比 RTL 更少而非更多的原因之一。<、>、<=、>= のいずれかのオペランドが x や z を含めば、結果は 0 でも 1 でもなく 1'bx になる。したがって入力の一部が不定な比較器は三つの出力をすべて x に駆動する。これは正直な振る舞いだ。構造記述版はそうならない。支配値があるため、あるゲートは値を確定し別のゲートは確定しない。$A = 1x$、$B = 00$ のとき、構造記述の A_gt_B は確定した 1 を出しうる(不定ビットが何であれ $A \geq 2 > 0$ なので正しい)のに、compare_2ca は x を報告する。ここではゲートレベル・モデルのほうが RTL モデルより悲観的でない。これは四値シミュレーションについての現実の、やや居心地の悪い事実であり、合成後のゲートレベル・シミュレーションが RTL より X を多くではなく少なく示すことがある理由の一つである。
Also note that A and B are declared with input [1:0], i.e. unsigned. The relational operators on unsigned operands do an unsigned comparison, so 2'b11 > 2'b01 is true (3 > 1). If you wanted two's-complement comparison you would need input signed [1:0] (Verilog-2001) and then 2'b11 is $-1$ and the same comparison is false. Signedness is a declaration property, not a property of the operator, and getting it wrong in a DSP datapath produces a filter that works for small signals and explodes for large ones.另外注意 A 與 B 是用 input [1:0] 宣告的,也就是無號。關係運算子對無號運算元做的是無號比較,所以 2'b11 > 2'b01 為真(3 > 1)。如果你要的是二補數比較,就必須寫 input signed [1:0](Verilog-2001),那時 2'b11 是 $-1$,同樣的比較就為假。有號性是宣告的屬性,不是運算子的屬性;在 DSP 資料路徑裡把它弄錯,會做出一個小訊號正常、大訊號爆掉的濾波器。また A と B が input [1:0]、すなわち符号なしで宣言されている点にも注意。関係演算子は符号なしオペランドには符号なし比較を行うので、2'b11 > 2'b01 は真である(3 > 1)。二の補数の比較が欲しければ input signed [1:0](Verilog-2001)が必要で、そのとき 2'b11 は $-1$ となり同じ比較は偽になる。符号の有無は宣言の属性であって演算子の属性ではない。DSP のデータパスでこれを誤ると、小信号では動き大信号で破綻するフィルタができる。
module compare_2_algo (A_lt_B, A_gt_B, A_eq_B, A, B); input [1:0] A, B; output A_lt_B, A_gt_B, A_eq_B; reg A_lt_B, A_gt_B, A_eq_B; always @ (A or B) // behavior and event expression begin A_lt_B = 0; // default assignments — A_gt_B = 0; // these are what prevent latches A_eq_B = 0; if (A == B) A_eq_B = 1; else if (A > B) A_gt_B = 1; else A_lt_B = 1; end endmodule
This is the version that teaches the most, because of those three leading zeros.這個版本教的最多,原因就在開頭那三個 0 的指定。最も多くを教えてくれるのがこの版である。理由は冒頭の三つのゼロ代入にある。
Trace the block's control flow. There are three paths through the if/else if/else chain. On the "equal" path, only A_eq_B is assigned inside the chain. If the three opening lines were absent, then on that path A_lt_B and A_gt_B would retain their previous values — and "retains its previous value" is precisely the definition of a transparent latch.追一遍這個區塊的控制流程。穿過 if/else if/else 鏈有三條路徑。在「相等」那條路徑上,鏈內只有 A_eq_B 被指定。如果開頭那三行不存在,那麼在那條路徑上 A_lt_B 與 A_gt_B 就會保留它們先前的值 — 而「保留先前的值」正是透通閂鎖的定義。ブロックの制御フローを追ってみよ。if/else if/else の連鎖を通る経路は三つある。「等しい」経路では、連鎖の中で代入されるのは A_eq_B だけだ。冒頭の三行がなければ、その経路で A_lt_B と A_gt_B は以前の値を保つ——そして「以前の値を保つ」とはまさにトランスペアレント・ラッチの定義である。
With the three defaults present, every variable is assigned on every path (first to 0, then possibly to 1), nothing is remembered, and the block is purely combinational. Assign a default to every output at the top of every combinational always block. It is one line per signal and it eliminates an entire failure mode.有了那三個預設指定,每個變數在每條路徑上都被指定了(先指定成 0,之後可能再指定成 1),沒有東西被記住,於是這個區塊是純組合的。在每一個組合式 always 區塊的開頭,替每一個輸出指定一個預設值。每條訊號只要一行,就消掉了一整種失效模式。三つの既定値があれば、すべての変数がすべての経路で代入され(まず 0、場合により 1)、何も記憶されず、ブロックは純粋に組合せになる。組合せ always ブロックの先頭で、すべての出力に既定値を代入せよ。信号あたり一行で、失敗モードがまるごと一つ消える。
Why are latches a failure mode rather than just an inefficiency? Three reasons, all of which matter in a cell-based flow. A latch is transparent while its enable is high, so a combinational path can propagate through it, which breaks the clean register-to-register timing model that STA assumes and forces you into time-borrowing analysis that is far harder to close. A latch inferred by accident has an enable derived from your if conditions, which is combinational logic acting as a clock — exactly what clock-tree synthesis cannot handle. And a latch is not in the scan chain the way a flip-flop is, so DFT coverage drops. Lecture 6 revisits all of this; for now, the rule is simply: if the synthesis log says "inferred latch", you have a bug.為什麼閂鎖是一種失效模式,而不只是效率差一點?三個理由,在 cell-based 流程裡都很要緊。閂鎖在 enable 為高時是透通的,所以一條組合路徑可以穿過它,這破壞了 STA 所假設的乾淨「暫存器到暫存器」時序模型,逼你走進難收斂許多的 time-borrowing 分析。意外推斷出來的閂鎖,其 enable 是從你的 if 條件推導出來的,也就是一塊當成時脈用的組合邏輯 — 那正是時脈樹合成處理不了的東西。而且閂鎖不像正反器那樣能進掃描鏈,所以 DFT 涵蓋率會掉。第 6 講會再回頭談這一切;現在的規則很單純:如果合成記錄檔說「inferred latch」,你就有臭蟲。なぜラッチは単なる非効率ではなく失敗モードなのか。理由は三つあり、いずれもセルベース・フローで重要である。ラッチはイネーブルが高の間は透過なので、組合せパスがその中を通り抜けられる。これは STA が前提とする清潔なレジスタ間タイミングモデルを壊し、収束がはるかに難しいタイムボローイング解析へ追い込む。事故で推論されたラッチのイネーブルは if の条件から導かれる、すなわちクロックとして働く組合せ論理であり、クロックツリー合成が最も扱えないものだ。さらにラッチはフリップフロップのようにスキャンチェーンに入らないので、DFT カバレッジが落ちる。第 6 講でこれらすべてを再訪する。いまのところ規則は単純だ。合成ログに「inferred latch」と出たら、それはバグである。
Note also the operator > inside the else if. Because A == B was already tested and was false, the else if (A > B) arm and the final else arm together partition the remaining cases. The chain is a priority structure — the synthesiser builds it as cascaded multiplexers, with the first condition closest to the output. Had the three conditions been mutually exclusive and written as a case, the tool would have built a flatter, faster parallel structure. For three conditions the difference is negligible; for a 32-way priority chain it is the difference between meeting timing and not. Lecture 5 covers case versus if-else in detail.另外注意 else if 裡的運算子 >。因為 A == B 已經先被測試過而且為假,else if (A > B) 這一支與最後的 else 這一支合起來就分割了剩下的所有情況。這個鏈是一個優先權結構 — 合成器會把它搭成串接的多工器,第一個條件離輸出最近。如果這三個條件彼此互斥而且寫成 case,工具就會搭出一個比較扁平、比較快的並行結構。三個條件時,差別微不足道;三十二路的優先鏈時,那就是時序過與不過的差別。第 5 講會詳細談 case 與 if-else 的取捨。else if の中の演算子 > にも注意せよ。A == B は既に検査されて偽だったので、else if (A > B) の腕と最後の else の腕が残りの場合を分割する。この連鎖は優先順位構造であり、合成器はこれを縦続したマルチプレクサとして組み、最初の条件を出力にいちばん近く置く。三つの条件が互いに排反でかつ case として書かれていれば、ツールはもっと平たく速い並列構造を組んだだろう。条件が三つなら差は無視できるが、32 方向の優先連鎖ではタイミングが入るか入らないかの差になる。case と if-else の比較は第 5 講で詳しく扱う。
The slides show the Design Vision schematic for the comparator: roughly a dozen cells — a handful of inverters, two XNOR-ish gates, several NANDs and NORs, an AOI or two, and output buffers. It looks nothing like any of the four source descriptions. Here is what the tool did, step by step, and it is worth knowing because it explains why "optimising" your RTL by hand is usually wasted effort.投影片展示了這個比較器在 Design Vision 裡的電路圖:大約十來顆 cell — 幾顆反相器、兩顆類 XNOR 的閘、數顆 NAND 與 NOR、一兩顆 AOI,還有輸出緩衝器。它和那四種原始碼描述裡的任何一種都不像。以下是工具一步一步做了什麼,值得知道,因為它解釋了為什麼親手「最佳化」你的 RTL 通常是白費力氣。スライドは比較器の Design Vision 回路図を示す。セルはおよそ十数個——インバータが数個、XNOR 系のゲートが二つ、NAND と NOR がいくつか、AOI が一つ二つ、そして出力バッファ。四つのソース記述のどれにも似ていない。以下はツールが段階ごとに何をしたかであり、知っておく価値がある。RTL を手で「最適化」するのがたいてい徒労である理由を説明してくれるからだ。
<, >, == become generic comparator cells (DesignWare components, in Synopsys terms).展開(elaborate)。讀入 RTL、解析參數、建出一張與製程無關的網路。關係運算子 <、>、== 會變成通用比較器元件(在 Synopsys 的術語裡就是 DesignWare 元件)。エラボレーション。RTL を読み、パラメータを解決し、プロセス非依存のネットワークを構築する。関係演算子 <、>、== は汎用比較器セル(Synopsys の用語では DesignWare コンポーネント)になる。A_eq_B and A_lt_B share the subexpression $A_1 \odot B_1$ and builds it once. It notices that A_gt_B is the NOR of the other two outputs — whether or not you wrote it that way, because that identity is visible in the truth table.因式分解與共用。它注意到 A_eq_B 與 A_lt_B 共用子運算式 $A_1 \odot B_1$,於是只建一次。它也注意到 A_gt_B 是另外兩個輸出的 NOR — 不管你有沒有那樣寫,因為那個恆等式在真值表上看得見。因数分解と共有。ツールは A_eq_B と A_lt_B が部分式 $A_1 \odot B_1$ を共有していることに気づき、一度だけ作る。また A_gt_B が他の二出力の NOR であることにも気づく——君がそう書いたかどうかに関係なくである。その同一性は真理値表に見えているからだ。Four very different source descriptions, one netlist. The tool does not care how you wrote it; it cares what function you described. So the criterion for choosing a style is not "which is faster in hardware" but "which is clearest to a human and hardest to get wrong" — which is why real RTL looks like compare_2ca and never like compare_2_str. The exception is structure you want to preserve — a hand-crafted carry chain, a cell you need for a specific physical reason — and that requires a synthesis attribute to protect it, not just writing it out longhand.四份差異極大的原始碼描述,一份網表。工具不在乎你怎麼寫;它在乎你描述了什麼函數。所以選擇風格的標準不是「哪一種在硬體上比較快」,而是「哪一種對人最清楚、最不容易寫錯」 — 這就是為什麼真實的 RTL 長得像 compare_2ca,永遠不會長得像 compare_2_str。例外是你想保留的結構 — 一條手工打造的進位鏈、一顆你因為特定物理理由而需要的 cell — 而那需要一個合成屬性來保護它,不是光把它長長地手寫出來就行。まるで違う四つのソース記述から、ネットリストは一つ。ツールは書き方を気にしない。どんな関数を記述したかを気にする。だからスタイルを選ぶ基準は「どれがハードウェアとして速いか」ではなく、「どれが人間にとって明瞭で、間違えにくいか」である。実務の RTL が compare_2ca に似て compare_2_str には決して似ない理由がこれだ。例外は保存したい構造——手作りのキャリーチェーン、物理的な理由で必要な特定のセル——であり、それには長々と手書きするだけでなく、守るための合成属性が要る。
Problem: (a) Verify A_lt_B from the §6.2 equation for $A = 01$, $B = 10$. (b) Verify A_gt_B for $A=10$, $B=01$. (c) Evaluate all three slide equations for $A = 11$, $B = 11$ and confirm mutual exclusivity. (d) Show that the structural module's nor (A_gt_B, A_lt_B, A_eq_B) gives the right answer for $A=10,B=01$ by tracing w1…w7.題目:(a) 用 §6.2 的方程式驗證 $A = 01$、$B = 10$ 時的 A_lt_B。(b) 驗證 $A=10$、$B=01$ 時的 A_gt_B。(c) 對 $A = 11$、$B = 11$ 求出投影片三條方程式的值,並確認互斥性。(d) 透過追蹤 w1…w7,說明結構化模組中的 nor (A_gt_B, A_lt_B, A_eq_B) 在 $A=10,B=01$ 時給出正確答案。問題:(a) §6.2 の式を使い、$A = 01$、$B = 10$ のときの A_lt_B を検算せよ。(b) $A=10$、$B=01$ のときの A_gt_B を検算せよ。(c) $A = 11$、$B = 11$ についてスライドの三式を評価し、排反性を確認せよ。(d) w1…w7 を追い、構造記述モジュールの nor (A_gt_B, A_lt_B, A_eq_B) が $A=10,B=01$ で正しい答えを出すことを示せ。
(a) $A_1=0,A_0=1,B_1=1,B_0=0$. Term 1: $\bar A_1 B_1 = 1\cdot 1 = 1$. The OR is already 1, so $A\_lt\_B=1$. Correct: $1 < 2$. (Terms 2 and 3 are 0 because $B_0=0$.)(a) $A_1=0,A_0=1,B_1=1,B_0=0$。第 1 項:$\bar A_1 B_1 = 1\cdot 1 = 1$。OR 已經是 1,所以 $A\_lt\_B=1$。正確:$1 < 2$。(第 2、3 項因為 $B_0=0$ 而為 0。)(a) $A_1=0,A_0=1,B_1=1,B_0=0$。第 1 項:$\bar A_1 B_1 = 1\cdot 1 = 1$。OR は既に 1 なので $A\_lt\_B=1$。正しい($1 < 2$)。第 2・3 項は $B_0=0$ ゆえ 0。
(b) $A_1=1,A_0=0,B_1=0,B_0=1$. Term 1: $A_1\bar B_1 = 1\cdot 1=1$ → $A\_gt\_B=1$. Correct: $2 > 1$.(b) $A_1=1,A_0=0,B_1=0,B_0=1$。第 1 項:$A_1\bar B_1 = 1\cdot 1=1$ → $A\_gt\_B=1$。正確:$2 > 1$。(b) $A_1=1,A_0=0,B_1=0,B_0=1$。第 1 項:$A_1\bar B_1 = 1\cdot 1=1$ → $A\_gt\_B=1$。正しい($2 > 1$)。
(c) $A_1=A_0=B_1=B_0=1$. lt: $\bar A_1B_1 = 0$; $\bar A_1\bar A_0B_0=0$; $\bar A_0B_1B_0 = 0$ → lt = 0. gt: $A_1\bar B_1=0$; $A_0\bar B_1\bar B_0=0$; $A_1A_0\bar B_0=0$ → gt = 0. eq: the third minterm $A_1A_0B_1B_0 = 1$ → eq = 1. Exactly one output is high. ✔(c) $A_1=A_0=B_1=B_0=1$。lt:$\bar A_1B_1 = 0$;$\bar A_1\bar A_0B_0=0$;$\bar A_0B_1B_0 = 0$ → lt = 0。gt:$A_1\bar B_1=0$;$A_0\bar B_1\bar B_0=0$;$A_1A_0\bar B_0=0$ → gt = 0。eq:第三個最小項 $A_1A_0B_1B_0 = 1$ → eq = 1。恰好一個輸出為高。✔(c) $A_1=A_0=B_1=B_0=1$。lt:$\bar A_1B_1 = 0$、$\bar A_1\bar A_0B_0=0$、$\bar A_0B_1B_0 = 0$ → lt = 0。gt:$A_1\bar B_1=0$、$A_0\bar B_1\bar B_0=0$、$A_1A_0\bar B_0=0$ → gt = 0。eq:第三の最小項 $A_1A_0B_1B_0 = 1$ → eq = 1。高になる出力はちょうど一つ。✔
(d) $A_1=1,A_0=0,B_1=0,B_0=1$. w6 = ~A1 = 0, w7 = ~A0 = 1. w1 = w6 & B1 = 0 & 0 = 0. w2 = w6 & w7 & B0 = 0 & 1 & 1 = 0. w3 = w7 & B0 & B1 = 1 & 1 & 0 = 0. So A_lt_B = w1|w2|w3 = 0. w4 = xnor(A1,B1) = xnor(1,0) = 0, so A_eq_B = w4 & w5 = 0. Then A_gt_B = nor(0,0) = 1. ✔(d) $A_1=1,A_0=0,B_1=0,B_0=1$。w6 = ~A1 = 0,w7 = ~A0 = 1。w1 = w6 & B1 = 0 & 0 = 0。w2 = w6 & w7 & B0 = 0 & 1 & 1 = 0。w3 = w7 & B0 & B1 = 1 & 1 & 0 = 0。所以 A_lt_B = w1|w2|w3 = 0。w4 = xnor(A1,B1) = xnor(1,0) = 0,所以 A_eq_B = w4 & w5 = 0。於是 A_gt_B = nor(0,0) = 1。✔(d) $A_1=1,A_0=0,B_1=0,B_0=1$。w6 = ~A1 = 0、w7 = ~A0 = 1。w1 = w6 & B1 = 0 & 0 = 0。w2 = w6 & w7 & B0 = 0 & 1 & 1 = 0。w3 = w7 & B0 & B1 = 1 & 1 & 0 = 0。よって A_lt_B = w1|w2|w3 = 0。w4 = xnor(A1,B1) = xnor(1,0) = 0 なので A_eq_B = w4 & w5 = 0。したがって A_gt_B = nor(0,0) = 1。✔
Exam trap: the NOR creates a combinational chain w4,w5 → A_eq_B → A_gt_B, so A_gt_B is two gate delays later than A_eq_B and three later than its inputs. The three outputs do not settle simultaneously, and during the transient a decoder downstream may briefly see two outputs high. If those outputs feed combinational logic that must be glitch-free, you must register them.考試陷阱:那顆 NOR 造出一條組合鏈 w4,w5 → A_eq_B → A_gt_B,所以 A_gt_B 比 A_eq_B 晚兩個閘延遲,比它自己的輸入晚三個。三個輸出不會同時穩定,而在暫態期間,下游的解碼器可能短暫看到兩個輸出同時為高。如果那些輸出要餵進一段必須無毛刺的組合邏輯,你就得把它們過暫存器。試験の罠:この NOR は組合せの連鎖 w4,w5 → A_eq_B → A_gt_B を作る。したがって A_gt_B は A_eq_B より 2 ゲート遅延、自身の入力より 3 ゲート遅延遅い。三つの出力は同時には落ち着かず、過渡の間に下流のデコーダが一瞬、二つの出力を同時に高と見ることがある。それらがグリッチのあってはならない組合せ論理を駆動するなら、レジスタで受けねばならない。
Problem: A student removes the three default assignments from compare_2_algo. (a) How many latches does synthesis infer, and what is each one's enable? (b) Describe what the simulation waveform looks like for the input sequence $AB$ = (0,0), (1,0), (1,1), (2,3). (c) Give two different fixes.題目:某學生把 compare_2_algo 裡的三個預設指定拿掉了。(a) 合成會推斷出幾個閂鎖,每一個的 enable 是什麼?(b) 對輸入序列 $AB$ = (0,0)、(1,0)、(1,1)、(2,3),描述模擬波形長什麼樣子。(c) 給出兩種不同的修法。問題:ある学生が compare_2_algo から三つの既定値代入を取り除いた。(a) 合成はラッチをいくつ推論し、それぞれのイネーブルは何か。(b) 入力列 $AB$ = (0,0)、(1,0)、(1,1)、(2,3) に対するシミュレーション波形を述べよ。(c) 異なる直し方を二つ示せ。
(a) Three latches, one per output. Each output is assigned on exactly one of the three paths, so each must hold on the other two. A_eq_B's latch is enabled by (A == B); A_gt_B's by (A != B) & (A > B), i.e. just (A > B); A_lt_B's by (A < B). Each latch's D input is a constant 1 — and a latch whose D is tied high is a set-only element. Once an output goes to 1 it can never return to 0. Design Compiler will report three inferred latches in its elaboration log.(a) 三個閂鎖,每個輸出一個。每個輸出只在三條路徑中的一條上被指定,所以在另外兩條上都必須保持。A_eq_B 的閂鎖由 (A == B) 致能;A_gt_B 的由 (A != B) & (A > B),也就是單純的 (A > B);A_lt_B 的由 (A < B)。每個閂鎖的 D 輸入都是常數 1 — 而 D 綁在高電位的閂鎖是一個只會被設定的元件。某個輸出一旦變成 1 就再也回不到 0。Design Compiler 會在它的展開記錄檔裡回報三個 inferred latch。(a) ラッチ三つ、出力ごとに一つ。各出力は三つの経路のうちちょうど一つでしか代入されないので、残り二つでは保持せねばならない。A_eq_B のラッチは (A == B) でイネーブルされ、A_gt_B のそれは (A != B) & (A > B)、すなわち単に (A > B)、A_lt_B のそれは (A < B) である。各ラッチの D 入力は定数 1 であり、D を高に固定したラッチはセット専用の素子になる。いったん 1 になった出力は二度と 0 に戻らない。Design Compiler はエラボレーションログに inferred latch を三つ報告する。
(b) Everything starts at x. (0,0): equal → A_eq_B latches 1; lt and gt stay x. (1,0): greater → A_gt_B becomes 1; A_eq_B stays 1 because nothing cleared it; lt still x. (1,1): equal → eq still 1, gt still 1. (2,3): less → lt becomes 1. Final state: all three outputs stuck at 1. The waveform shows three monotonically rising signals that never fall — the unmistakable signature of an accidental set-only latch.(b) 一切從 x 開始。(0,0):相等 → A_eq_B 閂住 1;lt 與 gt 仍是 x。(1,0):大於 → A_gt_B 變成 1;A_eq_B 仍是 1,因為沒有東西把它清掉;lt 還是 x。(1,1):相等 → eq 仍是 1,gt 也仍是 1。(2,3):小於 → lt 變成 1。最終狀態:三個輸出全部卡在 1。波形上看到的是三條單調上升、永不下降的訊號 — 那是不小心做出只能設定的閂鎖的明確簽名。(b) すべては x から始まる。(0,0):等しい → A_eq_B が 1 をラッチし、lt と gt は x のまま。(1,0):大きい → A_gt_B が 1 になる。A_eq_B は誰も消さないので1 のまま、lt はまだ x。(1,1):等しい → eq も gt も 1 のまま。(2,3):小さい → lt が 1 になる。最終状態は三出力すべてが 1 に貼り付く。波形には単調に上がって決して下がらない三本の信号が現れる——偶発的なセット専用ラッチの紛れもない署名である。
(c) Fix 1: restore the default assignments at the top of the block. Fix 2: make every path assign all three outputs explicitly, e.g.(c) 修法 1:把區塊開頭那些預設指定放回去。修法 2:讓每一條路徑都明確指定全部三個輸出,例如(c) 直し方 1:ブロック冒頭の既定値代入を戻す。直し方 2:すべての経路で三つの出力を明示的に代入する。たとえば
always @ (*)
begin
A_eq_B = (A == B);
A_gt_B = (A > B);
A_lt_B = (A < B);
end
which is really just the RTL version wearing a procedural costume, and is the clearest of all. Fix 3 (do not use, but understand it): an else on every if and a default on every case. That also closes every path, but it scales badly — with five outputs and four branches you must write twenty assignments instead of five defaults.那其實就是 RTL 版本披上了一件程序敘述的外衣,而且是所有版本中最清楚的。修法 3(別用,但要理解它):替每個 if 都加 else、替每個 case 都加 default。那同樣封住了每條路徑,但它的規模性很差 — 五個輸出、四個分支時,你得寫二十條指定,而不是五個預設值。これは実のところ RTL 版が手続き的な衣装をまとっただけであり、すべての中で最も明瞭である。直し方 3(使ってはならないが理解はせよ):すべての if に else を、すべての case に default を付ける。これも全経路を閉じるが、規模に弱い。出力五つ、分岐四つなら、既定値五行の代わりに代入を二十行書く羽目になる。
Hierarchy in Verilog is built by instantiation: writing a child module's name as an item inside a parent module, giving the instance a name, and listing the parent's signals that connect to the child's ports. The slides state four rules; each deserves a sentence of unpacking.Verilog 的階層是靠實例化建立的:把子模組的名稱當成一個項目寫在父模組裡面,給這個實例一個名字,再列出父模組中要連到子模組各埠的訊號。投影片列了四條規則;每一條都值得用一句話展開。Verilog の階層はインスタンス化で築かれる。子モジュールの名前を親モジュールの中の項目として書き、インスタンスに名前を与え、子のポートへつなぐ親側の信号を並べる。スライドは四つの規則を挙げる。どれも一文ずつほどく価値がある。
Add_half four times creates four physically distinct sets of gates on the die. They do not share anything.實例化創造階層。模組宣告定義一個型別;實例化配置該型別的一份複本。把 Add_half 實例化四次,就在晶粒上造出四組物理上各自獨立的閘。它們之間什麼都不共用。インスタンス化が階層を作る。モジュール宣言は型を定義し、インスタンス化はその型のコピーを一つ確保する。Add_half を四回インスタンス化すれば、ダイ上に物理的に別個のゲート群が四組できる。互いに何も共有しない。<module_name> <instance_name> ( <port connections> );做法是把模組名稱當成一個模組項目寫進去,並在埠的位置放上訊號識別字。語法:<module_name> <instance_name> ( <埠連接> );やり方は、モジュール名をモジュール項目として書き、ポートの位置に信号の識別子を置くことである。構文:<module_name> <instance_name> ( <ポート接続> );Add_half (w1, w2, a, b);.模組實例化需要一個模組識別字 — 也就是說,實例必須命名。和閘基元不同,你不可以寫 Add_half (w1, w2, a, b);。モジュールのインスタンス化には識別子が要る——つまりインスタンスには名前が必要である。ゲートプリミティブと違い、Add_half (w1, w2, a, b); とは書けない。The rule about naming is more than bookkeeping. Every instance name becomes part of a hierarchical path — tb.dut.M1.HA2.sum — and those paths are how you probe a signal from a testbench, how a waveform viewer labels its tree, how a synthesis constraint targets one block, how a timing report identifies a path, and how an SDF file back-annotates delays after layout. An unnamed instance would have no address in any of those tools. A gate primitive can get away with it because the tool invents a name; a module instance, which may contain thousands of signals, cannot.命名這條規則不只是帳務工作。每個實例名稱都會成為一條階層路徑的一部分 — tb.dut.M1.HA2.sum — 而那些路徑正是你從測試平台探測某條訊號的方式、波形檢視器替它的樹狀圖標名的方式、合成限制鎖定某個區塊的方式、時序報告指認一條路徑的方式,以及 SDF 檔在佈局後反標註延遲的方式。沒有名字的實例,在上述任何一個工具裡都沒有地址。閘基元可以僥倖過關,因為工具會替它發明一個名字;而一個可能含有上千條訊號的模組實例,不行。命名の規則は事務処理以上のものである。各インスタンス名は階層パスの一部になる——tb.dut.M1.HA2.sum——そしてそのパスこそ、テストベンチから信号を覗く方法であり、波形ビューアがツリーにラベルを付ける方法であり、合成制約が特定ブロックを狙う方法であり、タイミング報告がパスを同定する方法であり、SDF ファイルがレイアウト後に遅延をバックアノテートする方法である。名前のないインスタンスは、そのどのツールにおいても住所を持たない。ゲートプリミティブが見逃されるのはツールが名前を発明するからだ。数千本の信号を含みうるモジュールインスタンスでは、そうはいかない。
module child (a, b, c); ... endmodule
module parent; wire u, v, w; child m1 (u, v, w); // by position child m2 (.c(w), .a(u), .b(v)); // by name child m3 (u, , w); // empty port: b unconnected endmodule
All three instances connect a–u, b–v, c–w (except that m3 leaves b unconnected).三個實例全都把 a–u、b–v、c–w 連在一起(只有 m3 讓 b 懸空未接)。三つのインスタンスはいずれも a–u、b–v、c–w を接続する(ただし m3 は b を未接続にしている)。
| Positional位置對應位置指定 | Named名稱對應名前指定 | |
|---|---|---|
| Syntax語法構文 | child m1 (u, v, w); | child m2 (.a(u), .b(v), .c(w)); |
| Order順序順序 | Must match the declaration order of the child's port list exactly必須與子模組埠列的宣告順序完全一致子モジュールのポートリストの宣言順と厳密に一致せねばならない | Irrelevant — .c may come first無關緊要 — .c 可以寫在最前面無関係——.c を先頭に書いてもよい |
| Omitting a port略過某個埠ポートを省く | Leave the position empty: (u, , w)把位置留空:(u, , w)位置を空ける:(u, , w) | Omit the entry entirely, or write .b()整項不寫,或寫成 .b()項目ごと書かないか、.b() と書く |
| Failure mode失效模式失敗の形 | Silently wrong connections when the child's port list changes or when the two lists use a different convention當子模組的埠列改變,或兩份清單採用不同慣例時,會安靜地接錯子のポートリストが変わったとき、あるいは双方が別の慣習に従うとき、黙って誤接続になる | Compile error on a misspelled port name埠名稱拼錯會得到編譯錯誤ポート名を綴り間違えればコンパイルエラーになる |
| Readability in review審查時的可讀性レビュー時の可読性 | You must open the child to know what port 7 is你必須打開子模組才知道第 7 個埠是什麼7 番目のポートが何かを知るには子を開かねばならない | Self-documenting自我說明自己説明的 |
Positional binding is a loaded gun. Every port list in this lecture puts outputs first and inputs second — Add_half (sum, c_out, a, b) — but half the IP cores in the world put inputs first. Connect a 6-port module positionally against the wrong convention and the compiler accepts it without a word, because every port is a 1-bit wire and 1-bit wires are interchangeable to the type system. You then debug for a day. Named binding turns every such mistake into an immediate compile error. The only legitimate use of positional binding is a two-port module like an inverter, and even then.位置對應是一把上了膛的槍。本講裡每一份埠列都把輸出放前面、輸入放後面 — Add_half (sum, c_out, a, b) — 但世界上有一半的 IP 核是把輸入放前面。用錯誤的慣例去位置式連接一個六埠模組,編譯器會一聲不吭地接受,因為每個埠都是一條 1 位元 wire,而 1 位元 wire 在型別系統眼中可以互換。然後你就除錯一整天。名稱對應會把每一個這種錯誤變成立即的編譯錯誤。位置對應唯一正當的用途是像反相器那樣的雙埠模組,而且就算那樣也該想一想。位置指定は弾を込めた銃である。この講義のポートリストはどれも出力を先、入力を後に置く——Add_half (sum, c_out, a, b)——が、世の IP コアの半分は入力を先に置く。誤った慣習のまま 6 ポートのモジュールを位置指定でつなげば、コンパイラは一言もなく受理する。どのポートも 1 ビットの wire であり、型システムにとって 1 ビットの wire は互いに交換可能だからだ。そして君は一日デバッグする。名前指定はこの種の誤りをすべて即座のコンパイルエラーに変える。位置指定の正当な用途はインバータのような 2 ポートのモジュールくらいであり、それすら考え直す余地がある。
Empty ports. Leaving a port unconnected is legal and sometimes correct, but the two directions behave very differently:空埠。把一個埠留著不接是合法的,有時也正確,但兩個方向的行為差很多:未接続のポート。ポートをつながずに残すのは合法で、正しいこともあるが、方向によって振る舞いが大きく違う。
z. Every gate it feeds then produces x unless a controlling value saves it. In synthesis you get a warning and the tool typically ties it to a constant, but which constant is tool-dependent. Never leave an input unconnected by accident; tie it explicitly: .enable(1'b1).未接的輸入浮在 z。它餵給的每一顆閘接著都會產生 x,除非有控制值救了它。在合成中你會得到一條警告,工具通常會把它綁到一個常數,但是哪一個常數則因工具而異。絕對不要不小心讓某個輸入懸空;明確把它綁起來:.enable(1'b1)。未接続の入力は z で浮く。支配値に救われない限り、それが養うゲートはすべて x を出す。合成では警告が出て、ツールはたいてい定数に固定するが、どちらの定数かはツール依存である。入力をうっかり未接続のまま残してはならない。明示的に固定せよ。.enable(1'b1)。module Add_full (sum, c_out, a, b, c_in); // parent module input a, b, c_in; output c_out, sum; wire w1, w2, w3; Add_half M1 (w1, w2, a, b); // child module Add_half M2 (sum, w3, w1, c_in); // child module or (c_out, w2, w3); // primitive instantiation endmodule
| NetNetネット | Produced by由誰產生生成元 | Value值値 |
|---|---|---|
w1 | M1's sum outputM1 的 sum 輸出M1 の sum 出力 | $a \oplus b$ |
w2 | M1's c_out outputM1 的 c_out 輸出M1 の c_out 出力 | $ab$ |
w3 | M2's c_out outputM2 的 c_out 輸出M2 の c_out 出力 | $(a \oplus b)\,c_{in}$ |
sum | M2's sum outputM2 的 sum 輸出M2 の sum 出力 | $(a \oplus b) \oplus c_{in}$ |
c_out | the or primitive那顆 or 基元or プリミティブ | $(a \oplus b)\,c_{in} + ab$ |
The resulting hierarchy is three levels deep: Add_full contains M1 and M2 (each an Add_half) plus an or; each Add_half contains an xor, a nand and a not. The slide's tree diagram makes the point that nested module instantiation is the mechanism for hierarchical decomposition of a design, and the count is worth noting: one Add_full costs 2 × 3 + 1 = 7 primitive gates.由此得到的階層有三層深:Add_full 含有 M1 與 M2(各是一個 Add_half)加上一顆 or;每個 Add_half 含有一顆 xor、一顆 nand 與一顆 not。投影片的樹狀圖要講的是:巢狀的模組實例化就是設計階層分解的機制,而數量也值得注意:一個 Add_full 要花 2 × 3 + 1 = 7 顆基元閘。こうしてできる階層は三段である。Add_full は M1 と M2(いずれも Add_half)に or を一つ加えたもので、各 Add_half は xor、nand、not を一つずつ含む。スライドのツリー図が言いたいのは、入れ子のモジュール・インスタンス化こそ設計を階層的に分解する機構であるということだ。個数も記しておく価値がある。Add_full 一つは 2 × 3 + 1 = 7 個のプリミティブゲートである。
The lecture scales the same idea twice. Four Add_full instances make a 4-bit ripple-carry adder; four of those make a 16-bit one.本講把同一個想法放大了兩次。四個 Add_full 實例組成一個 4 位元漣波進位加法器;四個那樣的區塊再組成一個 16 位元的。講義は同じ考えを二度スケールさせる。Add_full 四つで 4 ビットのリップルキャリー加算器を作り、それを四つ並べて 16 ビットにする。
module Add_rca_16 (sum, c_out, a, b, c_in);
output [15:0] sum;
output c_out;
input [15:0] a, b;
input c_in;
wire c_in4, c_in8, c_in12;
Add_rca_4 M1 (sum[3:0], c_in4,
a[3:0], b[3:0], c_in);
Add_rca_4 M2 (sum[7:4], c_in8,
a[7:4], b[7:4], c_in4);
Add_rca_4 M3 (sum[11:8], c_in12,
a[11:8], b[11:8], c_in8);
Add_rca_4 M4 (sum[15:12], c_out,
a[15:12], b[15:12], c_in12);
endmodulemodule Add_rca_4 (sum, c_out, a, b, c_in); output [3:0] sum; output c_out; input [3:0] a, b; input c_in; wire c_in2, c_in3, c_in4; Add_full G1 (sum[0], c_in2, a[0], b[0], c_in); Add_full G2 (sum[1], c_in3, a[1], b[1], c_in2); Add_full G3 (sum[2], c_in4, a[2], b[2], c_in3); Add_full G4 (sum[3], c_out, a[3], b[3], c_in4); endmodule
Two things to notice in the source. Part selects as port connections: a[7:4] is a 4-bit slice of a 16-bit net, and connecting it to a 4-bit port is perfectly legal — port connections are made by width, not by identity. The naming of the carry nets follows the bit index of the stage they feed, so c_in4 is the carry into bit 4. That is a good convention; the alternative, c1, c2, c3, gives you no clue which stage a timing report is talking about.原始碼裡有兩件事要注意。把部分選取當作埠連接:a[7:4] 是一條 16 位元 net 的 4 位元切片,把它接到一個 4 位元的埠完全合法 — 埠連接是按位寬配對的,不是按身分。進位 net 的命名跟著它所餵入的那一級的位元索引走,所以 c_in4 是進入位元 4 的進位。那是好慣例;另一種寫法 c1, c2, c3 完全無法讓你從時序報告看出它在講哪一級。ソースで注意すべき点が二つある。部分選択をポート接続に使うこと:a[7:4] は 16 ビットのネットの 4 ビットの切片であり、それを 4 ビットのポートにつなぐのは完全に合法である。ポート接続は同一性ではなく幅で行われる。キャリー・ネットの命名は、それが入る段のビット番号に従う。だから c_in4 はビット 4 へ入るキャリーである。良い慣習だ。もう一方の c1, c2, c3 では、タイミング報告がどの段の話をしているのか手がかりがない。
The slide's Add_rca_16 declares wire c_in, c_in4, c_in8, c_in12, c_out; — including c_in and c_out, which are already ports. Redeclaring a port as a wire is legal Verilog-1995 and changes nothing (ports are wires by default), so this compiles, but it is redundant and most lint rules flag it. More importantly: do not get in the habit of redeclaring ports, because if you ever redeclare an output as reg you have changed its meaning entirely, and the two cases look identical on the page.投影片的 Add_rca_16 宣告了 wire c_in, c_in4, c_in8, c_in12, c_out; — 其中包含 c_in 與 c_out,而它們本來就是埠。把埠重新宣告成 wire 在 Verilog-1995 裡是合法的,而且什麼也沒改變(埠預設就是 wire),所以這能編譯過,但它是多餘的,而且大多數 lint 規則會標出它。更重要的是:不要養成重新宣告埠的習慣,因為萬一哪天你把一個 output 重新宣告成 reg,你就徹底改變了它的意義,而這兩種情況在紙面上看起來一模一樣。スライドの Add_rca_16 は wire c_in, c_in4, c_in8, c_in12, c_out; と宣言しており、既にポートである c_in と c_out まで含んでいる。ポートを wire として再宣言するのは Verilog-1995 では合法で、何も変わらない(ポートの既定は wire である)ので、これはコンパイルを通る。しかし冗長であり、たいていの lint 規則が指摘する。さらに重要なのは、ポートを再宣言する癖を付けないことだ。いつか output を reg と再宣言すれば意味がまるごと変わるのに、紙面では両者が見分けられないからである。
The ripple-carry adder is the lecture's vehicle for its first real timing argument, and it is worth doing with actual numbers rather than hand-waving.漣波進位加法器是本講第一個真正的時序論證所用的載具,而它值得用實際數字來做,而不是揮揮手帶過。リップルキャリー加算器は、この講義で初めて本格的なタイミング議論を行うための題材である。手振りで済ませず、実際の数字でやる価値がある。
Assume each primitive gate has a unit delay $t_g$. Inside one Add_half, the path from an input to sum is one XOR, i.e. $t_g$; the path from an input to c_out is a NAND then a NOT, i.e. $2t_g$. Now trace the carry path through one Add_full, which is the path that matters, because it is the one that chains:假設每顆基元閘有單位延遲 $t_g$。在一個 Add_half 內部,從輸入到 sum 的路徑只有一顆 XOR,也就是 $t_g$;從輸入到 c_out 的路徑是一顆 NAND 接一顆 NOT,也就是 $2t_g$。現在追蹤穿過一個 Add_full 的進位路徑,那才是要緊的路徑,因為它是會串起來的那一條:各プリミティブゲートの遅延を単位 $t_g$ とする。Add_half 一つの中で、入力から sum までの経路は XOR 一つ、すなわち $t_g$。入力から c_out までは NAND と NOT、すなわち $2t_g$ である。では Add_full 一つを貫くキャリー経路を追おう。連鎖するのはこの経路なので、重要なのはこちらである。
c_in arrives at M2's b input. (It does not go through M1 at all — M1's inputs are a and b of the adder, which arrived long ago.)入口。c_in 抵達 M2 的 b 輸入。(它完全不會經過 M1 — M1 的輸入是加法器的 a 與 b,它們早就到了。)入口。c_in は M2 の b 入力に届く。(M1 はまったく通らない。M1 の入力は加算器の a と b であり、とうの昔に到着している。)nand then not: $2t_g$. This produces w3.穿過 M2 的進位邏輯。nand 接 not:$2t_g$。這產生 w3。M2 のキャリー論理を通る。nand から not へ、$2t_g$。これが w3 を生む。c_out.穿過最後的 OR。$+\,t_g$,產生 c_out。最後の OR を通る。$+\,t_g$ で c_out になる。c_in → M2's xor → sum is only $t_g$, so the sum of a stage is ready long before its carry-out.進位輸入到進位輸出的總計:$t_{\text{carry}} = 3t_g$。同時,c_in → M2 的 xor → sum 這條路徑只有 $t_g$,所以一級的和早在它的進位輸出之前就備妥了。キャリー入力からキャリー出力までの合計:$t_{\text{carry}} = 3t_g$。一方 c_in → M2 の xor → sum の経路は $t_g$ しかないので、各段の和はそのキャリー出力よりずっと早く確定する。The first term is the delay from the operand bits of stage 0 to its carry-out ($3t_g$: M1's nand, M1's not, the or). The middle term is the ripple itself. Taking $t_{\text{carry}}=3t_g$ throughout, a 16-bit adder costs $16\times 3t_g = \mathbf{48\,t_g}$ from c_in to c_out.第一項是從第 0 級的運算元位元到它的進位輸出的延遲($3t_g$:M1 的 nand、M1 的 not、那顆 or)。中間那一項就是漣波本身。全程取 $t_{\text{carry}}=3t_g$,一個 16 位元加法器從 c_in 到 c_out 要花 $16\times 3t_g = \mathbf{48\,t_g}$。第一項は段 0 のオペランドビットからそのキャリー出力までの遅延である($3t_g$:M1 の nand、M1 の not、そして or)。中央の項がリップルそのものだ。全段で $t_{\text{carry}}=3t_g$ とすれば、16 ビット加算器は c_in から c_out まで $16\times 3t_g = \mathbf{48\,t_g}$ を要する。
The structure of that equation is the thing to remember. Ripple-carry delay is $O(n)$: double the word width, double the delay. At 16 bits and a 50 ps gate that is about 2.4 ns, so the adder alone eats a 400 MHz clock period. At 64 bits it is 9.6 ns and nothing works. This linear growth is why carry-select, carry-skip, carry-look-ahead and parallel-prefix (Kogge–Stone, Brent–Kung) adders exist, all of which trade area for an $O(\log n)$ delay. It is also why the one-line assign {c_out, sum} = a + b; of §5.5 is better engineering than the structural version: it lets the synthesiser pick the architecture that meets your constraint, instead of freezing the slowest one into the source.該記住的是那條方程式的結構。漣波進位延遲是 $O(n)$:字長加倍,延遲就加倍。在 16 位元、每顆閘 50 ps 的條件下大約是 2.4 ns,光一顆加法器就吃掉一個 400 MHz 的時脈週期。到 64 位元就是 9.6 ns,什麼都動不了。這種線性成長正是進位選擇、進位跳躍、超前進位,以及並行前綴(Kogge–Stone、Brent–Kung)加法器存在的理由,它們全都以面積換取 $O(\log n)$ 的延遲。這也是為什麼 §5.5 那一行 assign {c_out, sum} = a + b; 比結構化版本更好的工程:它讓合成器去挑一個滿足你限制的架構,而不是把最慢的那一個凍結進原始碼裡。覚えるべきはこの式の構造である。リップルキャリー遅延は $O(n)$。語長を倍にすれば遅延も倍になる。16 ビットでゲート 50 ps なら約 2.4 ns であり、加算器だけで 400 MHz のクロック周期を食い尽くす。64 ビットなら 9.6 ns で、何も動かない。この線形増加こそ、キャリーセレクト、キャリースキップ、キャリー先見、並列プレフィックス(Kogge–Stone、Brent–Kung)加算器が存在する理由であり、いずれも面積と引き換えに $O(\log n)$ の遅延を得る。§5.5 の一行 assign {c_out, sum} = a + b; が構造記述版より良い工学である理由でもある。制約を満たすアーキテクチャを合成器に選ばせるのであって、最も遅いものをソースに凍結するのではない。
One further consequence worth internalising: during that 48-gate-delay window, the sum outputs glitch. Bit 15's XOR sees its carry input change several times as the ripple works its way up, so sum[15] may toggle multiple times before settling. In a purely synchronous design that is harmless — nothing samples it until the clock edge, long after settling — but it burns dynamic power, and it is fatal if you ever feed a ripple-carry output into something edge-sensitive. Never clock on a data signal.還有一個值得內化的後果:在那 48 個閘延遲的窗口裡,和的輸出會發生毛刺。第 15 位元的 XOR 會隨著漣波一路往上爬而看到它的進位輸入變動好幾次,所以 sum[15] 可能在穩定之前翻轉好幾次。在純同步設計裡這無害 — 在時脈邊緣之前沒有東西會去取樣它,而那早在穩定之後 — 但它會燒掉動態功耗,而且如果你哪天把漣波進位的輸出餵進任何對邊緣敏感的東西,那就是致命的。永遠不要拿資料訊號當時脈。もう一つ身体に入れておくべき帰結がある。その 48 ゲート遅延の窓の間、和の出力はグリッチする。ビット 15 の XOR は、リップルが上がっていくにつれてキャリー入力が何度も変わるのを見るので、sum[15] は落ち着くまでに何度もトグルしうる。純粋な同期設計では無害だ——クロックエッジまで誰もサンプルせず、それは確定のはるか後である——が、動的電力を燃やすし、リップルキャリーの出力をエッジ感応な何かに食わせれば致命的になる。データ信号をクロックにしてはならない。
Writing G1 through G4 by hand is fine for four stages and intolerable for thirty-two. Verilog's array of instances creates a range of identical instances in one statement and distributes vector connections across them bit by bit.手寫 G1 到 G4 在四級時還行,三十二級就令人無法忍受。Verilog 的實例陣列用一條敘述建立一整個範圍的相同實例,並把向量連接逐位元分配給它們。G1 から G4 までを手書きするのは四段なら問題ないが、三十二段では耐えがたい。Verilog のインスタンス配列は、一つの文で同一インスタンスの範囲を作り、ベクタ接続をビットごとに分配する。
module array_of_nor (y, a, b);
input [0:7] a, b;
output [0:7] y;
nor array [0:7] (y, a, b); // 8 NOR gates
endmodulemodule array_of_adders (sum, c_out, a, b, c_in);
input [3:0] a, b;
input c_in;
output [3:0] sum;
output c_out;
wire [3:1] carry;
Add_full M [3:0] (sum, {c_out, carry[3:1]},
a, b, {carry[3:1], c_in});
endmoduleThe distribution rule is mechanical: for each port, if the connected expression is exactly as wide as the port, every instance gets the whole thing; if it is $N$ times as wide (where $N$ is the number of instances), it is split into $N$ consecutive slices, with the lowest-numbered instance taking the least significant slice.分配規則是機械式的:對每個埠而言,如果所接的運算式和埠一樣寬,每一個實例都拿到整份;如果它是埠的 $N$ 倍寬($N$ 是實例個數),就被切成 $N$ 個連續切片,編號最小的實例拿到最低位的切片。分配の規則は機械的である。各ポートについて、接続された式がポートと同じ幅ならすべてのインスタンスが全体を受け取る。幅が $N$ 倍($N$ はインスタンス数)なら、$N$ 個の連続した切片に分けられ、番号の最も小さいインスタンスが最下位の切片を取る。
Example 2 is the one to study, because the clever part is the two concatenations. Expand it instance by instance:值得細讀的是範例 2,因為巧妙之處在那兩個串接。把它逐一實例展開:精読すべきは例 2 である。巧妙なのは二つの連結だからだ。インスタンスごとに展開してみよう。
// Add_full M [3:0] (sum, {c_out, carry[3:1]}, a, b, {carry[3:1], c_in});
// is exactly equivalent to:
Add_full M[0] (sum[0], carry[1], a[0], b[0], c_in);
Add_full M[1] (sum[1], carry[2], a[1], b[1], carry[1]);
Add_full M[2] (sum[2], carry[3], a[2], b[2], carry[2]);
Add_full M[3] (sum[3], c_out, a[3], b[3], carry[3]);
{c_out, carry[3:1]} is 4 bits: c_out is the MSB, then carry[3], carry[2], carry[1] as the LSB. Split across M[0..3], instance M[0] gets the LSB carry[1] and M[3] gets the MSB c_out. That is exactly right: stage 0's carry-out is the carry into stage 1, and stage 3's carry-out is the adder's carry-out.進位輸出埠。運算式 {c_out, carry[3:1]} 是 4 位元:c_out 是 MSB,接著是 carry[3]、carry[2],而 carry[1] 是 LSB。分配給 M[0..3] 時,實例 M[0] 拿到 LSB carry[1],M[3] 拿到 MSB c_out。這正好對:第 0 級的進位輸出就是進入第 1 級的進位,而第 3 級的進位輸出就是整個加法器的進位輸出。キャリー出力ポート。式 {c_out, carry[3:1]} は 4 ビットで、c_out が MSB、続いて carry[3]、carry[2]、そして carry[1] が LSB である。M[0..3] に分配すると、M[0] は LSB の carry[1] を、M[3] は MSB の c_out を受け取る。これはまさに正しい。段 0 のキャリー出力は段 1 へのキャリーであり、段 3 のキャリー出力は加算器全体のキャリー出力である。{carry[3:1], c_in} is also 4 bits, with c_in as the LSB. So M[0] gets c_in, M[1] gets carry[1], M[2] gets carry[2], M[3] gets carry[3]. Also exactly right.進位輸入埠。運算式 {carry[3:1], c_in} 同樣是 4 位元,而 c_in 是 LSB。所以 M[0] 拿到 c_in、M[1] 拿到 carry[1]、M[2] 拿到 carry[2]、M[3] 拿到 carry[3]。同樣完全正確。キャリー入力ポート。式 {carry[3:1], c_in} もまた 4 ビットで、c_in が LSB である。したがって M[0] は c_in、M[1] は carry[1]、M[2] は carry[2]、M[3] は carry[3] を受け取る。これも完全に正しい。carry[3:1] appears shifted by one bit position in the two concatenations, which is what makes a chain out of an array. The array construct itself has no notion of chaining; the shift does all the work.關鍵技巧。同一段 carry[3:1] 在兩個串接裡錯開了一個位元位置,這就是把一個陣列變成一條鏈的原因。陣列這個語法本身完全沒有「串鏈」的概念;全部的工作都是那個位移做的。種明かし。同じ carry[3:1] が二つの連結でビット位置を一つずらして現れる。これが配列を鎖に変えている。配列という構文自体に連鎖の概念はない。仕事はすべてこのずれがしている。carry is declared [3:1] — three bits — and each concatenation adds one more. Declare it [3:0] by mistake and both expressions become 5 bits wide, which is not a multiple of 4; the compiler reports a port width mismatch at the instantiation line and you will stare at it for a while before noticing the wire declaration above.位寬的算術。對一個由 1 位元埠構成的 4 實例陣列,兩個串接都必須剛好是 4 位元。carry 宣告成 [3:1] — 三個位元 — 而每個串接再加上一個。萬一誤宣告成 [3:0],兩個運算式就都變成 5 位元寬,而 5 不是 4 的倍數;編譯器會在實例化那一行回報埠位寬不符,而你會盯著它看一陣子,才注意到上面那行 wire 宣告。幅の算術。1 ビットのポートを持つ 4 インスタンスの配列では、どちらの連結もちょうど 4 ビットでなければならない。carry は [3:1]——3 ビット——と宣言され、各連結がもう 1 ビット足す。誤って [3:0] と宣言すれば両方の式が 5 ビットになり、5 は 4 の倍数でない。コンパイラはインスタンス化の行でポート幅不一致を報告し、君はしばらくそれを睨んだあげく、上にある wire 宣言にようやく気づくことになる。generate實例陣列與 generate 的比較インスタンス配列と generateVerilog-2001 added generate/genvar, which is strictly more powerful: it can instantiate different modules in different iterations, apply conditions, and compute each port connection with an expression instead of relying on bit-slicing. For a regular chain like this adder, either works and the array form is shorter. For anything irregular — a tree, a first stage that differs from the rest — use generate. Both unroll completely at elaboration time, so neither costs anything at run time, and both appear in the netlist as individually named instances (M[2] or gen_loop[2].u_add).Verilog-2001 加入了 generate/genvar,它嚴格來說更強大:它可以在不同次迭代裡實例化不同的模組、套用條件,並用運算式計算每一個埠連接,而不必依賴位元切片。對像這個加法器這樣規則的鏈,兩種都行,而陣列形式比較短。只要有任何不規則之處 — 一棵樹、第一級與其餘各級不同 — 就用 generate。兩者都在展開期完全攤開,所以執行期都不花成本,而且兩者在網表裡都以個別命名的實例出現(M[2] 或 gen_loop[2].u_add)。Verilog-2001 は generate/genvar を追加した。こちらは厳密に強力で、反復ごとに異なるモジュールをインスタンス化でき、条件を適用でき、ビット切片に頼らず式でポート接続を計算できる。この加算器のような規則的な鎖ならどちらでもよく、配列形式のほうが短い。不規則なもの——木構造、最初の段だけ他と違う場合——には generate を使え。どちらもエラボレーション時に完全に展開されるので実行時コストはなく、ネットリストにはどちらも個別に名付けられたインスタンスとして現れる(M[2] や gen_loop[2].u_add)。
Problem: Given module child (a, b, c); with a and b inputs and c an output, and a parent with wire u, v, w; (a) Which ports connect to which wires in child m2 (.c(w), .a(u), .b(v));? (b) Rewrite child m3 (u, , w); using named binding. (c) What is wrong with child (u, v, w);? (d) A colleague later changes the child to module child (a, b, c, en); with en a new input. Which of m1, m2, m3 still compile, and which still behave correctly? (e) What is the value of w in m3 if child computes assign c = a & b;?題目:給定 module child (a, b, c);,其中 a 與 b 是輸入、c 是輸出,而父模組有 wire u, v, w;。(a) 在 child m2 (.c(w), .a(u), .b(v)); 裡,哪個埠接到哪條線?(b) 用名稱對應改寫 child m3 (u, , w);。(c) child (u, v, w); 哪裡有問題?(d) 某位同事後來把子模組改成 module child (a, b, c, en);,en 是新的輸入。m1、m2、m3 哪些還能編譯,哪些行為還正確?(e) 如果 child 內部算的是 assign c = a & b;,m3 裡的 w 是什麼值?問題:module child (a, b, c); があり、a と b は入力、c は出力、親側には wire u, v, w; があるとする。(a) child m2 (.c(w), .a(u), .b(v)); ではどのポートがどの配線につながるか。(b) child m3 (u, , w); を名前指定で書き直せ。(c) child (u, v, w); の何が誤りか。(d) 同僚が後に子を module child (a, b, c, en); へ変え、en という新しい入力を加えた。m1、m2、m3 のどれがなおコンパイルでき、どれがなお正しく振る舞うか。(e) child が assign c = a & b; を計算するとき、m3 の w の値は何か。
(a) a–u, b–v, c–w. The order in the instantiation is irrelevant for named binding.(a) a–u、b–v、c–w。對名稱對應而言,實例化裡的順序無關緊要。(a) a–u、b–v、c–w。名前指定ではインスタンス化中の順序は関係ない。
(b) child m3 (.a(u), .c(w)); — simply omit b. Equivalently child m3 (.a(u), .b(), .c(w));, which is more explicit about the intent and is the better style because a reviewer can see you meant it.(b) child m3 (.a(u), .c(w)); — 把 b 直接省略即可。等價地也可以寫 child m3 (.a(u), .b(), .c(w));,那把意圖表達得更明確,也是比較好的風格,因為審查者看得出你是故意的。(b) child m3 (.a(u), .c(w));——b を単に省く。等価な書き方として child m3 (.a(u), .b(), .c(w)); もあり、意図がより明示的でスタイルとしても優れている。レビュアーが「わざとだ」と読み取れるからだ。
(c) No instance name. Module instances must be named; only gate primitives may omit the name. The compiler reports a syntax error at the open parenthesis.(c) 沒有實例名稱。模組實例必須命名;只有閘基元可以省略名稱。編譯器會在左括號處回報語法錯誤。(c) インスタンス名がない。モジュールインスタンスには名前が必須で、名前を省けるのはゲートプリミティブだけである。コンパイラは開き括弧の位置で構文エラーを出す。
(d) All three still compile — Verilog allows missing trailing positional connections and missing named connections. But m1 and m3 now have en unconnected and floating at z, so c probably becomes x; m2 is in exactly the same boat. None of them is safe. The real lesson is that adding a port to a widely-instantiated module is a breaking change, and the only protection is a lint rule that errors on unconnected inputs. Named binding does not save you here; it only saves you from misconnection, not from omission.(d) 三個都還能編譯 — Verilog 允許位置式連接在結尾缺項,也允許名稱式連接缺項。但 m1 與 m3 現在的 en 未接、浮在 z,所以 c 大概會變成 x;m2 的處境一模一樣。三個都不安全。真正的教訓是:對一個被大量實例化的模組加一個埠,是一次破壞性變更,而唯一的保護是一條「未接輸入就報錯」的 lint 規則。名稱對應在這裡救不了你;它只救得了接錯,救不了漏接。(d) 三つともコンパイルは通る。Verilog は位置指定の末尾欠落も名前指定の欠落も許すからだ。しかし m1 と m3 は en が未接続で z に浮き、c はおそらく x になる。m2 もまったく同じ境遇である。三つとも安全でない。真の教訓は、広くインスタンス化されたモジュールにポートを足すのは破壊的変更であり、唯一の防御は未接続入力をエラーにする lint 規則だということだ。名前指定はここでは助けにならない。救ってくれるのは誤接続であって、欠落ではない。
(e) b is unconnected, so inside the child it is 1'bz. By the AND table, u & z is 0 if u is 0 (controlling value) and x otherwise. So w is 0 when u = 0 and x when u = 1 — a half-working circuit, which is the most expensive kind of bug because it passes a careless test.(e) b 未接,所以在子模組內部它是 1'bz。依 AND 表,u & z 在 u 為 0 時是 0(控制值),否則是 x。所以 w 在 u = 0 時是 0、在 u = 1 時是 x — 一個半通不通的電路,而那是最昂貴的一種臭蟲,因為它能通過一個不夠仔細的測試。(e) b は未接続なので、子の内部では 1'bz である。AND 表により u & z は、u が 0 なら支配値で 0、そうでなければ x になる。したがって w は u = 0 のとき 0、u = 1 のとき x——半分だけ動く回路であり、これは最も高くつくバグだ。雑なテストを通ってしまうからである。
Problem: Every primitive gate (xor, nand, not, or) has delay $t_g = 60$ ps. (a) What is the c_in-to-c_out delay of one Add_full? (b) Of Add_rca_4? (c) Of Add_rca_16? (d) In picoseconds, what is the maximum clock frequency of a circuit whose only combinational logic is this 16-bit adder between two registers, if the flip-flops have $t_{cq} = 90$ ps and $t_{su} = 50$ ps? (e) The spec demands 500 MHz. Name three ways to get there and say which is architectural.題目:每顆基元閘(xor、nand、not、or)的延遲都是 $t_g = 60$ ps。(a) 一個 Add_full 的 c_in 到 c_out 延遲是多少?(b) Add_rca_4 呢?(c) Add_rca_16 呢?(d) 若某電路在兩個暫存器之間唯一的組合邏輯就是這顆 16 位元加法器,而正反器的 $t_{cq} = 90$ ps、$t_{su} = 50$ ps,以皮秒計,它的最高時脈頻率是多少?(e) 規格要求 500 MHz。說出三種達成的方法,並指出哪一種屬於架構層級。問題:各プリミティブゲート(xor、nand、not、or)の遅延を $t_g = 60$ ps とする。(a) Add_full 一つの c_in から c_out までの遅延は。(b) Add_rca_4 では。(c) Add_rca_16 では。(d) 二つのレジスタの間の組合せ論理がこの 16 ビット加算器だけで、フリップフロップが $t_{cq} = 90$ ps、$t_{su} = 50$ ps のとき、最高クロック周波数はいくらか(ピコ秒で計算せよ)。(e) 仕様は 500 MHz を要求する。到達する方法を三つ挙げ、どれがアーキテクチャ上のものかを述べよ。
(a) c_in enters M2's b port → M2's nand → M2's not → the final or = 3 gates = 180 ps.(a) c_in 進入 M2 的 b 埠 → M2 的 nand → M2 的 not → 最後那顆 or = 3 顆閘 = 180 ps。(a) c_in が M2 の b ポートに入り → M2 の nand → M2 の not → 最後の or。ゲート 3 段、すなわち 180 ps。
(b) Four stages chained: $4 \times 3t_g = 12t_g$ = 720 ps.(b) 四級串接:$4 \times 3t_g = 12t_g$ = 720 ps。(b) 四段の連鎖:$4 \times 3t_g = 12t_g$ = 720 ps。
(c) Sixteen stages: $16 \times 3t_g = 48t_g$ = 2880 ps = 2.88 ns. (Strictly, the last stage's output of interest is c_out so 48 is exact; if you want sum[15] instead it is $15\times 3t_g + t_g = 46t_g$, slightly less — the carry is the critical output.)(c) 十六級:$16 \times 3t_g = 48t_g$ = 2880 ps = 2.88 ns。(嚴格說,我們關心的最後一級輸出是 c_out,所以 48 是精確值;若你要的是 sum[15],則是 $15\times 3t_g + t_g = 46t_g$,略少一些 — 進位才是關鍵輸出。)(c) 十六段:$16 \times 3t_g = 48t_g$ = 2880 ps = 2.88 ns。(厳密には、関心のある最終段の出力は c_out なので 48 が正確である。sum[15] なら $15\times 3t_g + t_g = 46t_g$ とわずかに小さい。クリティカルな出力はキャリーのほうだ。)
(d) $T_{\min} = t_{cq} + t_{\text{logic}} + t_{su} = 90 + 2880 + 50 = 3020$ ps, so $f_{\max} = 1/3.02\text{ ns} \approx \mathbf{331\ MHz}$.(d) $T_{\min} = t_{cq} + t_{\text{logic}} + t_{su} = 90 + 2880 + 50 = 3020$ ps,所以 $f_{\max} = 1/3.02\text{ ns} \approx \mathbf{331\ MHz}$。(d) $T_{\min} = t_{cq} + t_{\text{logic}} + t_{su} = 90 + 2880 + 50 = 3020$ ps なので、$f_{\max} = 1/3.02\text{ ns} \approx \mathbf{331\ MHz}$。
(e) 500 MHz means $T = 2000$ ps, leaving $2000 - 90 - 50 = 1860$ ps for logic — we are 1020 ps over.(e) 500 MHz 代表 $T = 2000$ ps,留給邏輯的是 $2000 - 90 - 50 = 1860$ ps — 我們超出了 1020 ps。(e) 500 MHz は $T = 2000$ ps を意味し、論理に残るのは $2000 - 90 - 50 = 1860$ ps——1020 ps の超過である。
assign {c_out, sum} = a + b; with a 2 ns constraint, letting Design Compiler select from its DesignWare adder family.換一個更快的加法器架構(超前進位、進位選擇、Kogge–Stone)。一個 16 位元 CLA 的進位邏輯大約是 $O(\log_2 16)=4$ 層,而不是 16 層,輕鬆落在預算內。這是架構層級的修法,而要得到它的唯一辦法,就是別再親手寫漣波結構,改寫 assign {c_out, sum} = a + b; 並給一條 2 ns 的限制,讓 Design Compiler 從它的 DesignWare 加法器家族裡挑。より速い加算器アーキテクチャを使う(キャリー先見、キャリーセレクト、Kogge–Stone)。16 ビット CLA のキャリー論理はおよそ $O(\log_2 16)=4$ 段であり 16 段ではないので、余裕で予算に収まる。これがアーキテクチャ上の解決であり、手に入れる唯一の方法は、リップル構造を手書きするのをやめ、assign {c_out, sum} = a + b; と書いて 2 ns の制約を与え、Design Compiler に DesignWare の加算器一族から選ばせることである。The first two are architectural decisions a human makes; only the third is something a tool does for you. That is the Y-chart lesson from §1.5 arriving with a number attached.前兩種是人做的架構決策;只有第三種是工具替你做的。那就是 §1.5 的 Y 圖教訓,這次帶著一個數字出現。最初の二つは人間が下すアーキテクチャ上の決定であり、ツールが代わりにやってくれるのは三つ目だけである。§1.5 の Y チャートの教訓が、今度は数字を携えて現れたわけだ。
The slides' definition is compact: a sequential circuit "models the storage elements with a clock". More precisely, a circuit is sequential if its output depends not only on the present inputs but on the history of inputs — which requires state, which requires an element that remembers. In a synchronous design that element is the edge-triggered D flip-flop, and the clock is the signal that decides when it remembers.投影片的定義很精簡:順序電路「用一個時脈來建模儲存元件」。更精確地說,如果一個電路的輸出不只取決於當下的輸入,還取決於輸入的歷史,它就是順序的 — 而那需要狀態,狀態需要一個會記憶的元件。在同步設計中,那個元件就是邊緣觸發 D 型正反器,而時脈就是決定它何時記憶的那條訊號。スライドの定義は簡潔だ。順序回路は「記憶素子をクロックとともにモデル化する」。より正確には、回路の出力が現在の入力だけでなく入力の履歴にも依存するとき、その回路は順序的である。それには状態が要り、状態には記憶する素子が要る。同期設計におけるその素子がエッジトリガ D フリップフロップであり、いつ記憶するかを決める信号がクロックである。
Everything in this course is synchronous. That is a design-methodology decision, not a law of physics, and it is worth knowing what it buys: a synchronous circuit's behaviour is fully described by "what combinational function computes each flip-flop's next state", and its timing is fully described by "does every combinational path fit inside one clock period". Both are mechanically checkable — the first by simulation, the second by static timing analysis — and both become intractable the moment you introduce asynchronous feedback or gated clocks. The entire cell-based flow, from synthesis through APR to sign-off, assumes synchrony.這門課裡的一切都是同步的。那是一項設計方法論的決定,不是物理定律,而且值得知道它換來什麼:一個同步電路的行為,完全可以用「哪個組合函數算出每顆正反器的下一個狀態」來描述;它的時序,完全可以用「每條組合路徑是否塞得進一個時脈週期」來描述。兩者都可以機械化檢查 — 前者靠模擬,後者靠靜態時序分析 — 而一旦你引入非同步回授或閘控時脈,兩者都會變得難以處理。整條 cell-based 流程,從合成到 APR 再到簽核,都假設了同步性。この科目のすべては同期式である。それは物理法則ではなく設計方法論の決定であり、見返りを知っておく価値がある。同期回路の振る舞いは「どの組合せ関数が各フリップフロップの次状態を計算するか」で完全に記述でき、そのタイミングは「すべての組合せパスが 1 クロック周期に収まるか」で完全に記述できる。前者はシミュレーションで、後者は静的タイミング解析で、どちらも機械的に検証できる。そして非同期帰還やゲーテッドクロックを持ち込んだ瞬間、どちらも手に負えなくなる。合成から APR、サインオフに至るセルベース・フロー全体が同期性を前提にしている。
module Flip_flop (q, data_in, clk, rst); input data_in, clk, rst; output q; reg q; always @ (posedge clk) // declaration of synchronous behavior begin if (rst == 1) q = 0; // rst is active high else q = data_in; end endmodule
Dissect the three ingredients that make this a flip-flop and not a wire.拆解讓這東西成為正反器、而不是一條線的三項要素。これを配線ではなくフリップフロップにしている三つの要素を解剖しよう。
posedge clk in the sensitivity list. This is the single decisive feature. An always block triggered by an edge can only execute at discrete instants, so whatever it assigns must hold between those instants — and the only cell that does that is a flip-flop. Synthesis recognises the pattern and emits one DFFRX1 (or whatever the library calls it) per bit of q.敏感清單裡的 posedge clk。這是唯一具決定性的特徵。由邊緣觸發的 always 區塊只能在離散的瞬間執行,所以它所指定的任何東西都必須在那些瞬間之間保持住 — 而唯一會做這件事的 cell 就是正反器。合成認得這個樣式,並為 q 的每一個位元產生一顆 DFFRX1(或你的函式庫怎麼稱呼它)。感度リストの posedge clk。これが唯一の決定的特徴である。エッジで起動される always ブロックは離散的な瞬間にしか実行できないので、そこで代入されるものはその間ずっと保持されねばならない。それを行う唯一のセルがフリップフロップである。合成はこのパターンを認識し、q のビットごとに DFFRX1(ライブラリがどう呼ぶにせよ)を一つ出す。q declared reg. Necessary because q is assigned procedurally, but — as §3.5 laboured — not sufficient and not the reason it becomes a flop.q 宣告成 reg。這是必要的,因為 q 是以程序敘述指定的;但正如 §3.5 費力說明的,它並不充分,也不是它變成正反器的原因。q を reg と宣言すること。q が手続き的に代入される以上これは必要だが、§3.5 が力説したとおり十分ではなく、フリップフロップになる理由でもない。rst tested inside the edge-triggered block and absent from the sensitivity list. This makes the reset synchronous: it has no effect whatsoever except at a rising clock edge. Hold rst high for a picosecond between edges and nothing happens. Compare the asynchronous form, which appears in Lecture 4 and Lecture 6:rst 在邊緣觸發區塊內部被測試,而且不在敏感清單裡。這讓這個重置成為同步的:除了在時脈上升邊緣之外,它完全不起作用。在兩次邊緣之間把 rst 拉高一皮秒,什麼事也不會發生。對照非同步形式,那會出現在第 4 講與第 6 講:rst がエッジトリガ・ブロックの内側で検査され、感度リストにはないこと。これがリセットを同期式にする。クロックの立ち上がりエッジ以外では一切効かない。エッジの合間に rst を 1 ピコ秒高くしても何も起きない。第 4 講と第 6 講に現れる非同期形式と比べよ。always @ (posedge clk) if (rst) q <= 1'b0; else q <= data_in;
Reset is just another input to the flop's D logic. Infers a plain DFF plus a mux (or a DFF cell with a synchronous-reset pin, if the library has one). Needs a running clock to take effect.重置只是正反器 D 側邏輯的又一個輸入。推斷出的是一顆普通 DFF 加一顆多工器(或者,如果函式庫有的話,一顆帶同步重置腳的 DFF cell)。它需要時脈在跑才會生效。リセットはフリップフロップの D 側論理に入るもう一つの入力にすぎない。推論されるのは素の DFF とマルチプレクサ(あるいはライブラリにあれば同期リセットピン付きの DFF セル)である。効かせるにはクロックが動いている必要がある。
always @ (posedge clk or posedge rst)
if (rst) q <= 1'b0;
else q <= data_in;
Reset appears in the sensitivity list, so it acts immediately. Infers a DFF cell with a dedicated CLR pin. Works before the clock starts — which is why power-on reset is almost always asynchronous.重置出現在敏感清單裡,所以它立即生效。推斷出的是一顆帶專用 CLR 腳的 DFF cell。它在時脈開始之前就能作用 — 這正是為什麼上電重置幾乎總是非同步的。リセットが感度リストに現れるので即座に効く。推論されるのは専用 CLR ピンを持つ DFF セルである。クロックが始まる前から働く——パワーオンリセットがほぼ常に非同期である理由がこれだ。
For the asynchronous form, synthesis will only recognise your intent if the code matches the template exactly: the sensitivity list contains posedge clk and the reset edge and nothing else, and the reset condition is the first test in the block, with no other statement before it. Write always @(posedge clk or posedge rst) begin x = y; if (rst) … end and most tools report an error or build something you did not ask for, because there is no cell that does "on either edge, first do some logic, then maybe clear". The template is not a style guideline; it is the pattern-match key.對非同步形式而言,只有當你的程式碼完全符合樣板時,合成才會認出你的意圖:敏感清單裡只有 posedge clk 與重置邊緣、別無他物,而且重置條件必須是區塊中第一個測試,前面不能有任何其他敘述。寫成 always @(posedge clk or posedge rst) begin x = y; if (rst) … end,多數工具會報錯,或是搭出你沒要的東西,因為根本沒有一顆 cell 做得到「在任一邊緣,先做一些邏輯,然後也許清除」。那個樣板不是風格指引;它是樣式比對的鑰匙。非同期形式では、コードがテンプレートに厳密に一致したときにだけ合成は意図を認識する。感度リストには posedge clk とリセットのエッジだけがあり他は何もなく、リセット条件はブロックの最初の検査で、その前に他の文があってはならない。always @(posedge clk or posedge rst) begin x = y; if (rst) … end と書けば、多くのツールはエラーを出すか、頼んでいないものを作る。「どちらかのエッジで、まず何か論理を行い、それからたぶんクリアする」セルなど存在しないからだ。このテンプレートはスタイル指針ではなく、パターンマッチの鍵である。
module Flip_flop (q, data_in, clk, set, rst);
input data_in, clk, set, rst;
output q;
reg q;
always @ (posedge clk)
begin
if (rst == 0) q = 0; // rst active LOW
else
if (set == 0) q = 1; // set active LOW
else q = data_in;
end
endmodule
Note that in this second slide both controls are active low — they act when the signal is 0 — whereas the first slide's single reset was active high. The lecture is deliberately showing both polarities. Active-low controls are the industry norm for reset (the usual name is rst_n or resetb), partly historical and partly because a floating or un-driven control line tends to pull toward the supply on a CMOS input, which with active-low polarity means "inactive".注意第二張投影片裡兩個控制訊號都是低準位有效 — 它們在訊號為 0 時作用 — 而第一張投影片的那個單一重置是高準位有效。這一講是刻意把兩種極性都呈現出來。低準位有效的控制是業界對重置的常態(常見名稱是 rst_n 或 resetb),一部分是歷史因素,一部分是因為一條浮接或無人驅動的控制線在 CMOS 輸入上傾向被拉向電源,而在低準位有效的極性下,那意味著「未作用」。二枚目のスライドでは両方の制御が負論理——信号が 0 のときに効く——であるのに対し、一枚目の単一のリセットは正論理であった点に注意。講義は意図的に両方の極性を見せている。リセットの制御を負論理にするのは業界の標準であり(名前はたいてい rst_n か resetb)、半分は歴史的な理由、半分は、浮いたり駆動されていない制御線が CMOS 入力では電源側へ引かれがちで、負論理ならそれが「非アクティブ」を意味するからである。
The nested if/else establishes a priority: reset is tested first, so if rst and set are both asserted, q goes to 0. There is no ambiguity and no race; the priority is whatever the code says it is, and the synthesiser builds exactly that. If the library's flip-flop cell has both a set and a clear pin, their simultaneous assertion may be undefined in the cell's datasheet, so a synthesiser often implements set/reset priority in the D-side logic rather than on the cell pins. Either way the function is what you wrote.巢狀的 if/else 建立了一個優先權:重置先被測試,所以如果 rst 與 set 同時成立,q 會變成 0。這裡沒有歧義也沒有競爭;優先權就是程式碼說的那樣,而合成器就照那樣搭出來。如果函式庫的正反器 cell 同時有 set 與 clear 腳,兩者同時成立時的行為在那顆 cell 的資料表裡可能是未定義的,所以合成器常常改在 D 側邏輯裡實現 set/reset 的優先權,而不是用 cell 的腳。不論哪一種,功能都是你寫的那樣。入れ子の if/else が優先順位を定める。リセットが先に検査されるので、rst と set が同時にアサートされれば q は 0 になる。曖昧さも競合もない。優先順位はコードが言うとおりであり、合成器はそのとおりに作る。ライブラリのフリップフロップ・セルがセットピンとクリアピンの両方を持つ場合、同時アサート時の挙動はセルのデータシートで未定義かもしれない。そこで合成器はセットとリセットの優先順位を、セルのピンではなく D 側の論理で実現することが多い。いずれにせよ機能は君が書いたとおりになる。
rst | set | Next q at posedgeposedge 之後的 qposedge 後の q | Why原因理由 |
|---|---|---|---|
| 0 | 0 | 0 | rst is tested first and winsrst 先被測試,所以它勝出rst が先に検査され、こちらが勝つ |
| 0 | 1 | 0 | reset active重置作用中リセットが有効 |
| 1 | 0 | 1 | reset inactive, set active重置未作用,設定作用中リセット無効、セットが有効 |
| 1 | 1 | data_in | neither control active — normal operation兩個控制都未作用 — 正常運作どちらの制御も無効——通常動作 |
Both flip-flop slides declare module Flip_flop (…) with different port lists — one with four ports, one with five. Only one of them can exist in a given compilation. If you copy both into one file you get a duplicate-module error (or worse, with some tools, a silent "last definition wins"). Also note that the second version's module name and the first version's are identical while their port orders differ, which means any positional instantiation written against one will silently misconnect against the other. §7.2's argument again.兩張正反器投影片都宣告了 module Flip_flop (…),但埠列不同 — 一個四個埠,一個五個埠。在同一次編譯裡只能存在其中一個。如果你把兩個都複製到同一個檔案,就會得到重複模組的錯誤(或更糟,在某些工具上是安靜的「最後一個定義勝出」)。另外注意,第二版與第一版的模組名稱完全相同,但埠的順序不同,這表示任何針對其中一版寫的位置式實例化,拿去對另一版就會安靜地接錯。又是 §7.2 的論證。フリップフロップの二枚のスライドはどちらも module Flip_flop (…) を宣言しているが、ポートリストが異なる——片方は四ポート、もう片方は五ポート。一回のコンパイルに存在できるのはどちらか一方だけだ。両方を同じファイルに写せばモジュール重複のエラーになる(さらに悪い場合、ツールによっては黙って「最後の定義が勝つ」)。さらに、第二版と第一版はモジュール名が同一でありながらポートの順序が違う。つまり一方に合わせて書いた位置指定のインスタンス化は、もう一方に対しては黙って誤接続になる。またしても §7.2 の論拠である。
The slides' flip-flops use the blocking assignment = inside always @(posedge clk). In the examples shown this is harmless, because each block assigns exactly one variable and no other block reads it in the same time step. But it is a habit that fails the moment you write two flip-flops that feed each other, and since the pipeline in §8.6 is exactly that, you need the rule now even though Lecture 4 is where it is taught properly.投影片的正反器在 always @(posedge clk) 裡用的是阻隔式指定 =。在所示的範例中這是無害的,因為每個區塊只指定一個變數,而且在同一個時間步裡沒有別的區塊會讀它。但那是一個習慣,而這個習慣在你寫出兩顆互相餵食的正反器的那一刻就失效了;既然 §8.6 的管線正是那樣,你現在就需要這條規則,即使第 4 講才是正式教它的地方。スライドのフリップフロップは always @(posedge clk) の中でブロッキング代入 = を使っている。示されている例では無害だ。各ブロックが変数を一つだけ代入し、同じ時間ステップで他のブロックがそれを読まないからである。しかしそれは習慣であり、互いを養う二つのフリップフロップを書いた瞬間に破綻する。§8.6 のパイプラインがまさにそれなので、正式に教えるのは第 4 講でも、規則はいま必要になる。
always @(posedge clk) begin b = a; // b takes a NOW c = b; // c takes the NEW b end // Result: c = a after ONE edge. // One flip-flop's worth of shifting, // from two flip-flops' worth of code. // And if the two statements were in // two separate always blocks, the answer // would depend on which the simulator // happened to run first — a race.
always @(posedge clk) begin b <= a; // schedule b := old a c <= b; // schedule c := old b end // Result: a 2-stage shift register. // Both right-hand sides are sampled // before any left-hand side is updated, // which is exactly what real flip-flops // do at a clock edge, and the answer // does not depend on statement order.
always @(posedge clk), use non-blocking <=.在 always @(posedge clk) 裡,用非阻隔式 <=。always @(posedge clk) の中ではノンブロッキング <= を使う。always @(*), use blocking =.在組合式的 always @(*) 裡,用阻隔式 =。組合せの always @(*) の中ではブロッキング = を使う。The reason is the simulator's event scheduler. At each time step it processes an active region, where blocking assignments take effect immediately, and then a non-blocking update region, where all the right-hand sides sampled earlier are written to their targets. Because every clocked block samples before any writes, the collective effect is "all flip-flops load simultaneously from the previous state" — which is physically what happens. Blocking assignments in clocked blocks make the result depend on the order in which the simulator chose to run the blocks, which the standard explicitly leaves undefined. Synthesis, which has no event queue, infers the shift register either way; so blocking assignments in clocked logic are the purest form of simulation/synthesis mismatch, and the one that costs the most debugging time in this course.理由在於模擬器的事件排程器。在每個時間步,它先處理一個active 區域,阻隔式指定在那裡立即生效;接著處理一個非阻隔式更新區域,先前取樣下來的所有右側值在那裡被寫入各自的目標。因為每個時脈區塊都在任何寫入之前先取樣,集體效果就是「所有正反器同時從先前的狀態載入」 — 而那正是物理上真正發生的事。在時脈區塊裡用阻隔式指定,會讓結果取決於模擬器選擇執行這些區塊的順序,而標準明確把那個順序留作未定義。合成沒有事件佇列,兩種寫法它都推斷出同一個移位暫存器;所以時脈邏輯裡的阻隔式指定是最純粹的模擬/合成不一致,也是這門課裡最耗除錯時間的一種。理由はシミュレータのイベントスケジューラにある。各時間ステップで、まずアクティブ領域が処理され、そこではブロッキング代入が即座に効く。続いてノンブロッキング更新領域が処理され、先にサンプルされた右辺の値がそれぞれの左辺へ書き込まれる。どのクロック同期ブロックも書き込みより前にサンプルするので、全体としての効果は「すべてのフリップフロップが前の状態から同時にロードする」となり、それが物理的に起きていることである。クロック同期ブロックでブロッキング代入を使うと、結果はシミュレータがブロックを走らせた順序に依存し、標準はその順序を明示的に未定義としている。合成にはイベントキューがないのでどちらでも同じシフトレジスタを推論する。したがってクロック同期論理のブロッキング代入は最も純粋なシミュレーション/合成ミスマッチであり、この科目で最もデバッグ時間を食うものでもある。
A flip-flop is not instantaneous. Three numbers from its datasheet govern everything:正反器不是瞬時的。它資料表上的三個數字主宰了一切:フリップフロップは瞬時ではない。データシートの三つの数値がすべてを支配する。
| Parameter參數パラメータ | Meaning意義意味 | Typical (28 nm)典型值(28 nm)典型値(28 nm) |
|---|---|---|
| $t_{cq}$ (clock-to-Q) | Delay from the clock edge until Q is valid從時脈邊緣到 Q 有效為止的延遲クロックエッジから Q が確定するまでの遅延 | 40–120 ps |
| $t_{su}$ (setup) | How long D must be stable before the edgeD 必須在邊緣之前保持穩定多久D がエッジの前に安定していなければならない時間 | 20–60 ps |
| $t_{h}$ (hold) | How long D must stay stable after the edgeD 必須在邊緣之後繼續穩定多久D がエッジの後も安定していなければならない時間 | 0–30 ps, sometimes negative0–30 ps,有時為負0–30 ps、負になることもある |
For a path from flop A through combinational logic to flop B, both clocked by the same clock of period $T$:對一條從正反器 A 經組合邏輯到正反器 B 的路徑,兩者由同一個週期為 $T$ 的時脈驅動:フリップフロップ A から組合せ論理を経てフリップフロップ B へ至る経路について、両者が周期 $T$ の同じクロックで駆動されるとき:
Two very different kinds of failure live in those two lines.那兩行式子裡住著兩種非常不同的失效。この二つの式には、まったく異なる二種類の失敗が住んでいる。
A setup violation is a speed problem. The logic is too slow for the clock; the data arrives after the capturing edge has already sampled. The fix is to make the path faster or the clock slower, and you can always test for it by running the chip at a lower frequency — if it works at 200 MHz and fails at 400 MHz, you have a setup problem. Note that $T$ appears on the right: setup is the constraint that the clock period is for, and it is why the 48-gate ripple-carry delay of §7.5 translated directly into a maximum frequency.setup 違規是速度問題。邏輯對這個時脈來說太慢了;資料在擷取邊緣已經取樣之後才抵達。修法是把路徑弄快或把時脈放慢,而你永遠可以用降頻來測它 — 如果它在 200 MHz 可以跑、400 MHz 掛掉,那就是 setup 問題。注意 $T$ 出現在右邊:時脈週期就是為了 setup 這條限制而存在的,這也是為什麼 §7.5 那 48 個閘延遲的漣波進位會直接換算成一個最高頻率。セットアップ違反は速度の問題である。論理がクロックに対して遅すぎ、データが捕捉エッジのサンプル後に到着する。直し方は経路を速くするかクロックを遅くすることであり、周波数を下げれば必ず切り分けられる。200 MHz で動き 400 MHz で落ちるなら、それはセットアップの問題だ。右辺に $T$ がある点に注意せよ。クロック周期はセットアップ制約のためにあるのであり、§7.5 の 48 ゲート遅延のリップルキャリーがそのまま最高周波数に翻訳された理由でもある。
A hold violation is a race problem and it is fatal. There is no $T$ in the hold equation. A chip with a hold violation fails at every frequency, including DC, and cannot be rescued by slowing the clock. Hold violations come from paths that are too fast — typically a flop driving another flop directly with almost no logic between them, where $t_{cq}$ of the launching flop is less than $t_h$ plus the clock skew of the capturing one. They are fixed by inserting buffers to add delay, which is why APR tools spend a whole optimisation pass inserting what looks like pointless buffering. This is also why clock-tree synthesis works so hard to minimise skew: skew eats margin on both constraints, but it does so asymmetrically, helping setup on one path and hurting hold on the same path.hold 違規是競爭問題,而且是致命的。hold 的式子裡沒有 $T$。一顆有 hold 違規的晶片在任何頻率下都會失敗,包括直流,而且無法靠放慢時脈來挽救。hold 違規來自太快的路徑 — 典型是一顆正反器幾乎不經任何邏輯就直接驅動另一顆,此時發射端的 $t_{cq}$ 小於擷取端的 $t_h$ 加上時脈偏斜。修法是插入緩衝器來增加延遲,這正是為什麼 APR 工具會花一整趟最佳化去插入那些看起來毫無意義的緩衝。這也是為什麼時脈樹合成如此拚命地把偏斜壓小:偏斜會吃掉兩條限制的裕度,但吃的方式是不對稱的 — 它對同一條路徑的 setup 有幫助,卻對 hold 有害。ホールド違反は競合の問題であり、致命的である。ホールドの式に $T$ はない。ホールド違反を抱えたチップは直流を含むあらゆる周波数で失敗し、クロックを遅くしても救えない。ホールド違反は速すぎる経路から生じる。典型的には、論理をほとんど挟まずフリップフロップが別のフリップフロップを直接駆動し、送出側の $t_{cq}$ が捕捉側の $t_h$ とクロックスキューの和より小さい場合である。直し方はバッファを挿入して遅延を足すことであり、APR ツールが一見無意味なバッファ挿入に最適化の一周を費やす理由がこれだ。クロックツリー合成がスキューの最小化に力を注ぐ理由でもある。スキューは両方の制約の余裕を削るが、その削り方は非対称で、同じ経路のセットアップを助けつつホールドを害する。
The lecture's final example chains four 8-bit registers, using arrays of instances twice.本講最後一個範例把四個 8 位元暫存器串起來,而且用了兩次實例陣列。講義の最後の例は 8 ビットのレジスタ四つを連ね、インスタンス配列を二度使う。
module array_of_flops (q, data_in, clk, set, rst); input [7:0] data_in; input clk, set, rst; output [7:0] q; Flip_flop M [7:0] (q, data_in, clk, set, rst); endmodule
module array_pipeline (q, data_in, clk, set, rst);
input [7:0] data_in;
input clk, set, rst;
output [7:0] q;
wire [23:0] pipe;
array_of_flops M [3:0] ({q, pipe},
{pipe, data_in},
clk, set, rst);
endmoduleWork the widths. array_of_flops has an 8-bit q, an 8-bit data_in, and three scalar controls. Four instances are requested, so:把位寬算一遍。array_of_flops 有一個 8 位元的 q、一個 8 位元的 data_in,以及三個純量控制。這裡要求四個實例,所以:幅を計算しよう。array_of_flops は 8 ビットの q、8 ビットの data_in、そして三つのスカラ制御を持つ。ここでは四インスタンスを要求しているので:
{q, pipe} is 8 + 24 = 32 bits, split into four 8-bit slices. M[0] gets the least significant slice, pipe[7:0]; M[1] gets pipe[15:8]; M[2] gets pipe[23:16]; M[3] gets the most significant slice, q[7:0].{q, pipe} 是 8 + 24 = 32 位元,切成四個 8 位元切片。M[0] 拿到最低位的切片 pipe[7:0];M[1] 拿到 pipe[15:8];M[2] 拿到 pipe[23:16];M[3] 拿到最高位的切片 q[7:0]。{q, pipe} は 8 + 24 = 32 ビットで、8 ビットの切片四つに分けられる。M[0] は最下位の切片 pipe[7:0]、M[1] は pipe[15:8]、M[2] は pipe[23:16]、M[3] は最上位の切片 q[7:0] を受け取る。{pipe, data_in} is also 32 bits. M[0] gets data_in; M[1] gets pipe[7:0]; M[2] gets pipe[15:8]; M[3] gets pipe[23:16].{pipe, data_in} 同樣是 32 位元。M[0] 拿到 data_in;M[1] 拿到 pipe[7:0];M[2] 拿到 pipe[15:8];M[3] 拿到 pipe[23:16]。{pipe, data_in} もまた 32 ビットである。M[0] は data_in、M[1] は pipe[7:0]、M[2] は pipe[15:8]、M[3] は pipe[23:16] を受け取る。clk, set and rst are 1 bit and the port is 1 bit, so every instance gets the same signal. That is the "equal width means broadcast" half of the distribution rule, and it is what makes a shared clock possible.clk、set 與 rst 是 1 位元,而埠也是 1 位元,所以每一個實例都拿到同一條訊號。那就是分配規則中「等寬即廣播」的那一半,也正是共用時脈之所以可能的原因。clk、set、rst は 1 ビットで、ポートも 1 ビットなので、すべてのインスタンスが同じ信号を受け取る。これが分配規則の「幅が等しければブロードキャスト」の半分であり、クロックの共有を可能にしているものである。The same one-bit shift between the two concatenations that chained the carries in §7.6 chains the pipeline stages here. Stage i's output is stage i+1's input, by construction.在 §7.6 把進位串起來的那同一個「兩個串接之間錯開一位元」的手法,在這裡把管線各級串了起來。第 i 級的輸出就是第 i+1 級的輸入,由構造保證。§7.6 でキャリーを連鎖させたのと同じ「二つの連結を 1 ビットずらす」手法が、ここではパイプラインの段を連鎖させている。段 i の出力が段 i+1 の入力になることが、構成そのものから保証される。
These two numbers are independent and students routinely conflate them.這兩個數字彼此獨立,而學生經常把它們混為一談。この二つの数は互いに独立であり、学生は決まって混同する。
Pipelining is the trade that buys throughput with latency and area. Adding a stage lets you shorten the critical path, which lets you raise $1/T$, which raises throughput — while the latency in cycles goes up by one and the latency in seconds may go either way. It is also the reason the slide's pipeline, which contains no logic between the stages at all, is not useful as a circuit: it costs 32 flip-flops to delay data by four cycles and computes nothing. It is a teaching structure. Real pipelines put combinational logic between the registers, and Lecture 8 does exactly that.管線化就是用延遲與面積去買吞吐率的那筆交易。加一級讓你能縮短關鍵路徑,於是可以提高 $1/T$,於是吞吐率上升 — 同時以週期計的延遲多了一個,而以秒計的延遲則可能往兩邊走。這也是為什麼投影片那條各級之間完全沒有邏輯的管線,作為電路並不實用:它花了 32 顆正反器只為了把資料延後四個週期,什麼也沒算。它是一個教學用的結構。真實的管線會在暫存器之間放組合邏輯,而第 8 講做的正是這件事。パイプライン化とは、レイテンシと面積でスループットを買う取引である。段を足せばクリティカルパスを短くでき、$1/T$ を上げられ、スループットが上がる——一方でサイクルで測ったレイテンシは一つ増え、秒で測ったレイテンシはどちらにも振れうる。スライドのパイプラインが段間にまったく論理を持たず、回路として役に立たない理由でもある。データを 4 サイクル遅らせるためにフリップフロップを 32 個使い、何も計算しない。これは教材としての構造である。現実のパイプラインはレジスタの間に組合せ論理を置き、第 8 講がまさにそれを行う。
Note that $t_{cq}+t_{su}$ does not divide by $N$. This register overhead is why throughput saturates: splitting a 2 ns combinational block into 2 stages nearly doubles the rate, into 4 stages gains much less, and into 32 stages gains essentially nothing while costing 32× the flip-flops and 32 cycles of latency.注意 $t_{cq}+t_{su}$ 不會被 $N$ 除。這項暫存器開銷正是吞吐率會飽和的原因:把一塊 2 ns 的組合邏輯切成 2 級,速率幾乎翻倍;切成 4 級,獲益小得多;切成 32 級,幾乎一點好處都沒有,卻要付出 32 倍的正反器與 32 個週期的延遲。$t_{cq}+t_{su}$ が $N$ で割られない点に注意。このレジスタ・オーバヘッドこそスループットが頭打ちになる理由である。2 ns の組合せブロックを 2 段に割れば速度はほぼ倍になり、4 段にすれば得は大きく減り、32 段にすればほとんど何も得られないまま、フリップフロップは 32 倍、レイテンシは 32 サイクルになる。
Problem: set = rst = 1 (both inactive, since both are active low in §8.3). All registers start at 8'h00. data_in is 8'h11, 8'h22, 8'h33, 8'h44, 8'h55 at clock edges 1–5. (a) Give pipe and q after edge 4 and after edge 5. (b) What happens if rst goes to 0 just before edge 6 and back to 1 just after? (c) What happens if rst goes to 0 and back to 1 entirely between edges 5 and 6, never overlapping an edge? (d) Would the answer to (c) change if the flip-flop used an asynchronous reset?題目:set = rst = 1(兩者皆未作用,因為 §8.3 裡兩者都是低準位有效)。所有暫存器從 8'h00 開始。data_in 在時脈邊緣 1–5 依序是 8'h11, 8'h22, 8'h33, 8'h44, 8'h55。(a) 寫出邊緣 4 之後與邊緣 5 之後的 pipe 與 q。(b) 如果 rst 在邊緣 6 之前剛好降為 0、之後又回到 1,會發生什麼?(c) 如果 rst 完全在邊緣 5 與 6 之間降為 0 再回到 1,從未與任何邊緣重疊,會發生什麼?(d) 如果這顆正反器用的是非同步重置,(c) 的答案會改變嗎?問題:set = rst = 1(§8.3 ではどちらも負論理なので、両方とも非アクティブ)。全レジスタは 8'h00 から始まる。data_in はクロックエッジ 1–5 で順に 8'h11, 8'h22, 8'h33, 8'h44, 8'h55 である。(a) エッジ 4 の後とエッジ 5 の後の pipe と q を示せ。(b) rst がエッジ 6 の直前に 0 になり直後に 1 へ戻ったらどうなるか。(c) rst がエッジ 5 と 6 の間だけで 0 になり 1 へ戻り、どのエッジとも重ならなかったらどうなるか。(d) このフリップフロップが非同期リセットなら (c) の答えは変わるか。
(a) Each edge shifts everything one stage right.(a) 每個邊緣都把所有東西往右移一級。(a) 各エッジですべてが一段右へシフトする。
| After edge邊緣之後エッジ後 | pipe[7:0] (M[0]) | pipe[15:8] (M[1]) | pipe[23:16] (M[2]) | q (M[3]) |
|---|---|---|---|---|
| 1 | 11 | 00 | 00 | 00 |
| 2 | 22 | 11 | 00 | 00 |
| 3 | 33 | 22 | 11 | 00 |
| 4 | 44 | 33 | 22 | 11 |
| 5 | 55 | 44 | 33 | 22 |
Note that q first shows real data after edge 4 — the latency.注意 q 要到邊緣 4 之後才第一次出現真實資料 — 那就是延遲。q に実データが初めて現れるのはエッジ 4 の後である点に注意。これがレイテンシだ。
(b) At edge 6 every stage sees rst == 0 and loads 0. The whole pipeline becomes 00 in all four stages simultaneously. The datum 55 that was in flight is lost; it is not flushed out, it is overwritten.(b) 在邊緣 6,每一級都看到 rst == 0 而載入 0。整條管線的四級同時變成 00。原本還在途中的那筆資料 55 就不見了;它不是被沖出去,而是被覆蓋掉。(b) エッジ 6 で各段が rst == 0 を見て 0 をロードする。四段すべてが同時に 00 になる。飛行中だったデータ 55 は失われる。押し出されたのではなく、上書きされたのである。
(c) Nothing happens. The reset is synchronous: it is sampled only at a rising edge, and no rising edge occurred while it was low. Edge 6 therefore performs a normal shift. This is the defining property of synchronous reset, and it is both its advantage (reset glitches are filtered out, and the reset net has no special timing requirement other than ordinary setup/hold) and its limitation (a reset pulse shorter than a clock period is simply ignored, and reset does nothing at all if the clock is stopped).(c) 什麼也不會發生。這個重置是同步的:它只在上升邊緣被取樣,而它為低的期間並沒有發生任何上升邊緣。所以邊緣 6 執行的是一次正常的移位。這正是同步重置的定義性質,而它既是優點(重置上的毛刺會被濾掉,而且重置 net 除了一般的 setup/hold 之外沒有特殊的時序要求),也是限制(比一個時脈週期還短的重置脈衝就這樣被忽略,而且時脈停了的話重置完全無效)。(c) 何も起きない。このリセットは同期式であり、立ち上がりエッジでのみサンプルされる。低だった間に立ち上がりエッジは一度もなかった。よってエッジ 6 は通常どおりシフトする。これこそ同期リセットの定義的性質であり、利点(リセットのグリッチは濾過され、リセット・ネットは通常のセットアップ/ホールド以外に特別なタイミング要求を持たない)でもあり、限界(クロック周期より短いリセットパルスは単に無視され、クロックが止まっていればリセットはまったく効かない)でもある。
(d) Yes, completely. An asynchronous reset acts the instant it is asserted, so a pulse between edges would clear all four stages immediately and they would stay cleared. That is the advantage (works with no clock, e.g. at power-on) and the hazard (a glitch on the reset net resets your chip, and the deassertion of an asynchronous reset must still meet a recovery/removal time relative to the clock edge or the flop can go metastable — which is why real designs assert reset asynchronously and deassert it synchronously through a two-flop synchroniser).(d) 會,而且完全改變。非同步重置在被拉起的瞬間就作用,所以邊緣之間的一個脈衝會立刻清掉四級,而且它們會保持被清除的狀態。那是它的優點(沒有時脈也能運作,例如上電時),也是它的危險(重置 net 上一個毛刺就重置你的晶片;而且非同步重置的解除仍然必須相對於時脈邊緣滿足 recovery/removal 時間,否則正反器可能進入亞穩態 — 這就是為什麼真實的設計會非同步地拉起重置,再透過一個雙級正反器同步器把它同步地解除)。(d) 変わる。まったく変わる。非同期リセットはアサートされた瞬間に効くので、エッジ間のパルスは四段すべてを即座にクリアし、そのまま保たれる。それが利点(クロックなしでも働く。たとえば電源投入時)であり、危険(リセット・ネット上のグリッチがチップをリセットする。しかも非同期リセットの解除はクロックエッジに対してリカバリ/リムーバル時間を満たさねばならず、さもなければフリップフロップが準安定になりうる——だから実際の設計はリセットを非同期にアサートし、二段のシンクロナイザを通して同期的に解除する)でもある。
Problem: A block has $t_{cq} = 80$ ps, $t_{su} = 45$ ps, $t_h = 25$ ps, and a combinational cloud of 1.9 ns between two registers. Clock skew is 60 ps, worst case, and may be of either sign. (a) What is the maximum clock frequency? (b) A designer removes the combinational cloud entirely so one flop feeds the next directly. Does the circuit now work at any frequency? (c) How much logic delay must be guaranteed on the direct path for it to be safe? (d) The block is pipelined by splitting the cloud exactly in half. What is the new maximum frequency, and by what factor did throughput improve?題目:某區塊的 $t_{cq} = 80$ ps、$t_{su} = 45$ ps、$t_h = 25$ ps,兩個暫存器之間有一團 1.9 ns 的組合邏輯。時脈偏斜最差為 60 ps,正負號皆有可能。(a) 最高時脈頻率是多少?(b) 某設計者把那團組合邏輯整個拿掉,讓一顆正反器直接餵下一顆。這個電路現在在任何頻率下都能動嗎?(c) 那條直連路徑上必須保證多少邏輯延遲才安全?(d) 把那團邏輯剛好切成兩半來做管線化。新的最高頻率是多少,吞吐率改善了幾倍?問題:あるブロックは $t_{cq} = 80$ ps、$t_{su} = 45$ ps、$t_h = 25$ ps で、二つのレジスタの間に 1.9 ns の組合せ論理の塊がある。クロックスキューは最悪 60 ps で、符号はどちらにもなりうる。(a) 最高クロック周波数は。(b) 設計者が組合せ論理を丸ごと取り除き、フリップフロップが次のフリップフロップを直接駆動するようにした。この回路はどの周波数でも動くか。(c) その直結経路に最低どれだけの論理遅延を保証すれば安全か。(d) 論理の塊をちょうど半分に割ってパイプライン化する。新しい最高周波数は、またスループットは何倍になったか。
(a) Worst case for setup is skew working against you, so take $t_{\text{skew}} = -60$ ps in $T \ge t_{cq}+t_{\text{logic}}+t_{su}-t_{\text{skew}}$:(a) 對 setup 而言最差的情況是偏斜與你作對,所以在 $T \ge t_{cq}+t_{\text{logic}}+t_{su}-t_{\text{skew}}$ 中取 $t_{\text{skew}} = -60$ ps:(a) セットアップの最悪はスキューが不利に働く場合なので、$T \ge t_{cq}+t_{\text{logic}}+t_{su}-t_{\text{skew}}$ で $t_{\text{skew}} = -60$ ps とする。
$T \ge 80 + 1900 + 45 + 60 = 2085$ ps → $f_{\max} = 1/2.085\text{ ns} \approx \mathbf{480\ MHz}$.
(b) No. Check hold: $t_{cq} + t_{\text{logic,min}} \ge t_h + t_{\text{skew}}$ becomes $80 + 0 \ge 25 + 60 = 85$. That is $80 \ge 85$ — false by 5 ps. The circuit has a hold violation and will fail at every frequency, including stepping the clock by hand. This is the single most important thing to take from the hold equation: no clock period appears in it, so "slow it down and see" does not help.(b) 不行。檢查 hold:$t_{cq} + t_{\text{logic,min}} \ge t_h + t_{\text{skew}}$ 變成 $80 + 0 \ge 25 + 60 = 85$。也就是 $80 \ge 85$ — 差 5 ps 不成立。這個電路有 hold 違規,在任何頻率下都會失敗,連用手一步一步敲時脈也一樣。這正是 hold 方程式最該帶走的一點:裡面沒有時脈週期,所以「放慢看看」毫無幫助。(b) 動かない。ホールドを調べる。$t_{cq} + t_{\text{logic,min}} \ge t_h + t_{\text{skew}}$ は $80 + 0 \ge 25 + 60 = 85$ となる。すなわち $80 \ge 85$——5 ps 足りず偽である。この回路はホールド違反を抱え、手でクロックを一つずつ送っても含め、あらゆる周波数で失敗する。ホールドの式から持ち帰るべき最重要の一点がこれだ。式にクロック周期は現れないので、「遅くして様子を見る」は効かない。
(c) We need $t_{\text{logic,min}} \ge t_h + t_{\text{skew}} - t_{cq} = 25+60-80 = 5$ ps. So just 5 ps of guaranteed minimum delay — one small buffer — fixes it. APR tools insert exactly these, which is why a post-route netlist is full of buffers that appear to do nothing.(c) 我們需要 $t_{\text{logic,min}} \ge t_h + t_{\text{skew}} - t_{cq} = 25+60-80 = 5$ ps。所以只要保證 5 ps 的最小延遲 — 一顆小緩衝器 — 就修好了。APR 工具插入的正是這種東西,這也是為什麼繞線後的網表裡滿是看起來什麼事都沒做的緩衝器。(c) 必要なのは $t_{\text{logic,min}} \ge t_h + t_{\text{skew}} - t_{cq} = 25+60-80 = 5$ ps である。保証すべき最小遅延はわずか 5 ps——小さなバッファ一つ——でこれは直る。APR ツールが挿入するのはまさにこれであり、配線後のネットリストが何もしていないように見えるバッファだらけになる理由でもある。
(d) Each half is 950 ps. $T \ge 80 + 950 + 45 + 60 = 1135$ ps → $f_{\max} \approx \mathbf{881\ MHz}$.(d) 每一半是 950 ps。$T \ge 80 + 950 + 45 + 60 = 1135$ ps → $f_{\max} \approx \mathbf{881\ MHz}$。(d) 各半分は 950 ps。$T \ge 80 + 950 + 45 + 60 = 1135$ ps → $f_{\max} \approx \mathbf{881\ MHz}$。
Throughput improved by $2085/1135 = \mathbf{1.84\times}$, not $2\times$. The missing 16% is the register overhead $t_{cq}+t_{su}+|t_{\text{skew}}| = 185$ ps, which is paid once per stage and does not shrink when you cut the logic. Latency went from 2085 ps (one cycle) to $2\times 1135 = 2270$ ps (two cycles) — slightly worse in absolute time, which is the price of the throughput.吞吐率改善了 $2085/1135 = \mathbf{1.84\times}$,不是 $2\times$。缺掉的 16% 就是暫存器開銷 $t_{cq}+t_{su}+|t_{\text{skew}}| = 185$ ps,它每一級都要付一次,而且不會因為你把邏輯切小而縮水。延遲則從 2085 ps(一個週期)變成 $2\times 1135 = 2270$ ps(兩個週期) — 以絕對時間看還略微變差了,而那正是吞吐率的代價。スループットは $2085/1135 = \mathbf{1.84\times}$ 改善し、$2\times$ にはならない。足りない 16% はレジスタ・オーバヘッド $t_{cq}+t_{su}+|t_{\text{skew}}| = 185$ ps であり、段ごとに一度支払われ、論理を細かく割っても縮まない。レイテンシは 2085 ps(1 サイクル)から $2\times 1135 = 2270$ ps(2 サイクル)になった——絶対時間ではわずかに悪化しており、それがスループットの代価である。
These are the rules the lexer applies before anything else happens, and every one of them has produced a confusing error message for somebody.這些是詞法分析器在任何事情發生之前就套用的規則,而其中每一條都曾經害某個人收到一則莫名其妙的錯誤訊息。これらは、他の何が起こるより先に字句解析器が適用する規則であり、どれもが誰かに意味不明なエラーメッセージを浴びせてきた。
Data, data and DATA are three different signals. Combined with implicit net declaration (§4.3), a case typo creates a new floating wire rather than an error.Verilog 區分大小寫。Data、data 與 DATA 是三條不同的訊號。再加上隱式 net 宣告(§4.3),一個大小寫打錯只會造出一條新的浮接 wire,而不是一則錯誤。Verilog は大文字と小文字を区別する。Data、data、DATA は三本の別の信号である。暗黙のネット宣言(§4.3)と組み合わさると、大小の打ち間違いはエラーではなく新しい浮いた wire を生む。module is a keyword; Module is an identifier. This is why Reg is a legal variable name and reg is not.所有關鍵字都是小寫。module 是關鍵字;Module 是識別字。這就是為什麼 Reg 是合法的變數名稱,而 reg 不是。キーワードはすべて小文字である。module はキーワード、Module は識別子である。Reg が合法な変数名で reg がそうでない理由がこれだ。// to end of line, and /* … */ for a block. Block comments do not nest, so commenting out a region that already contains a block comment ends the comment early and leaves the rest of the region as live code. Use `ifdef or line comments for that.註解:// 到行尾,/* … */ 則是區塊註解。區塊註解不能巢狀,所以把一段本來就含有區塊註解的區域註解掉,註解會提早結束,剩下的部分又變成活的程式碼。那種情況請改用 `ifdef 或行註解。コメント:// は行末まで、/* … */ はブロック。ブロックコメントは入れ子にできないので、既にブロックコメントを含む領域をコメントアウトすると、コメントが早く終わり、残りが生きたコードとして残る。その用途には `ifdef か行コメントを使え。begin/end, endmodule, endcase, endfunction and the other end… keywords, which never take one. Writing end; is a syntax error whose message usually points at the next line.每個模組項目都以分號結尾,但 begin/end、endmodule、endcase、endfunction 以及其他 end… 關鍵字例外,它們永遠不加分號。寫成 end; 是語法錯誤,而錯誤訊息通常指向下一行。モジュール項目はすべてセミコロンで終わる。ただし begin/end、endmodule、endcase、endfunction ほかの end… キーワードは例外で、決してセミコロンを取らない。end; と書けば構文エラーになり、そのメッセージはたいてい次の行を指す。An identifier may contain upper- and lower-case letters, the decimal digits, the underscore _ and the dollar sign $. It may be up to 1024 characters long. The first character must not be a digit and must not be $ — the first because the lexer would start reading a number, the second because the $-prefixed namespace is reserved for system tasks and functions.識別字可以包含大小寫字母、十進位數字、底線 _ 與錢字號 $,長度最多 1024 個字元。第一個字元不可以是數字,也不可以是 $ — 前者是因為詞法分析器會開始讀成一個數字,後者是因為 $ 開頭的命名空間保留給系統任務與函式。識別子には大文字・小文字の英字、十進数字、アンダースコア _、ドル記号 $ を使え、長さは最大 1024 文字である。先頭の文字は数字であってはならず、$ でもあってはならない——前者は字句解析器が数値として読み始めてしまうため、後者は $ で始まる名前空間がシステムタスクと関数のために予約されているためである。
There is also an escaped form: a backslash, then any sequence of printable non-whitespace characters, then whitespace. \bus[3] and \my-signal are legal escaped identifiers. You will never write one by hand, but synthesis tools generate them when flattening hierarchy (a signal top.cpu.alu.carry becomes \cpu/alu/carry ), so you must be able to read them in a netlist. The trailing space is part of the syntax, not a typo.另外還有一種跳脫形式:一個反斜線,接著任意一串可列印的非空白字元,最後以空白結束。\bus[3] 與 \my-signal 都是合法的跳脫識別字。你永遠不會親手寫一個,但合成工具在把階層攤平時會產生它們(訊號 top.cpu.alu.carry 會變成 \cpu/alu/carry ),所以你必須看得懂網表裡的它們。結尾那個空白是語法的一部分,不是打錯字。エスケープ形式もある。バックスラッシュに続けて印字可能な非空白文字の任意列を書き、空白で終える。\bus[3] や \my-signal は合法なエスケープ識別子である。手で書くことは生涯ないだろうが、合成ツールが階層を平坦化する際に生成する(信号 top.cpu.alu.carry が \cpu/alu/carry になる)ので、ネットリスト中のそれを読めなければならない。末尾の空白は構文の一部であって誤記ではない。
Two special namespaces, distinguished by their prefix character, and students mix them up constantly because both look "special".兩個特殊的命名空間,靠前綴字元區分,而學生老是把它們搞混,因為兩者看起來都很「特別」。接頭文字で区別される二つの特別な名前空間。どちらも「特別」に見えるせいで、学生は絶えず混同する。
| Compiler directives編譯指示コンパイラ指示子 | System tasks and functions系統任務與系統函式システムタスクとシステム関数 | |
|---|---|---|
| Prefix前綴接頭文字 | Grave accent / backtick ` (the key left of 1, not an apostrophe)重音符/反引號 `(鍵盤上 1 左邊那顆鍵,不是單引號)バッククォート `(キーボードの 1 の左のキー。アポストロフィではない) | Dollar sign $錢字號 $ドル記号 $ |
| When they act何時起作用いつ働くか | At compile time, textually, before elaboration編譯期,以文字替換方式,在展開之前コンパイル時、テキストとして、エラボレーションより前 | At run time, during simulation執行期,模擬進行中実行時、シミュレーション中 |
| Examples例子例 | `define state0 3'b001, `include "defs.v", `timescale 1ns/1ps, `ifdef/`else/`endif, `default_nettype none | $monitor, $display, $time, $finish, $readmemh, $random, $dumpvars |
| Scope作用範圍スコープ | Global from the point of definition to the end of the compilation, crossing file boundaries從定義處起,全域有效直到該次編譯結束,會跨越檔案邊界定義の位置から、そのコンパイルの終わりまでグローバル。ファイル境界も越える | Normal statement scope一般敘述的作用範圍通常の文のスコープ |
| Synthesisable可合成合成可能か | Yes (they are preprocessing)可以(它們屬於前置處理)可(前処理だから) | No testbench only不可 僅限測試平台不可 テストベンチ専用 |
Two traps. First, a macro is referenced with the backtick too: you define `define WIDTH 8 and use it as reg [`WIDTH-1:0] bus;. Omitting the backtick at the use site gives "undefined identifier WIDTH". Second, macro definitions are not scoped to a module or even a file; they leak into every file compiled after the one that defined them, in command-line order. A `define of a common name like WIDTH in one block silently redefines it for the next, which is why real projects wrap every macro in a project prefix and guard header files with `ifndef.兩個陷阱。第一,引用巨集時也要加反引號:你定義 `define WIDTH 8,使用時要寫 reg [`WIDTH-1:0] bus;。在使用處漏掉反引號,就會得到「undefined identifier WIDTH」。第二,巨集定義不受模組甚至檔案的範圍限制;它們會依命令列順序,洩漏到定義它的那個檔案之後編譯的每一個檔案。在某個區塊裡 `define 一個像 WIDTH 這樣的常見名稱,就安靜地替下一個區塊重新定義了它 — 這正是為什麼真實專案會把每個巨集都冠上專案前綴,並用 `ifndef 替標頭檔加上防護。罠が二つある。第一に、マクロの参照にもバッククォートが要る。`define WIDTH 8 と定義したら、使うときは reg [`WIDTH-1:0] bus; と書く。使用側でバッククォートを落とせば「undefined identifier WIDTH」になる。第二に、マクロ定義はモジュールにもファイルにもスコープされない。コマンドライン順で、定義したファイルより後にコンパイルされるすべてのファイルへ漏れ出す。あるブロックで WIDTH のようなありふれた名前を `define すれば、次のブロックでそれを黙って再定義することになる。実際のプロジェクトがすべてのマクロにプロジェクト接頭辞を付け、ヘッダファイルを `ifndef でガードする理由がこれである。
The `timescale directive deserves its own mention because it is the one that produces mystifying simulation results: `timescale 1ns/1ps means "a bare #1 is one nanosecond, and round everything to the nearest picosecond". If one file in your design has a timescale and another does not, the behaviour is tool-dependent and your delays may be off by three orders of magnitude. Put the same `timescale at the top of every file, or none.`timescale 指示值得單獨一提,因為它正是那個會製造出莫名其妙模擬結果的傢伙:`timescale 1ns/1ps 的意思是「一個裸的 #1 是一奈秒,而且所有東西都四捨五入到最接近的皮秒」。如果你設計裡有一個檔案有 timescale、另一個沒有,行為就因工具而異,而你的延遲可能差了三個數量級。請在每個檔案開頭都放同一行 `timescale,或者一個都不放。`timescale 指示子は単独で触れる価値がある。不可解なシミュレーション結果を生むのはこれだからだ。`timescale 1ns/1ps は「素の #1 は 1 ナノ秒、すべてを最も近いピコ秒へ丸める」を意味する。設計中の一つのファイルに timescale があり別のファイルにないと、振る舞いはツール依存になり、遅延が三桁ずれることもある。すべてのファイルの先頭に同じ `timescale を置くか、どこにも置かないかにせよ。
The slides' closing advice — know the basic concepts and syntax, find a good reference (a person or some code files), form a good coding habit, and draw the architecture before coding the connections — is worth more than it looks. Here is the operational version.投影片的結語建議 — 掌握基本概念與語法、找一個好的參考(一個人或一些程式碼檔案)、養成好的寫碼習慣、在寫連接之前先畫出架構 — 其價值比它看起來的高。以下是它的可操作版本。スライドの締めくくりの助言——基本概念と構文を押さえる、良い参照(人でもコードでも)を見つける、良いコーディング習慣をつくる、接続を書く前にアーキテクチャを描く——は、見かけ以上の価値がある。以下はその実践版である。
always @(posedge clk) per register group, one assign or always @(*) per cloud.每一組暫存器配一個 always @(posedge clk),每一朵邏輯雲配一條 assign 或一個 always @(*)。レジスタ群ごとに always @(posedge clk) を一つ、雲ごとに assign または always @(*) を一つ。always block.絕對不要在同一個 always 區塊裡混用時脈式與組合式的指定。クロック同期の代入と組合せの代入を一つの always ブロックに混在させてはならない。<= in clocked blocks, = in combinational ones, never the reverse.時脈區塊裡用 <=,組合區塊裡用 =,永遠不要反過來。クロック同期ブロックでは <=、組合せブロックでは =。逆は決してしない。_n for active low, _r for registered, _q/_d for flop output/input, _i/_o for port direction.字尾慣例帶著資訊:_n 表示低準位有效、_r 表示已過暫存器、_q/_d 表示正反器的輸出/輸入、_i/_o 表示埠的方向。接尾辞の慣習は情報を運ぶ。_n は負論理、_r はレジスタ済み、_q/_d はフリップフロップの出力/入力、_i/_o はポートの方向を表す。u1: the name appears in every timing report you will ever read.用功能來替實例命名,不要叫 u1:那個名字會出現在你此生讀到的每一份時序報告裡。インスタンスには u1 ではなく機能に即した名前を付けよ。その名前は、君が今後読むすべてのタイミング報告に現れる。`default_nettype none at the top of every design file.每個設計檔的開頭都放 `default_nettype none。すべての設計ファイルの先頭に `default_nettype none。And one more, which the slides imply with "Verilog is a platform": keep the design and the testbench mentally separate even though they share a language. Design code answers "what hardware is this?" and is restricted to the synthesisable subset. Testbench code answers "how do I convince myself this is right?" and may use anything. A student who writes #10 in a design module, or who writes a testbench in the stiff style of RTL, has not yet made that distinction and will be slowed down by it all semester.還有一條,投影片以「Verilog 是一個平台」暗示了它:即使設計與測試平台共用一種語言,也要在心裡把它們分開。設計碼回答的是「這是什麼硬體?」,而且受限於可合成子集。測試平台碼回答的是「我要怎麼說服自己這是對的?」,而且什麼都可以用。在設計模組裡寫 #10 的學生,或是用 RTL 那種僵硬風格去寫測試平台的學生,都還沒做出這個區分,而這會拖累他們一整個學期。もう一つ、スライドが「Verilog はプラットフォームである」という言葉で示唆しているものがある。設計とテストベンチが言語を共有していても、頭の中では分けておくことだ。設計コードは「これはどんなハードウェアか」に答え、合成可能サブセットに縛られる。テストベンチのコードは「これが正しいと自分をどう納得させるか」に答え、何を使ってもよい。設計モジュールに #10 と書く学生や、RTL の硬い文体でテストベンチを書く学生は、まだこの区別ができておらず、一学期を通してそれに足を引っ張られる。
wire, wand, wor, tri, supply0/1): no storage, driven by assign / gate / instance output.Net(wire、wand、wor、tri、supply0/1):不儲存,由 assign/閘/實例輸出驅動。ネット(wire、wand、wor、tri、supply0/1):記憶せず、assign・ゲート・インスタンス出力が駆動する。reg, integer, time, real): written procedurally only.變數(reg、integer、time、real):只能以程序敘述寫入。変数(reg、integer、time、real):手続き的にのみ書き込まれる。reg ≠ flip-flop. The edge in the sensitivity list makes the flip-flop.reg ≠ 正反器。是敏感清單裡的邊緣造就了正反器。reg ≠ フリップフロップ。フリップフロップを作るのは感度リストのエッジである。wire; an output may be reg inside, never outside.埠預設是 wire;output 在模組內部可以是 reg,在外部絕對不行。ポートの既定は wire。output はモジュール内部では reg でもよいが、外部では決して reg にならない。0 1 x z; x = unknown, z = floating.0 1 x z;x =未知,z =浮接。0 1 x z。x は不定、z はフローティング。z; they read an incoming z as x.閘永遠不輸出 z;它們把進來的 z 讀成 x。ゲートは z を出力しない。入ってきた z は x として読む。== returns x on unknowns; === does not and is not synthesisable.== 遇到未知值會回傳 x;=== 不會,但它不可合成。== は不定値に対して x を返す。=== は返さないが合成できない。<size>'<base><value>; size is in bits.<size>'<base><value>;size 的單位是位元。<size>'<base><value>。size の単位はビット。x or z, matching the leftmost digit.太窄 → 往左延伸,用 0、x 或 z,依最左邊那個數字而定。狭すぎる → 左へ拡張。最左の桁に合わせて 0、x、z のいずれかで埋める。3'b5 is an error).每個數字都必須在該基底下合法(3'b5 是錯誤)。各桁はその基数で合法でなければならない(3'b5 は誤り)。assign, static binding, net target.RTL:assign、靜態繫結、目標是 net。RTL:assign、静的束縛、代入先はネット。always, procedural target, sensitivity list.行為描述:always、程序式目標、敏感清單。動作記述:always、手続き的な代入先、感度リスト。Problem: (a) Which of these are legal identifiers? data_in, 2bit, _tmp, $count, reg, Reg, a$b, clk-1, q_, MODULE. (b) What does `define state0 3'b001 do, and how do you use state0 afterwards? (c) Why can't $monitor be synthesised? (d) Explain the difference between ' and ` in Verilog and give one use of each.題目:(a) 下列哪些是合法的識別字?data_in、2bit、_tmp、$count、reg、Reg、a$b、clk-1、q_、MODULE。(b) `define state0 3'b001 做了什麼,之後你要怎麼使用 state0?(c) 為什麼 $monitor 不能被合成?(d) 說明 Verilog 裡 ' 與 ` 的差別,並各舉一個用途。問題:(a) 次のうち合法な識別子はどれか。data_in、2bit、_tmp、$count、reg、Reg、a$b、clk-1、q_、MODULE。(b) `define state0 3'b001 は何をし、その後 state0 をどう使うか。(c) $monitor が合成できないのはなぜか。(d) Verilog における ' と ` の違いを説明し、それぞれの用例を一つずつ挙げよ。
(a) Legal: data_in; _tmp (underscore may start an identifier); Reg (case sensitivity means it is not the keyword); a$b ($ is allowed, just not first); q_; MODULE (same reason as Reg).(a) 合法:data_in;_tmp(底線可以作為識別字的第一個字元);Reg(區分大小寫,所以它不是關鍵字);a$b($ 可以用,只是不能排第一);q_;MODULE(理由同 Reg)。(a) 合法:data_in、_tmp(アンダースコアは先頭に置ける)、Reg(大小を区別するのでキーワードではない)、a$b($ は使える。先頭でなければよい)、q_、MODULE(理由は Reg と同じ)。
Illegal: 2bit (first character is a digit); $count (first character is $, reserved for system tasks); reg (a keyword); clk-1 (- is not an identifier character — the lexer reads this as clk minus 1, so you get an expression rather than an error, which is worse).不合法:2bit(第一個字元是數字);$count(第一個字元是 $,保留給系統任務);reg(是關鍵字);clk-1(- 不是識別字字元 — 詞法分析器會把它讀成 clk 減 1,所以你得到的是一個運算式而不是錯誤,那更糟)。不正:2bit(先頭が数字)、$count(先頭が $ で、システムタスク用に予約されている)、reg(キーワード)、clk-1(- は識別子の文字ではない——字句解析器はこれを clk 引く 1 と読むので、エラーではなく式が得られる。そのほうが悪い)。
Legal-but-terrible: Reg and MODULE. Never name a signal after a keyword in a different case; a reader's eye sees the keyword.合法但很糟:Reg 與 MODULE。絕對不要用大小寫不同的關鍵字來命名訊號;讀者的眼睛看到的就是那個關鍵字。合法だがひどいもの:Reg と MODULE。大小だけ変えたキーワードを信号名にしてはならない。読者の目にはキーワードにしか見えない。
(b) It is a compiler directive defining a text macro. Everywhere the preprocessor subsequently sees `state0 (with the backtick) it substitutes the characters 3'b001. The definition takes effect from that point to the end of the compilation, across files. Note the asymmetry that catches everyone: no backtick at the definition's name, backtick at every use.(b) 它是一個編譯指示,定義了一個文字巨集。此後前置處理器每看到 `state0(帶反引號),就把它替換成字元 3'b001。這個定義從該點起生效,直到整次編譯結束,而且跨越檔案。注意那個絆倒每個人的不對稱:定義處的名稱不加反引號,每個使用處都要加。(b) これはコンパイラ指示子であり、テキストマクロを定義する。以後、前処理器が `state0(バッククォート付き)を見るたびに、文字列 3'b001 に置き換える。定義はその地点からコンパイルの終わりまで、ファイルを越えて有効である。誰もがつまずく非対称に注意せよ。定義側の名前にはバッククォートを付けず、使用側には毎回付ける。
(c) $monitor prints a line whenever one of the signals it is watching changes. There is no standard cell that prints text — it is an instruction to the simulator's I/O system, not a description of hardware. The same applies to every $ task. The one quasi-exception is $readmemh, which some FPGA flows interpret as memory initialisation data, but that is a tool feature, not language semantics.(c) $monitor 會在它所監看的某條訊號改變時印出一行。沒有任何標準元件會印字 — 它是給模擬器 I/O 系統的指令,不是對硬體的描述。每一個 $ 任務都一樣。唯一的準例外是 $readmemh,某些 FPGA 流程會把它解讀成記憶體初始化資料,但那是工具的功能,不是語言的語意。(c) $monitor は監視対象の信号が変わるたびに一行印字する。文字を印字するスタンダードセルなど存在しない。これはシミュレータの I/O システムへの命令であって、ハードウェアの記述ではない。すべての $ タスクについて同じことが言える。準例外は $readmemh で、一部の FPGA フローはこれをメモリ初期化データとして解釈するが、それはツールの機能であって言語の意味論ではない。
(d) ' is an apostrophe and belongs to number literals: it separates the size from the base, as in 8'hFF. ` is a grave accent (backtick) and introduces compiler directives and macros, as in `timescale 1ns/1ps or `WIDTH. They are adjacent on many keyboards and visually similar in some fonts; writing 8`hFF or 'define produces errors that read as though the problem is three lines away.(d) ' 是單引號,屬於數值常數:它把位寬和基底分開,例如 8'hFF。` 是重音符(反引號),用來引入編譯指示與巨集,例如 `timescale 1ns/1ps 或 `WIDTH。在許多鍵盤上它們相鄰,在某些字體裡看起來又很像;寫成 8`hFF 或 'define 產生的錯誤,讀起來會像是問題出在三行以外的地方。(d) ' はアポストロフィで、数値リテラルに属する。サイズと基数を区切る。例:8'hFF。` はバッククォートで、コンパイラ指示子とマクロを導く。例:`timescale 1ns/1ps や `WIDTH。多くのキーボードで隣り合い、フォントによっては見分けがつきにくい。8`hFF や 'define と書いたときのエラーは、三行離れた場所に問題があるかのように読める。
Everything in this lecture is a foundation for something specific later in the course, and it is worth seeing the map once.這一講裡的每一件事,都是課程後面某個特定主題的基礎;把這張地圖看過一次是值得的。この講義のすべては、科目の後半の具体的な何かの土台である。その地図を一度眺めておく価値はある。
| From Lecture 1來自第 1 講第 1 講から | Developed in在哪裡展開どこで展開されるか |
|---|---|
Simulation, testbenches, the event queue, the $ tasks模擬、測試平台、事件佇列、$ 系統任務シミュレーション、テストベンチ、イベントキュー、$ タスク | Lecture 2 — simulation and testbench construction第 2 講 — 模擬與測試平台的建構第 2 講 — シミュレーションとテストベンチの構築 |
| Four-value logic, gate primitives, strengths, delay models, user-defined primitives四值邏輯、閘基元、驅動強度、延遲模型、使用者自訂基元四値論理、ゲートプリミティブ、強度、遅延モデル、ユーザ定義プリミティブ | Lecture 3 — syntax, UDPs and delay第 3 講 — 語法、UDP 與延遲第 3 講 — 構文・UDP・遅延 |
always blocks, sensitivity lists, blocking vs non-blocking, if/case/loops, asynchronous resetalways 區塊、敏感清單、阻隔式與非阻隔式、if/case/迴圈、非同步重置always ブロック、感度リスト、ブロッキングとノンブロッキング、if/case/ループ、非同期リセット | Lecture 4 — behavioral modeling第 4 講 — 行為建模第 4 講 — 動作モデリング |
How assign and combinational always become gates; latch inference; always @(*)assign 與組合式 always 如何變成閘;閂鎖推斷;always @(*)assign と組合せ always がどうゲートになるか、ラッチ推論、always @(*) | Lecture 5 — synthesis of combinational logic第 5 講 — 組合邏輯的合成第 5 講 — 組合せ論理の合成 |
| Flip-flop inference, reset styles, pipelining, timing closure正反器推斷、重置風格、管線化、時序收斂フリップフロップ推論、リセットのスタイル、パイプライン化、タイミングクロージャ | Lecture 6 — synthesis of sequential logic第 6 講 — 順序邏輯的合成第 6 講 — 順序論理の合成 |
The synthesisable subset, arrays of instances vs generate, parameters, tri-state可合成子集、實例陣列與 generate 的取捨、參數、三態合成可能サブセット、インスタンス配列と generate、パラメータ、トライステート | Lecture 7 — synthesis of language constructs第 7 講 — 語言構造的合成第 7 講 — 言語構文の合成 |
| Architecture choice, adders, the delay/area trade, worked datapath examples架構選擇、加法器、延遲與面積的取捨、資料路徑的實例演練アーキテクチャの選択、加算器、遅延と面積のトレードオフ、データパスの実例 | Lecture 8 — design examples第 8 講 — 設計範例第 8 講 — 設計例 |
If one idea survives from this lecture, make it this: Verilog looks like a programming language and is not one. Every line you write is a claim about a piece of hardware, and two readers evaluate that claim — a simulator, which tells you what it does, and a synthesiser, which tells you what it is. The craft of RTL design is writing code that both readers agree about.如果這一講只留下一個想法,就讓它是這個:Verilog 看起來像程式語言,但它不是。你寫的每一行都是關於一塊硬體的主張,而有兩位讀者在評斷這個主張 — 模擬器告訴你它做什麼,合成器告訴你它是什麼。RTL 設計的手藝,就是寫出讓這兩位讀者意見一致的程式碼。この講義から一つだけ持ち帰るなら、これにせよ。Verilog はプログラミング言語に見えるが、そうではない。書く一行一行がハードウェアについての主張であり、その主張を二人の読み手が評価する——シミュレータはそれが何をするかを教え、合成器はそれが何であるかを教える。RTL 設計の技芸とは、二人の読み手の意見が一致するコードを書くことである。