Outline — click to expand大綱 — 點擊展開アウトライン — クリックで展開
1. Behaviors1. 行為描述1. 振る舞い
2. Blocking vs Non-Blocking2. 阻斷式與非阻斷式指派2. ブロッキング代入とノンブロッキング代入
3. Procedural Continuous Assignment3. 程序性連續指派3. 手続き型連続代入
4. Procedural Timing Controls4. 程序性時間控制4. 手続き型タイミング制御
5. Intra- vs Inter-Assignment Delay5. 指派內延遲與指派間延遲5. 代入内遅延と代入間遅延
6. The Scheduling Model6. 排程模型6. スケジューリングモデル
7. Activity Flow Control7. 活動流程控制7. アクティビティのフロー制御
8. fork ... join and Races8. fork ... join 與競爭8. fork ... join と競合
9. Tasks and Functions9. 任務與函式9. タスクと関数
10. Timing Analysis & Checks10. 時序分析與時序檢查10. タイミング解析とタイミングチェック
11. Finite State Machines11. 有限狀態機11. 有限状態機械
12. Summary12. 總結12. まとめ
← DCIC Study Guides← DCIC 學習指南← DCIC 学習ガイド
EE5733 Digital Communication IC Design · Lecture 4EE5733 數位通訊積體電路設計 · Lecture 4EE5733 デジタル通信 IC 設計 · Lecture 4

Behavioral Descriptions, Explained行為層描述詳解振る舞い記述の解説

The full Lecture 4, Parts 1 and 2: behaviors and the scheduling model, blocking versus non-blocking assignment, procedural continuous assignment, timing controls, flow control, fork/join races, tasks and functions, timing checks, and finite state machines.完整的 Lecture 4,第 1 部與第 2 部:行為描述與排程模型、阻斷式與非阻斷式指派、程序性連續指派、時間控制、流程控制、fork/join 競爭、任務與函式、時序檢查,以及有限狀態機。Lecture 4 の全体、パート 1 とパート 2:振る舞いとスケジューリングモデル、ブロッキング代入とノンブロッキング代入、手続き型連続代入、タイミング制御、フロー制御、fork/join の競合、タスクと関数、タイミングチェック、そして有限状態機械。

Contents目錄目次

  1. Behaviors: initial and always行為描述:initial 與 always振る舞い:initial と always
  2. Blocking vs Non-Blocking Assignment阻斷式與非阻斷式指派ブロッキング代入とノンブロッキング代入
  3. Procedural Continuous Assignment程序性連續指派手続き型連続代入
  4. Procedural Timing Controls程序性時間控制手続き型タイミング制御
  5. Intra- vs Inter-Assignment Delay指派內延遲與指派間延遲代入内遅延と代入間遅延
  6. The Simulation Scheduling Model模擬排程模型シミュレーションのスケジューリングモデル
  7. Activity Flow Control活動流程控制アクティビティのフロー制御
  8. Parallel Activity Flow and Race Conditions平行活動流程與競爭條件並列アクティビティフローと競合状態
  9. Tasks and Functions任務與函式タスクと関数
  10. Timing Analysis and Timing Checks時序分析與時序檢查タイミング解析とタイミングチェック
  11. Finite State Machines有限狀態機有限状態機械
  12. Summary, Checklist and Errata總結、檢查清單與勘誤まとめ・チェックリスト・正誤表
How to read this page這一頁怎麼讀このページの読み方

Lecture 4 is split across two slide decks. Part 1 (44 slides) is about how a procedural block executes: the two behavioral constructs, the two assignment operators, the timing controls that suspend a thread, and the control-flow statements. Part 2 (29 slides) is about how to package and verify that behavior: tasks and functions, the timing-check system tasks, and the two descriptive styles for finite state machines.Lecture 4 分成兩份投影片。第 1 部(44 張投影片)講的是一個程序區塊如何執行:兩種行為構造、兩種指派運算子、會讓執行緒暫停的時間控制,以及控制流程敘述。第 2 部(29 張投影片)講的是如何把那個行為包裝起來並加以驗證:任務與函式、時序檢查系統任務,以及有限狀態機的兩種描述風格。Lecture 4 は二つのスライドデッキに分かれている。パート 1(44 スライド)は手続き型ブロックがどう実行されるかを扱う:二つの振る舞い構文、二つの代入演算子、スレッドを中断させるタイミング制御、そして制御フロー文である。パート 2(29 スライド)はその振る舞いをどう包装し検証するかを扱う:タスクと関数、タイミングチェック用システムタスク、そして有限状態機械の二つの記述スタイルである。

The single hardest idea in the whole lecture — and the one that separates people who can debug RTL from people who cannot — is the difference between = and <=. Section 2 therefore goes well beyond the slides and explains the event queue that the two operators schedule into. If you read only one section, read that one.整個講次裡最難的一個觀念 —— 也是能不能除錯 RTL 的分水嶺 —— 就是 = 與 <= 的差別。因此第 2 節遠遠超出投影片的範圍,直接解釋這兩個運算子排程進去的那個事件佇列。如果你只讀一節,就讀那一節。この講義全体で最も難しい考え方であり、RTL をデバッグできる者とできない者を分けるのが、= と <= の違いである。そのため第 2 節はスライドをはるかに超えて、この二つの演算子がスケジュールする先のイベントキューを説明する。一節だけ読むならこの節を読むとよい。

1. Behaviors: initial and always1. 行為描述:initial 與 always1. 振る舞い:initial と always

1.1 One module, every level of abstraction1.1 一個模組,涵蓋每一個抽象層次1.1 一つのモジュールに、あらゆる抽象化レベル

The first slide of the lecture is deliberately unglamorous: it shows a module body containing a continuous assignment, a gate primitive, a user-defined primitive, a sub-module instance and an always block, all side by side. The point is not syntax. The point is that Verilog does not have "a behavioral language" and "a structural language" that you choose between; it has one language whose constructs can be mixed freely inside a single module, and the simulator runs all of them concurrently.講義的第一張投影片刻意畫得平淡:它展示一個模組本體,裡面同時擺著一個連續指派、一個閘級基本元件、一個使用者自定義基本元件、一個子模組實例,以及一個 always 區塊。重點不在語法。重點在於 Verilog 並沒有分成「一種行為語言」和「一種結構語言」讓你二選一;它只有一種語言,而這種語言的各種構造可以在同一個模組裡自由混用,模擬器會把它們全部並行執行。講義の最初のスライドはわざと地味である:連続代入、ゲートプリミティブ、ユーザ定義プリミティブ、下位モジュールのインスタンス、そして always ブロックが横並びに入ったモジュール本体を示している。要点は構文ではない。要点は、Verilog には選択すべき「振る舞い記述の言語」と「構造記述の言語」があるのではなく、言語は一つだけで、その構文は単一のモジュール内で自由に混在でき、シミュレータはそれらすべてを並行して走らせるという点である。

module my_module (...);
  ...
  assign ...;               // continuous assignment  - dataflow level
  and (...);                // primitive instance     - gate level
  my_udp (...);             // UDP instance           - gate level
  adder_16bits M1 (...);    // module instance        - structural level
  always @ (...) begin      // behavior               - behavioral level
    ...
  end
endmodule

This matters practically for two reasons. First, real designs are written this way: a datapath is often a structural netlist of hand-chosen adders and multiplexers, while the controller that drives it is a behavioral state machine in the same file. Second, and more subtly, every one of those constructs is a separate process in the simulator's eyes. A continuous assignment is a process that re-executes whenever its right-hand side changes. A gate primitive is a process that re-executes whenever an input changes. An always block is a process that re-executes whenever its event control is satisfied. They are all peers. None of them "runs first" because it appears earlier in the file — a fact that section 2 will turn into a precise statement about event regions, and section 8 will turn into a warning about race conditions.這在實務上有兩個意義。第一,真正的設計就是這樣寫的:資料路徑往往是一份由人工挑選的加法器與多工器構成的結構性 netlist,而驅動它的控制器則是同一個檔案裡的行為層狀態機。第二,更微妙的是,上面每一個構造在模擬器眼中都是一個獨立的行程。連續指派是一個行程,只要右手邊改變就重新執行。閘級基本元件是一個行程,只要輸入改變就重新執行。always 區塊是一個行程,只要事件控制被滿足就重新執行。它們地位完全平等。沒有任何一個會因為寫在檔案前面就「先跑」—— 第 2 節會把這件事變成關於事件區域的精確陳述,第 8 節則會把它變成關於競爭條件的警告。これは実務上二つの意味を持つ。第一に、実際の設計はこのように書かれる:データパスは手で選んだ加算器やマルチプレクサからなる構造的なネットリストであることが多く、それを駆動するコントローラは同じファイル内の振る舞い記述のステートマシンである。第二に、より微妙な点として、これらの構文は一つひとつがシミュレータから見れば別個のプロセスである。連続代入は右辺が変化するたびに再実行されるプロセスである。ゲートプリミティブは入力が変化するたびに再実行されるプロセスである。always ブロックはイベント制御が満たされるたびに再実行されるプロセスである。これらはすべて対等である。ファイル中で先に書かれているからといって「先に走る」ものはない。第 2 節ではこれをイベント領域についての厳密な記述に変え、第 8 節では競合状態への警告に変える。

The carry look-ahead adder of §7.6 is the lecture's showcase of this mixing: the generate and propagate terms are continuous assignments, the carry chain is an event-triggered procedural block, and the sum and carry-out are continuous assignments again. Three levels, eleven lines, one module.§7.6 的超前進位加法器就是這種混用的示範:產生項與傳遞項是連續指派,進位鏈是事件觸發的程序區塊,而總和與進位輸出又是連續指派。三個層次,十一行,一個模組。§7.6 のキャリー先見加算器は、この混在の見本である:生成項と伝播項は連続代入、キャリーチェーンはイベント駆動の手続き型ブロック、和とキャリー出力はふたたび連続代入である。三つの階層、十一行、一つのモジュール。

1.2 initial: the one-shot thread1.2 initial:一次性執行緒1.2 initial:一度きりのスレッド

An initial block starts executing at simulation time 0 and runs its body exactly once. When the last statement of the body completes, the thread dies and is never revived. That is the entire semantics — everything else follows from it.initial 區塊在模擬時間 0 開始執行,把本體剛好跑一次。當本體的最後一道敘述執行完,這個執行緒就死掉,永遠不會復活。語意就只有這些 —— 其他一切都是從這裡推出來的。initial ブロックはシミュレーション時刻 0 で実行を開始し、本体をちょうど一度だけ実行する。本体の最後の文が終わるとスレッドは死に、二度と復活しない。意味論はこれだけであり、残りはすべてここから導かれる。

initial                // an "initial" behavior
  begin
    sig_a = 0;         // procedural assignments
    sig_b = 1;         // execute sequentially
    sig_c = 0;
  end

Three consequences are worth spelling out, because exam questions live in all three.有三個推論值得明講,因為三個都是考題的產地。明示しておく価値のある帰結が三つある。試験問題はこの三つすべてに潜んでいるからだ。

initial is not synthesisable. There is no silicon that "does something once at power-up" unless you build a reset sequencer for it, and a synthesis tool is not going to invent one. Its home is the test bench, where it is used for stimulus sequences, for $monitor setup, for clock initialisation, and for the #T $finish; watchdog that stops a run that would otherwise never terminate.initial 不可合成。除非你自己做一個重置序列器,否則沒有任何矽片會「在上電時做一次某件事」,而合成工具也不會替你發明一個。它的歸宿是測試平台:用來寫激勵序列、設定 $monitor、初始化時脈,以及寫那個 #T $finish; 看門狗,好讓本來永遠不會停的模擬停下來。initial は合成できない。リセットシーケンサを自分で作らない限り「電源投入時に一度だけ何かをする」シリコンは存在せず、合成ツールがそれを発明してくれることもない。その居場所はテストベンチであり、刺激シーケンス、$monitor の設定、クロックの初期化、そして放っておけば終わらない実行を止める #T $finish; のウォッチドッグに使われる。

A note on FPGA initial blocks關於 FPGA 上的 initial 區塊FPGA の initial ブロックについての注意

Some FPGA flows do honour a simple initial reg_x = 0; by loading the value into the configuration bitstream. That is a vendor extension, not IEEE-1364 behaviour, and it does not exist in the cell-based ASIC flow this course targets. Write an explicit reset.有些 FPGA 流程確實會理會單純的 initial reg_x = 0;,把這個值載進組態位元流裡。那是廠商的延伸功能,不是 IEEE-1364 的行為,而且在本課程鎖定的 cell-based ASIC 流程裡並不存在。請明確寫出重置。一部の FPGA フローは単純な initial reg_x = 0; を実際に尊重し、その値をコンフィギュレーションビットストリームに埋め込む。これはベンダ拡張であって IEEE-1364 の振る舞いではなく、本講義が対象とするセルベース ASIC フローには存在しない。リセットは明示的に書くこと。

1.3 always: the cyclic thread1.3 always:循環執行緒1.3 always:循環するスレッド

An always block is an infinite loop. It starts at time 0, executes its body, and then immediately starts executing its body again, forever, until the simulation ends. That is also the entire semantics, and the slide's phrase "cyclic (repetitive) activity flow" is exactly right.always 區塊就是一個無窮迴圈。它在時間 0 啟動,執行本體,然後立刻又開始執行本體,永遠如此,直到模擬結束。語意也就只有這些,而投影片上「循環(重複)的活動流程」這句話說得完全正確。always ブロックは無限ループである。時刻 0 に起動して本体を実行し、終わると直ちにまた本体を実行し、シミュレーションが終わるまでそれを永遠に繰り返す。意味論もこれだけであり、スライドの「循環的(反復的)なアクティビティフロー」という表現はまさに正しい。

always @ (b or c or d) begin
  if (b == 1'b1) begin
    a = c;
  end
  else begin
    a = d;
  end
end

The event control @(b or c or d) is not part of the always construct — it is simply the first statement of the body. Read the block as: "forever: wait for a change on b, c or d; then run the if/else". That reading immediately explains the single most common way to hang a simulator:事件控制 @(b or c or d) 並不是 always 構造的一部分 —— 它只不過是本體的第一道敘述。把這個區塊讀成:「永遠重複:等 b、c 或 d 變化;然後執行那個 if/else」。這樣讀立刻就能解釋,為什麼最常見的模擬器當機寫法會是這樣:イベント制御 @(b or c or d) は always 構文の一部ではない。単に本体の最初の文にすぎない。このブロックは「永遠に繰り返す:b、c、d のいずれかの変化を待ち、それから if/else を実行する」と読めばよい。こう読めば、シミュレータをハングさせる最も一般的な書き方がただちに説明できる。

Hangs the simulator會讓模擬器當掉シミュレータがハングする
always
  a = b & c;      // no time control at all
Correct正確寫法正しい書き方
always @ (b or c)
  a = b & c;      // suspends on the event control

The left-hand version is a zero-delay infinite loop. The simulator will execute it an unbounded number of times without ever advancing simulated time, so the run appears to freeze while one CPU core spins at 100%. Every always block must contain at least one construct that consumes simulation time — an event control, a delay control, or a wait — somewhere on every path through the body.左邊那個版本是零延遲無窮迴圈。模擬器會在模擬時間完全不前進的情況下把它執行無限多次,於是整個執行看起來像凍住了,而某一顆 CPU 核心以 100% 空轉。每個 always 區塊都必須在通過本體的每一條路徑上,至少含有一個會消耗模擬時間的構造 —— 事件控制、延遲控制,或一個 wait。左の版はゼロ遅延の無限ループである。シミュレータはシミュレーション時間をまったく進めずにこれを無限回実行するため、実行は凍りついたように見え、CPU コアの一つが 100% で空回りする。すべての always ブロックは、本体を通るあらゆる経路のどこかに、シミュレーション時間を消費する構文——イベント制御、遅延制御、あるいは wait——を少なくとも一つ含まねばならない。

The example block is a two-to-one multiplexer. Its sensitivity list names every signal the body reads: b because the condition tests it, c and d because the branches read them. Omit d and the simulation will fail to update a when d changes, while synthesis will build a mux that does update — the canonical simulation/synthesis mismatch, treated in detail in §2.7. Verilog-2001's always @* (or @(*)) builds the list automatically and should be used for every combinational block in new code.範例區塊是一個二選一多工器。它的敏感度列表列出本體讀取的每一個訊號:b 因為條件要測試它,c 與 d 因為兩個分支會讀它們。少寫一個 d,模擬就會在 d 改變時忘了更新 a,而合成卻會做出一個真的會更新的多工器 —— 這就是經典的模擬/合成不一致,§2.7 會詳談。Verilog-2001 的 always @*(或 @(*))會自動建出這份列表,新寫的程式碼裡每一個組合邏輯區塊都該用它。例として挙げられているブロックは 2 対 1 のマルチプレクサである。その感度リストには本体が読む信号がすべて並ぶ:条件が評価する b、各分岐が読む c と d である。d を書き落とすと、シミュレーションは d が変化しても a を更新しないのに、合成はきちんと更新するマルチプレクサを作る——これが典型的なシミュレーション/合成の不一致であり、§2.7 で詳しく扱う。Verilog-2001 の always @*(または @(*))はこのリストを自動生成するので、新しいコードの組合せ論理ブロックにはすべてこれを使うべきである。

1.4 What a behavior may drive1.4 行為描述可以驅動什麼1.4 振る舞いが駆動できるもの

Statements inside initial and always blocks are procedural statements, and procedural assignments may only target register variables — reg, integer, real, time, realtime and memories. They may not target nets. The reason is structural rather than arbitrary: a net models a physical wire whose value is resolved from its drivers, and a procedural block is not a driver — it is a thread that writes a value and walks away. A variable can hold that value between writes; a net cannot, because a net with no active driver goes to z.initial 與 always 區塊裡的敘述是程序性敘述,而程序性指派只能寫到暫存器類變數 —— reg、integer、real、time、realtime 以及記憶體。它們不能寫到 net。理由是結構上的,不是隨便規定的:net 模擬的是一條實體導線,它的值由驅動者解析出來,而程序區塊並不是驅動者 —— 它只是一個寫個值就走人的執行緒。變數可以在兩次寫入之間把值保留住;net 不行,因為沒有作用中驅動者的 net 會變成 z。initial と always ブロックの中の文は手続き型文であり、手続き型代入が書き込めるのはレジスタ型変数——reg、integer、real、time、realtime、およびメモリ——に限られる。ネットには書き込めない。理由は恣意的なものではなく構造的である:ネットは駆動源から値が解決される物理的な配線のモデルであるのに対し、手続き型ブロックは駆動源ではなく、値を書いて立ち去るスレッドにすぎない。変数は書き込みと書き込みの間その値を保持できるが、ネットは保持できない。能動的な駆動源のないネットは z になるからである。

The lecture flags one exception: force ... release, which can drive a net from inside a procedural block. It gets its own treatment in §3.3, and the reason it is allowed is that force does not write a value and leave — it installs a persistent override that behaves like a driver until release removes it.講義指出一個例外:force ... release 可以從程序區塊內部驅動 net。§3.3 會專門處理它,而它之所以被允許,是因為 force 不是寫個值就走 —— 它安裝了一個持續性的覆寫,其行為就像一個驅動者,直到 release 把它拿掉為止。講義は例外を一つ挙げている:force ... release は手続き型ブロックの中からネットを駆動できる。これは §3.3 で改めて扱うが、許される理由は、force が値を書いて立ち去るのではなく、release が外すまで駆動源のように振る舞う持続的なオーバライドを設置するからである。

You want to write你想寫入的對象書き込みたい対象From a continuous assignment從連續指派連続代入からFrom a procedural block從程序區塊手続き型ブロックから
wire y;Yes — assign y = ...;可以 —— assign y = ...;可 —— assign y = ...;No (except force)不可以(force 除外)不可(force を除く)
reg y;No不可以不可Yes — y = ...; or y <= ...;可以 —— y = ...; 或 y <= ...;可 —— y = ...; または y <= ...;

A final terminology warning that catches people every semester: the keyword reg does not mean "register" in the hardware sense. reg means "a variable that holds its value in the simulator's data structures". Whether it becomes a flip-flop, a latch, or a lump of combinational logic is decided entirely by how you assign to it, which is the business of Lectures 5 and 6. A reg assigned in always @* with a complete branch structure becomes pure combinational logic and no storage element appears in the netlist at all.最後一個每學期都害到人的術語警告:關鍵字 reg 不是硬體意義上的「暫存器」。reg 的意思是「一個在模擬器資料結構裡保有自身數值的變數」。它最後會變成正反器、閂鎖,還是一團組合邏輯,完全取決於你怎麼對它指派,那是第 5、6 講的事。一個在 always @* 裡、分支結構完整地被指派的 reg,會變成純組合邏輯,netlist 裡根本不會出現任何儲存元件。最後に、毎学期必ず誰かがつまずく用語上の注意:キーワード reg はハードウェアの意味での「レジスタ」ではない。reg は「シミュレータのデータ構造の中で値を保持する変数」という意味である。それがフリップフロップになるか、ラッチになるか、組合せ論理の塊になるかは、どう代入するかだけで決まり、それは第 5 講と第 6 講の話題である。分岐構造が完備した always @* の中で代入される reg は純粋な組合せ論理となり、ネットリストには記憶素子がまったく現れない。

Practice 1 — Counting threads練習 1 — 數一數有幾個執行緒練習 1 — スレッドを数える

Problem: How many concurrent processes does the simulator create for the module below, and in what order do they start?題目:對於下面這個模組,模擬器會建立幾個並行行程?它們啟動的順序是什麼?問題:次のモジュールに対して、シミュレータはいくつの並行プロセスを生成するか。またそれらはどの順で起動するか。

module q1 (input a, b, output y, output reg z);
  wire t;
  assign t = a & b;
  and  G1 (y, t, a);
  initial z = 0;
  always @(posedge a) z = ~z;
  always #5 $display("tick");
endmodule
Click to reveal solution點擊展開解答クリックで解答を表示

Five. The continuous assignment assign t, the primitive instance G1, the initial block, and the two always blocks are each an independent process.五個。連續指派 assign t、基本元件實例 G1、initial 區塊,以及兩個 always 區塊,各自都是一個獨立的行程。五つ。連続代入 assign t、プリミティブインスタンス G1、initial ブロック、そして二つの always ブロックが、それぞれ独立したプロセスである。

All five are made active at time 0 and the order in which they are executed within time 0 is not defined by the standard. In this module that happens not to matter — initial z = 0; is the only writer of z at time 0, and always @(posedge a) suspends on its event control before doing anything. If instead you had written initial z = 0; alongside initial z = 1;, the final value of z would be a genuine race and either answer would be a legal simulation.這五個都在時間 0 被設為作用中,而它們在時間 0 之內的執行順序標準並未定義。在這個模組裡剛好不影響結果 —— initial z = 0; 是時間 0 唯一寫 z 的人,而 always @(posedge a) 什麼都還沒做就先停在事件控制上。但如果你寫的是 initial z = 0; 再加上 initial z = 1;,z 的最終值就會是真正的競爭,兩個答案都算合法的模擬結果。この五つはすべて時刻 0 でアクティブになるが、時刻 0 の中でどの順に実行されるかは標準が定めていない。このモジュールではたまたまそれが問題にならない——initial z = 0; が時刻 0 における z の唯一の書き手であり、always @(posedge a) は何かをする前にイベント制御で中断するからである。これに対し initial z = 0; と initial z = 1; を並べて書いたなら、z の最終値は正真正銘の競合となり、どちらの答えも合法なシミュレーション結果となる。

Note also that the third process never terminates and never stops advancing time, so this module needs a $finish somewhere or the run is unbounded.另外要注意,第三個行程永遠不會結束,也永遠不會停止推進時間,所以這個模組需要在某處放一個 $finish,否則模擬會無限跑下去。なお、三つ目のプロセスは決して終了せず、時間を進め続けることもやめない。したがってこのモジュールにはどこかに $finish が必要であり、さもなければ実行は際限なく続く。

2. Blocking vs Non-Blocking Assignment2. 阻斷式與非阻斷式指派2. ブロッキング代入とノンブロッキング代入

The slide states the rule in two lines: a blocking assignment "is completed before the next assignment starts", a non-blocking assignment is "executed in parallel". Both statements are true and neither is usable for debugging, because neither tells you when the update becomes visible to the rest of the design. To get that you have to know what the simulator's event queue looks like, so we start there.投影片用兩行講完規則:阻斷式指派「在下一個指派開始之前就已完成」,非阻斷式指派則是「並行執行」。兩句話都是對的,也都沒辦法拿來除錯,因為兩句都沒告訴你更新在什麼時候才對設計的其餘部分變得可見。要得到那個答案,你必須知道模擬器的事件佇列長什麼樣子,所以我們就從那裡開始。スライドは規則を二行で述べている:ブロッキング代入は「次の代入が始まる前に完了する」、ノンブロッキング代入は「並列に実行される」。どちらも正しいが、どちらもデバッグには使えない。更新が設計の他の部分からいつ見えるようになるのかを、どちらも述べていないからである。それを知るにはシミュレータのイベントキューの姿を知らねばならない。そこで、まずそこから始める。

2.1 The stratified event queue2.1 分層事件佇列2.1 階層化イベントキュー

IEEE 1364 does not describe simulation as "statements executing". It describes it as a loop over time slots, where each time slot contains several ordered regions, and each region is an unordered bag of events. The simulator picks any event from the earliest non-empty region, executes it, puts any new events it generates into the appropriate region, and repeats. Only when every region of the current time slot is empty does simulated time advance.IEEE 1364 不是把模擬描述成「敘述在執行」。它描述成一個對時槽的迴圈,每個時槽含有數個有序的區域,而每個區域是一袋沒有順序的事件。模擬器從最早的非空區域裡任選一個事件執行,把它產生的新事件放進對應的區域,然後重複。只有當目前時槽的每一個區域都空了,模擬時間才會前進。IEEE 1364 はシミュレーションを「文が実行される」とは記述しない。タイムスロットを回るループとして記述する。各タイムスロットは順序付けられた複数の領域を含み、各領域は順序のないイベントの袋である。シミュレータは最も早い空でない領域から任意のイベントを選んで実行し、生成された新しいイベントを適切な領域に入れ、これを繰り返す。現在のタイムスロットのすべての領域が空になって初めて、シミュレーション時間が進む。

The full standard has five regions. For Lecture 4 you need four of them, in this order:完整的標準有五個區域。就 Lecture 4 而言,你需要其中四個,順序如下:標準の完全な定義には五つの領域がある。Lecture 4 で必要なのはそのうち四つで、順序は次のとおりである。

#Region區域領域What lands here會落在這裡的東西ここに入るもの
1ActiveActiveActiveBlocking assignments; evaluation of continuous assignments, gate primitives and RHS expressions; $display and $write; the evaluation (not the update) of a non-blocking assignment.阻斷式指派;連續指派、閘級基本元件與右手邊運算式的求值;$display 與 $write;非阻斷式指派的求值(不含更新)。ブロッキング代入、連続代入・ゲートプリミティブ・右辺式の評価、$display と $write、そしてノンブロッキング代入の評価(更新は含まない)。
2InactiveInactiveInactiveEvents scheduled by an explicit #0 delay. Executed after the Active region of the same time slot is exhausted.由明確寫出的 #0 延遲所排程的事件。在同一時槽的 Active 區域耗盡之後才執行。明示的な #0 遅延によってスケジュールされたイベント。同じタイムスロットの Active 領域が尽きた後に実行される。
3NBA (non-blocking assignment update)NBA(非阻斷式指派更新)NBA(ノンブロッキング代入の更新)The update half of every non-blocking assignment whose delay has expired. Executed after Active and Inactive are both empty.每一個延遲已到期的非阻斷式指派的更新那一半。在 Active 與 Inactive 都空了之後才執行。遅延が満了したすべてのノンブロッキング代入のうち更新にあたる半分。Active と Inactive がともに空になった後に実行される。
4Monitor (postponed)Monitor(延後)Monitor(延期)$monitor and $strobe. Executed last, when the time slot has completely settled.$monitor 與 $strobe。最後執行,此時時槽已完全穩定。$monitor と $strobe。タイムスロットが完全に落ち着いた後、最後に実行される。

Two properties of this structure do all the work:這個結構有兩個性質撐起了一切:この構造の二つの性質がすべてを担っている。

One simulation time slot at time t 1 · ACTIVE blocking a = expr ; RHS evaluation $display 2 · INACTIVE events scheduled by #0 3 · NBA UPDATE the LHS write of every a <= expr 4 · POSTPONED $monitor $strobe An Active event may add more Active events at the same time t, so region 1 can iterate many times. Nothing in a later region runs until every earlier region is empty. Every RHS of a non-blocking assignment was already read in region 1, so all NBA updates see the pre-edge values. Then, and only then, time advances.
The stratified event queue. Blocking assignments and $display live in the Active region; the update half of a non-blocking assignment lives two regions later. That gap is the whole difference between = and <=.分層事件佇列。阻斷式指派與 $display 住在 Active 區域;非阻斷式指派的更新那一半則住在兩個區域之後。這個落差就是 = 與 <= 的全部差別。階層化イベントキュー。ブロッキング代入と $display は Active 領域にあり、ノンブロッキング代入の更新側は二つ後の領域にある。この隔たりこそが = と <= の違いのすべてである。

2.2 What = actually does2.2 = 實際做了什麼2.2 = が実際に行うこと

A blocking assignment a = expr; is a single Active event that does three things without interruption: evaluate expr, write the result into a, and schedule everything sensitive to a. Only then does the calling thread move to the next statement. The name is literal — the assignment blocks the thread until it is finished.阻斷式指派 a = expr; 是單一個 Active 事件,它不被打斷地做三件事:求出 expr、把結果寫進 a、並排程所有對 a 敏感的東西。做完這些,呼叫端的執行緒才往下一道敘述走。這個名字是字面上的意思 —— 指派會阻斷執行緒,直到它做完。ブロッキング代入 a = expr; は単一の Active イベントであり、中断されることなく三つのことを行う:expr を評価し、結果を a に書き込み、a に感応するすべてをスケジュールする。それが済んで初めて、呼び出し側のスレッドは次の文へ進む。名前は文字どおりで、代入は終わるまでスレッドをブロックする。

a = 0;
a = 1;
c = a;     // c = 1  - c sees the value a had one statement earlier

Because the write is immediate and in-region, a blocking assignment creates a visible intermediate value for anything else that happens to run between the two statements. In a single-threaded block this is exactly the behaviour you want from a programming language and exactly the behaviour you want when you are describing combinational logic, where intermediate wires are real.因為寫入是立即而且在同一區域內完成的,阻斷式指派會對任何剛好在兩道敘述之間執行的東西,產生一個看得見的中間值。在單執行緒的區塊裡,這正是你對一個程式語言所期待的行為,也正是你描述組合邏輯時想要的行為 —— 在那裡中間導線是真實存在的。書き込みが即時かつ同一領域内で行われるため、ブロッキング代入は、二つの文の間にたまたま走る何かに対して、見える中間値を作り出す。単一スレッドのブロックの中では、これはプログラミング言語に期待する振る舞いそのものであり、中間配線が実在する組合せ論理を記述するときに望む振る舞いそのものでもある。

2.3 What <= actually does2.3 <= 實際做了什麼2.3 <= が実際に行うこと

A non-blocking assignment a <= expr; is two events, not one. The Active half evaluates expr and stashes the result in an anonymous temporary; the thread then immediately moves to the next statement. The NBA half, scheduled in region 3 of the same time slot, writes the stashed value into a.非阻斷式指派 a <= expr; 是兩個事件,不是一個。Active 那一半求出 expr,把結果藏進一個匿名暫存位置;執行緒接著立刻往下一道敘述走。NBA 那一半排在同一時槽的第 3 區域,把藏起來的值寫進 a。ノンブロッキング代入 a <= expr; は一つではなく二つのイベントである。Active 側は expr を評価し、結果を無名の一時領域に退避させる。スレッドはただちに次の文へ進む。NBA 側は同じタイムスロットの第 3 領域にスケジュールされ、退避させた値を a に書き込む。

a = 0;
a <= 1;    // evaluate 1, remember it; a is still 0
c = a;     // c = 0  - the NBA update has not happened yet
           // ... end of the Active region ...
           // NBA region: a becomes 1

The slide's phrase "executed in parallel" means: every non-blocking assignment in the time slot reads its RHS during the Active region, before any of them writes its LHS. So all the reads see the same consistent snapshot of the design, and all the writes land together. That is precisely the behaviour of a bank of flip-flops sharing a clock edge: each flop samples its D input just before the edge, and all the Q outputs change just after it.投影片上「並行執行」這句話的意思是:這個時槽裡的每一個非阻斷式指派,都在 Active 區域讀它的右手邊,而且是在它們任何一個寫左手邊之前。所以所有的讀都看到同一份一致的設計快照,而所有的寫一起落地。這正是一組共用同一個時脈邊緣的正反器的行為:每個正反器在邊緣之前一瞬取樣它的 D 輸入,而所有 Q 輸出在邊緣之後一瞬一起改變。スライドの「並列に実行される」という表現の意味はこうである:そのタイムスロット内のすべてのノンブロッキング代入は、いずれかが左辺に書き込むより前に、Active 領域で右辺を読む。したがってすべての読み出しは同じ整合したスナップショットを見て、すべての書き込みはまとめて反映される。これはまさに、クロックエッジを共有するフリップフロップ群の振る舞いである:各フリップフロップはエッジの直前に D 入力をサンプルし、すべての Q 出力はエッジの直後にそろって変化する。

The slide says d <= 0; d <= 1; is "ambiguous → illegal". It is not.投影片說 d <= 0; d <= 1; 是「有歧義 → 不合法」。並不是。スライドは d <= 0; d <= 1; を「曖昧 → 違法」としている。そうではない。

Slide 6 of Part 1 claims that two non-blocking writes to the same variable in one time step leave d ambiguous. IEEE 1364-2001 §5.4.1 is explicit that the NBA update events for a single variable are kept in the order in which they were scheduled, so within one sequential block d <= 0; d <= 1; deterministically leaves d = 1. The last write wins, exactly as it would with blocking assignments.第 1 部的第 6 張投影片宣稱,在一個時間步裡對同一個變數做兩次非阻斷式寫入,會讓 d 的值有歧義。IEEE 1364-2001 §5.4.1 明確規定,同一個變數的 NBA 更新事件會依照它們被排程的順序保存,所以在一個循序區塊裡,d <= 0; d <= 1; 確定會讓 d = 1。最後一次寫入獲勝,和阻斷式指派完全一樣。パート 1 のスライド 6 は、一つのタイムステップ内で同じ変数に二度ノンブロッキング書き込みを行うと d が曖昧になると主張している。IEEE 1364-2001 §5.4.1 は、単一変数に対する NBA 更新イベントがスケジュールされた順に保たれると明記しており、したがって一つの逐次ブロック内では d <= 0; d <= 1; は確定的に d = 1 を残す。最後の書き込みが勝つ点は、ブロッキング代入とまったく同じである。

What is genuinely ambiguous is two different processes each scheduling an NBA to d in the same time slot: the relative order of the two Active evaluations is undefined, so the relative order of the two NBA updates is undefined too. Treat the slide's rule as excellent style advice — never write two assignments to one variable in one time step — rather than as a statement about the language.真正有歧義的,是兩個不同的行程在同一時槽各自對 d 排一個 NBA:兩次 Active 求值的相對順序未定義,所以兩次 NBA 更新的相對順序也未定義。請把投影片的規則當成很好的風格建議 —— 絕不要在一個時間步裡對同一個變數寫兩次指派 —— 而不是對語言本身的陳述。本当に曖昧なのは、異なる二つのプロセスが同じタイムスロットでそれぞれ d への NBA をスケジュールする場合である。二つの Active 評価の相対順序が未定義なので、二つの NBA 更新の相対順序も未定義となる。スライドの規則は、言語についての記述としてではなく、優れたスタイル上の助言——一つのタイムステップで一つの変数に二度代入してはならない——として受け取るとよい。

2.4 A full trace of the swap2.4 交換動作的完整追蹤2.4 スワップの完全トレース

Slides 7 and 8 of Part 1 show four two-line fragments starting from a = 1; b = 0;. Here is the same material traced event by event, which is what you should be able to do under exam conditions.第 1 部的第 7、8 張投影片展示了四個以 a = 1; b = 0; 為起點的兩行片段。以下是同樣的材料,一個事件一個事件地追蹤 —— 這正是你在考場上應該做得出來的事。パート 1 のスライド 7 と 8 は、a = 1; b = 0; から始まる二行の断片を四つ示している。以下は同じ題材をイベント単位でトレースしたものであり、試験の場で自力でできるようになっておくべき作業である。

Trace A — blocking, a = b; b = a;追蹤 A — 阻斷式,a = b; b = a;トレース A — ブロッキング、a = b; b = a;
Active event 1. Evaluate RHS b → 0. Write a ← 0. State: a=0, b=0.Active 事件 1。求右手邊 b → 0。寫 a ← 0。狀態:a=0, b=0。Active イベント 1。右辺 b を評価 → 0。a ← 0 を書き込む。状態:a=0, b=0。
Active event 2. Evaluate RHS a → 0 (the value written one event ago). Write b ← 0. State: a=0, b=0.Active 事件 2。求右手邊 a → 0(一個事件之前才寫進去的值)。寫 b ← 0。狀態:a=0, b=0。Active イベント 2。右辺 a を評価 → 0(一つ前のイベントで書き込まれた値)。b ← 0 を書き込む。状態:a=0, b=0。
Result: a=0, b=0. No swap; the original value of a was destroyed before it could be read.結果:a=0, b=0。沒有交換;a 的原始值在被讀到之前就被毀掉了。結果:a=0, b=0。スワップは起こらない。a の元の値は読まれる前に破壊されている。
Trace B — blocking, b = a; a = b;追蹤 B — 阻斷式,b = a; a = b;トレース B — ブロッキング、b = a; a = b;
Active event 1. RHS a → 1. Write b ← 1. State: a=1, b=1.Active 事件 1。右手邊 a → 1。寫 b ← 1。狀態:a=1, b=1。Active イベント 1。右辺 a → 1。b ← 1 を書き込む。状態:a=1, b=1。
Active event 2. RHS b → 1. Write a ← 1. State: a=1, b=1.Active 事件 2。右手邊 b → 1。寫 a ← 1。狀態:a=1, b=1。Active イベント 2。右辺 b → 1。a ← 1 を書き込む。状態:a=1, b=1。
Result: a=1, b=1. Also no swap, and note that the answer depends on statement order — A and B give different results from the same starting state.結果:a=1, b=1。一樣沒有交換,而且要注意答案取決於敘述順序 —— A 和 B 從同樣的起始狀態得到不同的結果。結果:a=1, b=1。やはりスワップは起こらず、しかも答えが文の順序に依存することに注意したい——A と B は同じ初期状態から別の結果を与える。
Trace C — non-blocking, a <= b; b <= a;追蹤 C — 非阻斷式,a <= b; b <= a;トレース C — ノンブロッキング、a <= b; b <= a;
Active event 1. Evaluate b → 0. Stash 0 for a. Schedule NBA #1. a is still 1.Active 事件 1。求 b → 0。替 a 藏起 0。排程 NBA #1。a 此刻仍是 1。Active イベント 1。b を評価 → 0。a 用に 0 を退避。NBA #1 をスケジュール。この時点で a はまだ 1 である。
Active event 2. Evaluate a → 1, because NBA #1 has not fired. Stash 1 for b. Schedule NBA #2.Active 事件 2。求 a → 1,因為 NBA #1 還沒發動。替 b 藏起 1。排程 NBA #2。Active イベント 2。a を評価 → 1。NBA #1 がまだ発火していないからである。b 用に 1 を退避。NBA #2 をスケジュール。
Active region empty. Inactive region empty. Move to NBA region.Active 區域已空。Inactive 區域也空。移到 NBA 區域。Active 領域が空になる。Inactive 領域も空である。NBA 領域へ移る。
NBA #1. Write a ← 0. NBA #2. Write b ← 1.NBA #1。寫 a ← 0。NBA #2。寫 b ← 1。NBA #1。a ← 0 を書き込む。NBA #2。b ← 1 を書き込む。
Result: a=0, b=1 — a true swap. Reversing the two statements gives exactly the same answer, because both RHS reads happened before both writes.結果:a=0, b=1 —— 真正的交換。把兩道敘述前後對調,答案完全一樣,因為兩次右手邊的讀都發生在兩次寫之前。結果:a=0, b=1——本物のスワップである。二つの文を入れ替えても答えはまったく同じになる。二つの右辺の読み出しが、どちらの書き込みよりも先に起きているからである。

That order-independence is the property you are buying when you use <=, and it is worth stating as a rule you can apply without thinking: inside one clocked block, if every assignment is non-blocking, the textual order of the statements cannot change the result. Inside the same block with blocking assignments, the order changes the result and therefore changes the hardware.這種與順序無關的性質,正是你使用 <= 所換來的東西,而且值得寫成一條可以不假思索套用的規則:在一個時脈同步區塊裡,若每一個指派都是非阻斷式的,敘述的文字順序就不可能改變結果。同一個區塊若改用阻斷式指派,順序會改變結果,因此也就改變了硬體。この順序非依存性こそ <= を使うことで得られる性質であり、考えずに適用できる規則として述べておく価値がある:一つのクロック同期ブロックの中で、すべての代入がノンブロッキングであれば、文の記述順序は結果を変えられない。同じブロックをブロッキング代入で書けば、順序が結果を変え、したがってハードウェアを変える。

2.5 Why the shift-register idiom needs <=2.5 為什麼移位暫存器慣用寫法需要 <=2.5 シフトレジスタの定石に <= が要る理由

Here is the canonical three-stage shift register, written both ways. Both compile. Both simulate without a warning. They are different circuits.以下是標準的三級移位暫存器,兩種寫法都列出來。兩者都編得過。兩者模擬時都不會有警告。它們是不同的電路。以下は標準的な三段シフトレジスタを両方の書き方で示したものである。どちらもコンパイルが通る。どちらも警告なしにシミュレーションできる。そして両者は別の回路である。

Correct — a 3-bit shift register正確 —— 一個 3 位元移位暫存器正しい —— 3 ビットシフトレジスタ
always @ (posedge clk) begin
  q1 <= d;
  q2 <= q1;
  q3 <= q2;
end
Wrong — one flip-flop and two wires錯誤 —— 一個正反器加兩條線誤り —— フリップフロップ一つと配線二本
always @ (posedge clk) begin
  q1 = d;
  q2 = q1;
  q3 = q2;
end

Trace the left-hand version at a rising edge where d=1 and the current state is q1=0, q2=0, q3=0. Three Active events evaluate d→1, q1→0 and q2→0 and stash them. Three NBA events then write q1←1, q2←0, q3←0. The data advanced exactly one stage. Repeat for three edges and the 1 walks q1 → q2 → q3. That is a shift register.在 d=1 且目前狀態為 q1=0、q2=0、q3=0 的某個上升緣,追蹤左邊那個版本。三個 Active 事件分別求出 d→1、q1→0、q2→0 並把它們藏起來。接著三個 NBA 事件寫入 q1←1、q2←0、q3←0。資料剛好前進一級。重複三個邊緣,那個 1 就沿著 q1 → q2 → q3 走。這才是移位暫存器。d=1 で現在の状態が q1=0、q2=0、q3=0 である立ち上がりエッジで、左の版をトレースしてみる。三つの Active イベントが d→1、q1→0、q2→0 を評価して退避する。続いて三つの NBA イベントが q1←1、q2←0、q3←0 を書き込む。データはちょうど一段進んだ。エッジを三回繰り返せば、その 1 は q1 → q2 → q3 と歩いていく。これがシフトレジスタである。

Trace the right-hand version at the same edge. Active event 1 writes q1←1 immediately. Active event 2 evaluates q1, which is now 1, and writes q2←1. Active event 3 writes q3←1. All three outputs change on the same edge, and d arrives at q3 in one clock cycle instead of three.同一個邊緣,追蹤右邊那個版本。Active 事件 1 立刻寫入 q1←1。Active 事件 2 求 q1,此時它已經是 1,於是寫入 q2←1。Active 事件 3 寫入 q3←1。三個輸出在同一個邊緣一起改變,而 d 只花一個時脈週期就抵達 q3,不是三個。同じエッジで右の版をトレースする。Active イベント 1 はただちに q1←1 を書き込む。Active イベント 2 は q1 を評価するが、それはすでに 1 なので q2←1 を書き込む。Active イベント 3 は q3←1 を書き込む。三つの出力が同じエッジでそろって変化し、d は三クロックではなく一クロックで q3 に到達する。

Synthesis sees it the same way. A synthesis tool reads a blocking assignment as "compute this value and make it available to the following statements in the same evaluation", which is the definition of a combinational intermediate. So q2 = q1 immediately after q1 = d does not read the flip-flop output q1 — it reads the wire carrying d. The resulting netlist has one flip-flop whose Q fans out to three names, or, depending on how the tool handles the unused registers, three flip-flops all loaded with d. Either way the circuit is wrong and the gate-level simulation will disagree with the RTL simulation.合成的看法也一樣。合成工具把阻斷式指派讀成「算出這個值,並讓它在同一次求值中供後續敘述使用」,這就是組合邏輯中間節點的定義。所以緊接在 q1 = d 之後的 q2 = q1 讀到的不是正反器輸出 q1 —— 它讀到的是那條載著 d 的線。產生出來的 netlist 會有一個正反器,其 Q 扇出到三個名字;或者,視工具怎麼處理那些沒用到的暫存器而定,變成三個都載入 d 的正反器。無論哪一種,電路都是錯的,而閘級模擬會和 RTL 模擬對不上。合成の見方も同じである。合成ツールはブロッキング代入を「この値を計算し、同じ評価内の後続の文から使えるようにする」と読む。これは組合せ論理の中間ノードの定義そのものである。したがって q1 = d の直後の q2 = q1 が読むのはフリップフロップ出力の q1 ではなく、d を運ぶ配線である。得られるネットリストは、Q が三つの名前にファンアウトするフリップフロップ一つになるか、未使用レジスタの扱い方によっては d を取り込むフリップフロップ三つになる。いずれにせよ回路は誤りであり、ゲートレベルシミュレーションは RTL シミュレーションと食い違う。

clk d q1 (<=) q2 (<=) q3 (<=) q1 (=) q2 (=) q3 (=) edge 1edge 2edge 3 edge 4edge 5 non-blocking blocking
A one-cycle pulse on d through the same three statements. With <= (green) the pulse walks one stage per edge. With = (red) all three outputs change together on edge 1 — the two later flip-flops have been optimised away into wires.同樣這三道敘述,讓 d 上的一個單週期脈衝通過。用 <=(綠色)時,脈衝每個邊緣前進一級。用 =(紅色)時,三個輸出在第 1 個邊緣一起改變 —— 後面兩個正反器已經被最佳化成導線了。同じ三つの文に、d 上の 1 サイクル幅のパルスを通した場合。<=(緑)ではパルスはエッジごとに一段ずつ歩く。=(赤)では三つの出力がエッジ 1 でそろって変化する——後ろの二つのフリップフロップは最適化で配線にされてしまっている。

2.6 Why the combinational idiom needs =2.6 為什麼組合邏輯慣用寫法需要 =2.6 組合せ論理の定石に = が要る理由

Now invert the situation. Combinational logic has no storage, so a multi-step computation inside always @* is a chain of wires, and every step must see the value the previous step just produced.現在把情況反過來。組合邏輯沒有儲存,所以 always @* 裡的多步驟運算就是一串導線,而每一步都必須看到前一步剛產生的值。今度は状況を逆にする。組合せ論理には記憶がないので、always @* の中の多段計算は配線の連鎖であり、各段は前段がいま生み出した値を見なければならない。

Correct — one XOR-AND network正確 —— 一個 XOR-AND 網路正しい —— 一つの XOR-AND ネットワーク
always @* begin
  t1 = a ^ b;
  t2 = t1 & c;
  y  = t2 | d;
end
Wrong — three stale reads錯誤 —— 三次讀到舊值誤り —— 三度とも古い値を読む
always @* begin
  t1 <= a ^ b;
  t2 <= t1 & c;
  y  <= t2 | d;
end

In the right-hand version, t2's RHS reads the value t1 held before this evaluation, because the NBA update of t1 has not fired yet. The block therefore computes y from a one-evaluation-old t1 and a two-evaluation-old t2.在右邊那個版本裡,t2 的右手邊讀到的是 t1 在這次求值之前所持有的值,因為 t1 的 NBA 更新還沒發動。於是這個區塊是用舊一次求值的 t1 和舊兩次求值的 t2 來算 y。右の版では、t2 の右辺はこの評価の前に t1 が保持していた値を読む。t1 の NBA 更新がまだ発火していないからである。したがってこのブロックは、一評価分古い t1 と二評価分古い t2 から y を計算する。

What saves the right-hand version from being outright broken in simulation is that t1 and t2 appear in the implicit sensitivity list of always @*. When the NBA region writes t1, that is a change on t1, which retriggers the block, which recomputes t2, which retriggers the block again... The block converges to the correct answer after three extra evaluations, in three extra NBA cycles, all at the same simulation time. So the final waveform is right and the intermediate waveform is a mess — and if you had written always @(a or b or c or d) with an explicit list that does not mention t1 and t2, it would never converge at all and y would simply be wrong.右邊那個版本之所以沒有在模擬中徹底壞掉,是因為 t1 和 t2 都出現在 always @* 的隱含敏感度列表裡。當 NBA 區域寫入 t1,那就是 t1 上的一次變化,於是重新觸發這個區塊,於是重算 t2,於是又重新觸發這個區塊……這個區塊在多花三次求值、多花三個 NBA 迴圈之後收斂到正確答案,而且全都發生在同一個模擬時間。所以最終波形是對的,中間波形一團亂 —— 而如果你寫的是 always @(a or b or c or d) 這種沒有提到 t1 和 t2 的明確列表,它根本永遠不會收斂,y 就乾脆是錯的。右の版がシミュレーションで完全に壊れずに済むのは、t1 と t2 が always @* の暗黙の感度リストに入っているからである。NBA 領域が t1 を書き込むと、それは t1 の変化であり、ブロックを再トリガし、t2 を再計算させ、それがまたブロックを再トリガする……。ブロックは追加の三回の評価、追加の三回の NBA サイクルを経て正しい答えに収束し、それらはすべて同一のシミュレーション時刻で起こる。つまり最終波形は正しく、途中の波形はひどいことになる。もし t1 と t2 を書いていない明示リスト always @(a or b or c or d) を書いていたなら、収束すらせず y はただ誤った値になる。

Synthesis, meanwhile, ignores the whole question and builds the same combinational cone in both cases. So the right-hand version is the classic mismatch in the other direction: the netlist is right, the RTL simulation is wrong or at best full of spurious transitions.另一方面,合成完全不理會這個問題,兩種寫法都建出同一個組合邏輯錐。所以右邊那個版本是另一個方向的經典不一致:netlist 是對的,RTL 模擬是錯的,最好的情況也只是充滿虛假的轉態。一方、合成はこの問題をまったく無視し、どちらの書き方からも同じ組合せ論理コーンを作る。したがって右の版は逆方向の典型的な不一致である:ネットリストは正しく、RTL シミュレーションが誤っているか、よくても偽の遷移だらけになる。

The two rules, and why they are not arbitrary兩條規則,以及它們為什麼不是隨便定的二つの規則と、それが恣意的でない理由
  • Clocked (sequential) logic → always <=. You want every statement to read the pre-edge state, because that is what flip-flops do.時脈同步(循序)邏輯 → 一律用 <=。你要的是每一道敘述都讀到邊緣之前的狀態,因為正反器就是這樣做的。クロック同期(順序)論理 → つねに <=。すべての文にエッジ前の状態を読ませたいからである。フリップフロップがそう振る舞う。
  • Combinational logic → always =. You want every statement to read the value the previous statement just produced, because that is what wires do.組合邏輯 → 一律用 =。你要的是每一道敘述都讀到前一道敘述剛產生的值,因為導線就是這樣做的。組合せ論理 → つねに =。すべての文に前の文がいま生み出した値を読ませたいからである。配線がそう振る舞う。
  • Never mix the two operators on the same variable, and preferably not in the same block. Several tools reject it outright; the ones that accept it give you a model whose behaviour depends on evaluation order.絕不要在同一個變數上混用這兩個運算子,最好連同一個區塊裡都不要混。有好幾個工具會直接拒絕;接受它的那些則會給你一個行為取決於求值順序的模型。同一の変数に二つの演算子を混ぜてはならない。できれば同一のブロック内でも混ぜない。これを即座に拒否するツールもいくつかあり、受け付けるツールは評価順序に振る舞いが依存するモデルを返してくる。
  • Never assign the same variable from two different always blocks. In simulation it is a race; in synthesis it is a multiple-driver error.絕不要從兩個不同的 always 區塊指派同一個變數。在模擬裡這是競爭;在合成裡這是多重驅動錯誤。同じ変数を二つの異なる always ブロックから代入してはならない。シミュレーションでは競合となり、合成では多重駆動エラーとなる。

2.7 What a simulation/synthesis mismatch looks like2.7 模擬/合成不一致長什麼樣子2.7 シミュレーション/合成の不一致とはどういうものか

"Mismatch" is a vague word, so here is a concrete catalogue of the four ways this lecture's material produces one, with the symptom you will actually see.「不一致」是個模糊的詞,所以這裡列出本講材料會造成不一致的四種具體情況,連同你實際會看到的症狀。「不一致」は曖昧な言葉なので、本講義の題材がそれを生む四つの具体的な経路を、実際に目にする症状とともに一覧にしておく。

Cause原因原因What RTL simulation showsRTL 模擬顯示什麼RTL シミュレーションが示すものWhat the netlist doesnetlist 實際怎麼跑ネットリストの実際の動作Symptom in the lab實驗室裡的症狀実験室で現れる症状
Blocking = in a clocked block, as in §2.5時脈同步區塊裡用了阻斷式 =,如 §2.5クロック同期ブロックでのブロッキング =(§2.5)Data races through all stages in one cycle資料在一個週期內衝過所有級データが 1 サイクルで全段を駆け抜けるTool infers fewer flip-flops; latency changes工具推斷出較少的正反器;延遲級數改變ツールが推論するフリップフロップが減り、レイテンシが変わるGate-level sim and RTL sim differ by N−1 cycles; a pipeline "works" in RTL and loses its alignment after synthesis閘級模擬與 RTL 模擬相差 N−1 個週期;管線在 RTL 下「會動」,合成後對不齊ゲートレベルシミュレーションと RTL シミュレーションが N−1 サイクルずれる。パイプラインが RTL では「動く」のに、合成後に整合が崩れる
Non-blocking <= in a combinational block, as in §2.6組合邏輯區塊裡用了非阻斷式 <=,如 §2.6組合せ論理ブロックでのノンブロッキング <=(§2.6)Extra evaluation passes; glitchy intermediate values; stale outputs if the sensitivity list is explicit多出額外的求值回合;中間值出現毛刺;若敏感度列表是明確寫出的則輸出為舊值余分な評価パスが発生し、中間値にグリッチが出る。感度リストが明示なら出力が古いままになるCorrect combinational cone正確的組合邏輯錐正しい組合せ論理コーンRTL sim fails, gate-level sim passes — people waste a day debugging the netlistRTL 模擬失敗、閘級模擬通過 —— 大家浪費一整天去除錯 netlistRTL シミュレーションが失敗し、ゲートレベルは通る——ネットリストのデバッグに丸一日を費やすことになる
Incomplete sensitivity list敏感度列表不完整感度リストが不完全Output does not update when a missing input changes漏掉的輸入改變時輸出不更新書き落とした入力が変化しても出力が更新されないTool builds the full cone from the whole body and warns工具從整個本體建出完整的邏輯錐,並發出警告ツールは本体全体から完全なコーンを作り、警告を出すRTL output stale; netlist correct. always @* removes the cause entirelyRTL 輸出是舊值;netlist 正確。改用 always @* 可以把病根完全拔掉RTL の出力が古いままで、ネットリストは正しい。always @* にすれば原因そのものがなくなる
Incomplete case/if in a combinational block (§7.2, §7.4)組合邏輯區塊裡的 case/if 不完整(§7.2、§7.4)組合せ論理ブロック内の不完全な case/if(§7.2、§7.4)Variable keeps its old value, which looks plausible變數保留舊值,而這看起來很合理変数が古い値を保持し、それがもっともらしく見えるTool infers a transparent latch to hold it工具推斷出一個透明閂鎖來保存它ツールがそれを保持する透過ラッチを推論するNetlist has latches that static timing analysis cannot close; a synthesis report full of "inferred latch" warningsnetlist 裡出現靜態時序分析收斂不了的閂鎖;合成報告裡滿是「inferred latch」警告静的タイミング解析が収束できないラッチがネットリストに現れ、合成レポートが「inferred latch」の警告であふれる

Notice the asymmetry. A sensitivity-list bug or an NBA-in-combinational bug makes the simulation pessimistic and the netlist correct. A blocking-in-clocked bug or a latch inference makes the simulation optimistic and the netlist wrong. The second kind is far more dangerous, because your test bench passes.注意這裡的不對稱。敏感度列表的錯、或組合邏輯裡用 NBA 的錯,會讓模擬偏悲觀而 netlist 正確。時脈同步區塊用阻斷式的錯、或推斷出閂鎖,則會讓模擬偏樂觀而 netlist 錯誤。第二種危險得多,因為你的測試平台會通過。ここにある非対称性に注目したい。感度リストのバグや組合せ論理での NBA のバグは、シミュレーションを悲観的にし、ネットリストを正しくする。クロック同期ブロックでのブロッキングのバグやラッチ推論は、シミュレーションを楽観的にし、ネットリストを誤らせる。後者のほうがはるかに危険である。テストベンチが通ってしまうからだ。

2.8 The merged four-flavour example2.8 四種寫法合併的範例2.8 四種類を併記した例

Slides 9 and 10 of Part 1 put all four combinations of (blocking / non-blocking) × (delay before the statement / delay inside the assignment) into one module. It is the single best exam question in the lecture, so work through it rather than memorising the answer.第 1 部的第 9、10 張投影片把(阻斷式/非阻斷式)×(延遲放在敘述之前/延遲放在指派內部)這四種組合全部塞進一個模組。這是整個講次裡最好的一道考題,所以請把它推一遍,不要背答案。パート 1 のスライド 9 と 10 は、(ブロッキング/ノンブロッキング)×(文の前に遅延/代入の内部に遅延)の四つの組合せをすべて一つのモジュールに詰め込んでいる。この講義で最良の試験問題なので、答えを暗記せずに自分で追ってほしい。

module blocking_nonblocking();
reg a, b, c, d;

initial begin            // (A) blocking, inter-assignment delay
  #10 a = 0;
  #11 a = 1;
  #12 a = 0;
  #13 a = 1;
end

initial begin            // (B) non-blocking, inter-assignment delay
  #10 b <= 0;
  #11 b <= 1;
  #12 b <= 0;
  #13 b <= 1;
end

initial begin            // (C) blocking, intra-assignment delay
  c = #10 0;
  c = #11 1;
  c = #12 0;
  c = #13 1;
end

initial begin            // (D) non-blocking, intra-assignment delay
  d <= #10 0;
  d <= #11 1;
  d <= #12 0;
  d <= #13 1;
end

initial begin
  $monitor("TIME = %d A = %b B = %b C = %b D = %b", $time, a, b, c, d);
  #50 $finish;
end
endmodule

The key question for each block is: does the thread wait?每個區塊的關鍵問題只有一個:執行緒會不會等?各ブロックについての鍵となる問いは一つだけである:スレッドは待つのか。

TIME =  0 A = x B = x C = x D = x
TIME = 10 A = 0 B = 0 C = 0 D = 0
TIME = 11 A = 0 B = 0 C = 0 D = 1
TIME = 12 A = 0 B = 0 C = 0 D = 0
TIME = 13 A = 0 B = 0 C = 0 D = 1
TIME = 21 A = 1 B = 1 C = 1 D = 1
TIME = 33 A = 0 B = 0 C = 0 D = 1
TIME = 46 A = 1 B = 1 C = 1 D = 1
abcd 0 10 21 33 46 t
The merged example. Signals a, b and c are indistinguishable; only d, whose four updates were all scheduled at t=0, produces the 0-1-0-1 burst at t = 10, 11, 12, 13.合併範例。訊號 a、b、c 無從區分;只有 d,因為它的四次更新全都在 t=0 排好,才在 t = 10、11、12、13 產生那串 0-1-0-1 的脈衝。併記した例。信号 a、b、c は区別がつかない。四回の更新がすべて t=0 で予約された d だけが、t = 10、11、12、13 に 0-1-0-1 のバーストを生む。
Why the $monitor line prints what it prints為什麼 $monitor 印出來的是那些行$monitor がああいう行を印字する理由

There is no line for t = 46 onwards because #50 $finish; ends the run at t = 50, and no line between 13 and 21 because nothing changes there. Note also that the t = 0 line appears even though nothing has been assigned: $monitor prints once when it is armed, then again at the end of any time slot in which one of its arguments changed. Section 6 covers this.t = 46 以後沒有任何一行,是因為 #50 $finish; 在 t = 50 結束這次執行;而 13 與 21 之間沒有行,是因為那段期間沒有東西改變。另外要注意,即使還沒指派任何東西,t = 0 那一行仍然會出現:$monitor 在被啟動時先印一次,之後在任何一個其引數有改變的時槽結尾再印一次。第 6 節會談這件事。t = 46 以降に行がないのは、#50 $finish; が t = 50 で実行を終えるからであり、13 と 21 の間に行がないのは、その間に何も変化しないからである。また、まだ何も代入されていないのに t = 0 の行が現れる点にも注意したい:$monitor は起動された時点で一度印字し、その後は引数のいずれかが変化したタイムスロットの終わりごとに印字する。これは第 6 節で扱う。

Practice 2 — Register swap練習 2 — 暫存器交換練習 2 — レジスタのスワップ

Problem: Inside always @(posedge clk), which of the following swap x and y every clock? (a) x = y; y = x; (b) x <= y; y <= x; (c) tmp = x; x = y; y = tmp; (d) y <= x; x = y;題目:在 always @(posedge clk) 裡,下列哪幾個能在每個時脈交換 x 與 y?(a) x = y; y = x; (b) x <= y; y <= x; (c) tmp = x; x = y; y = tmp; (d) y <= x; x = y;問題:always @(posedge clk) の中で、クロックごとに x と y を入れ替えるのは次のどれか。(a) x = y; y = x; (b) x <= y; y <= x; (c) tmp = x; x = y; y = tmp; (d) y <= x; x = y;

Click to reveal solution點擊展開解答クリックで解答を表示

(a) No. The first statement destroys x; both end up holding the old y.(a) 不行。第一道敘述毀掉 x;兩者最後都持有舊的 y。(a) 不可。最初の文が x を破壊し、両方とも古い y を保持して終わる。

(b) Yes. Both RHS are read in the Active region, both writes land in the NBA region. This is the only version you should ever write.(b) 可以。兩個右手邊都在 Active 區域被讀,兩次寫都落在 NBA 區域。這是你唯一該寫的版本。(b) 可。両方の右辺が Active 領域で読まれ、両方の書き込みが NBA 領域に落ちる。書くべきはこの版だけである。

(c) Yes in simulation. The temporary preserves the old x. Synthesis gives the same two flip-flops — tmp is written and read within one evaluation, so it becomes a wire and no third flop appears. It is correct but verbose.(c) 在模擬裡可以。那個暫時變數保住了舊的 x。合成得到的也是同樣兩個正反器 —— tmp 在一次求值之內被寫又被讀,所以它變成一條線,不會跑出第三個正反器。這樣寫是對的,只是囉嗦。(c) シミュレーション上は可。一時変数が古い x を保つ。合成結果も同じフリップフロップ二つである——tmp は一回の評価の中で書かれて読まれるので配線になり、三つ目のフリップフロップは現れない。正しいが冗長である。

(d) Numerically yes, by accident. y <= x stashes the old x; x = y then reads the old y because the NBA update of y has not fired. But mixing the operators in one block is exactly the pattern the tools warn about, and a tool that reorders the statements (or a reader who does) will get a different answer. Use (b).(d) 數值上可以,但純屬僥倖。y <= x 把舊的 x 藏起來;接著 x = y 讀到的是舊的 y,因為 y 的 NBA 更新還沒發動。但在一個區塊裡混用兩個運算子,正是工具會警告的那種寫法,而一個會重排敘述的工具(或一個這樣讀程式碼的人)會得到不同的答案。請用 (b)。(d) 数値的には可だが、たまたまである。y <= x が古い x を退避し、続く x = y は y の NBA 更新がまだ発火していないので古い y を読む。しかし一つのブロックで演算子を混ぜるのは、まさにツールが警告する書き方であり、文を並べ替えるツール(あるいはそう読む人間)は別の答えに至る。(b) を使うこと。

Practice 3 — Three-stage trace練習 3 — 三級追蹤練習 3 — 三段のトレース

Problem: Before the rising edge, d=1, q1=0, q2=1, q3=0. List, in order, the Active events and the NBA events of that clock edge for the block q1 <= d; q2 <= q1; q3 <= q2;, then give the post-edge values. Repeat for the same three statements written with =.題目:上升緣之前,d=1、q1=0、q2=1、q3=0。對於區塊 q1 <= d; q2 <= q1; q3 <= q2;,依序列出該時脈邊緣的 Active 事件與 NBA 事件,然後給出邊緣之後的值。再把同樣三道敘述改寫成 =,重做一次。問題:立ち上がりエッジの前で d=1、q1=0、q2=1、q3=0 とする。ブロック q1 <= d; q2 <= q1; q3 <= q2; について、そのクロックエッジでの Active イベントと NBA イベントを順に挙げ、エッジ後の値を示せ。同じ三文を = で書いた場合についても同様に行え。

Click to reveal solution點擊展開解答クリックで解答を表示

Non-blocking. Active: evaluate d→1 (stash for q1); evaluate q1→0 (stash for q2); evaluate q2→1 (stash for q3). NBA: q1←1, q2←0, q3←1. Post-edge: q1=1, q2=0, q3=1. Every value moved exactly one stage to the right.非阻斷式。Active:求 d→1(替 q1 藏起來);求 q1→0(替 q2 藏起來);求 q2→1(替 q3 藏起來)。NBA:q1←1、q2←0、q3←1。邊緣後:q1=1、q2=0、q3=1。每個值都剛好向右移動一級。ノンブロッキング。Active:d→1 を評価(q1 用に退避)、q1→0 を評価(q2 用に退避)、q2→1 を評価(q3 用に退避)。NBA:q1←1、q2←0、q3←1。エッジ後:q1=1、q2=0、q3=1。すべての値がちょうど一段右へ移った。

Blocking. Active 1: q1←1 (now q1=1). Active 2: read q1→1, q2←1. Active 3: read q2→1, q3←1. Post-edge: q1=q2=q3=1. The old contents of q1 and q2 vanished; d reached the far end in one edge.阻斷式。Active 1:q1←1(此刻 q1=1)。Active 2:讀 q1→1,q2←1。Active 3:讀 q2→1,q3←1。邊緣後:q1=q2=q3=1。q1 與 q2 原本的內容消失了;d 在一個邊緣就跑到了最末端。ブロッキング。Active 1:q1←1(これで q1=1)。Active 2:q1→1 を読み、q2←1。Active 3:q2→1 を読み、q3←1。エッジ後:q1=q2=q3=1。q1 と q2 の元の中身は消え、d は一エッジで最後尾まで到達した。

Note that the blocking version also has no NBA events at all, so $display placed after the three statements would print the new values in the blocking version and the old values in the non-blocking version.另外要注意,阻斷式版本根本沒有任何 NBA 事件,所以放在三道敘述之後的 $display,在阻斷式版本會印出新值,在非阻斷式版本會印出舊值。なお、ブロッキング版には NBA イベントがまったく存在しない。したがって三文の後ろに置いた $display は、ブロッキング版では新しい値を、ノンブロッキング版では古い値を印字する。

Practice 4 — Repair the mismatch練習 4 — 修好不一致練習 4 — 不一致を直す

Problem: A colleague's module simulates correctly in RTL but the gate-level netlist produces an output one cycle early, and the synthesis log contains two "register removed" messages. The code is below. Diagnose it and fix it without changing the intended function (a 2-cycle delayed, registered AND).題目:某同事的模組在 RTL 下模擬正確,但閘級 netlist 的輸出早了一個週期,而合成記錄裡出現兩則「register removed」訊息。程式碼如下。請診斷並修好它,同時不改變原本想要的功能(一個延遲 2 個週期、有暫存的 AND)。問題:ある同僚のモジュールは RTL では正しくシミュレートされるのに、ゲートレベルのネットリストでは出力が 1 サイクル早く出て、合成ログには「register removed」が二件出ている。コードは以下のとおりである。意図した機能(2 サイクル遅延のレジスタ付き AND)を変えずに診断し、修正せよ。

always @(posedge clk) begin
  stage1 = a & b;
  stage2 = stage1;
  y      = stage2;
end
Click to reveal solution點擊展開解答クリックで解答を表示

Diagnosis. All three assignments are blocking inside a clocked block, so each statement reads the value the previous statement just wrote. In simulation a & b propagates to y within the same edge, giving a latency of one cycle rather than three. Synthesis reaches the same conclusion structurally — stage1 and stage2 are written and read within a single evaluation, so they are combinational nets, not registers — and removes the two flip-flops. The "one cycle early" report is the RTL and gate views finally agreeing on a circuit that is not what was wanted.診斷。三個指派在一個時脈同步區塊裡全是阻斷式的,所以每道敘述都讀到前一道敘述剛寫進去的值。在模擬裡 a & b 在同一個邊緣之內就傳到了 y,延遲級數變成一個週期而不是三個。合成從結構上得到同樣的結論 —— stage1 與 stage2 在單一次求值之內被寫又被讀,所以它們是組合邏輯的 net,不是暫存器 —— 於是把那兩個正反器移除。所謂「早了一個週期」的報告,其實是 RTL 視角與閘級視角終於對同一個「不是當初想要的」電路取得了一致。診断。三つの代入がクロック同期ブロックの中ですべてブロッキングなので、各文は直前の文がいま書いた値を読む。シミュレーションでは a & b が同じエッジ内で y まで伝わり、レイテンシは三サイクルではなく一サイクルになる。合成も構造的に同じ結論に達する——stage1 と stage2 は一回の評価の中で書かれて読まれるので、レジスタではなく組合せ論理のネットである——ので、フリップフロップを二つ取り除く。「1 サイクル早い」という報告は、RTL とゲートの両視点が、意図とは違う回路についてついに一致したということである。

Fix. Use non-blocking assignment throughout:修法。全部改用非阻斷式指派:修正。すべてノンブロッキング代入に改める:

always @(posedge clk) begin
  stage1 <= a & b;
  stage2 <= stage1;
  y      <= stage2;
end

Now each RHS reads the pre-edge value, three flip-flops are inferred, and y shows a & b three edges later. If a two-cycle latency was wanted, delete the stage2 line and assign y <= stage1; — do not try to fix the latency by mixing operators.現在每個右手邊都讀到邊緣之前的值,會推斷出三個正反器,而 y 在三個邊緣之後顯示 a & b。如果原本想要的是兩個週期的延遲,就刪掉 stage2 那一行並改成 y <= stage1; —— 不要靠混用運算子來調整延遲級數。これで各右辺はエッジ前の値を読み、フリップフロップが三つ推論され、y は三エッジ後に a & b を示す。2 サイクル遅延が望みであれば stage2 の行を削って y <= stage1; とすること。演算子を混ぜてレイテンシを調整しようとしてはならない。

3. Procedural Continuous Assignment3. 程序性連續指派3. 手続き型連続代入

3.1 Why the construct exists at all3.1 這個構造究竟為什麼存在3.1 そもそもこの構文が存在する理由

Section 1.4 established a clean division: continuous assignments drive nets and live at module level; procedural assignments write variables and live inside behaviors. Procedural continuous assignment (PCA) deliberately breaks that division. A PCA is executed like a procedural statement — it sits inside an always or initial block and runs when the thread reaches it — but once executed it behaves like a continuous assignment: it installs a permanent binding so the target keeps tracking the right-hand expression even when the procedural block is long since suspended.1.4 節定出了一條乾淨的分界:連續指派驅動 net,住在模組層級;程序性指派寫變數,住在行為描述內部。程序性連續指派(PCA)刻意打破這條分界。PCA 的執行方式像一道程序性敘述 —— 它待在 always 或 initial 區塊裡,執行緒走到它才跑 —— 但一旦執行,它的行為就像連續指派:它安裝了一個永久的繫結,使得目標即使在程序區塊早就暫停之後,仍然持續追蹤右手邊的運算式。1.4 節は明快な区分を立てた:連続代入はネットを駆動し、モジュールレベルに置かれる。手続き型代入は変数に書き込み、振る舞いの内部に置かれる。手続き型連続代入(PCA)はこの区分を意図的に壊す。PCA は手続き型文のように実行される——always や initial ブロックの中にあり、スレッドがそこに達したときに走る——が、いったん実行されると連続代入のように振る舞う:恒久的な結び付きを設置し、手続き型ブロックがとうに中断した後も、対象が右辺の式を追い続けるようにする。

The motivation is asynchronous override. Think of a flip-flop with an asynchronous clear. While clear is low, the output must be 0 continuously, independent of the clock, and must ignore anything the clocked behavior tries to do. When clear returns high, the clocked behavior must take back control. That is an override with a beginning and an end, which is awkward to express with ordinary procedural assignments and natural to express with assign ... deassign. Verilog provides two flavours of PCA and they differ in what they can target and in how hard they push.動機是非同步覆寫。想想一個帶非同步清除的正反器。當 clear 為低時,輸出必須持續為 0,與時脈無關,而且必須無視時脈同步行為想做的任何事。當 clear 回到高時,時脈同步行為必須重新取回控制權。那是一個有起點也有終點的覆寫,用普通的程序性指派很難表達,用 assign ... deassign 表達卻很自然。Verilog 提供兩種 PCA,差別在於它們能鎖定什麼目標,以及推得多用力。動機は非同期オーバライドである。非同期クリア付きのフリップフロップを考えてほしい。clear が Low の間、出力はクロックとは無関係に継続的に 0 でなければならず、クロック同期の振る舞いが何をしようと無視しなければならない。clear が High に戻れば、クロック同期の振る舞いが制御を取り戻さねばならない。これは始まりと終わりのあるオーバライドであり、普通の手続き型代入では表しにくく、assign ... deassign では自然に表せる。Verilog は PCA を二種類用意しており、対象にできるものと押しの強さが違う。

3.2 Method 1: assign ... deassign3.2 方法一:assign ... deassign3.2 方法 1:assign ... deassign

The rules from slide 11 of Part 1, with the reasoning attached:以下是第 1 部第 11 張投影片上的規則,並附上理由:以下はパート 1 のスライド 11 にある規則に、理由を添えたものである。

Example 1 — a multiplexer written with PCA範例 1 — 用 PCA 寫出來的多工器例 1 — PCA で書いたマルチプレクサ

module mux4_PCA (a, b, c, d, select, y_out);
  input       a, b, c, d;
  input [1:0] select;
  output      y_out;
  reg         y_out;

  always @ (select) begin              // Caution!!
    if      (select == 0) assign y_out = a;
    else if (select == 1) assign y_out = b;
    else if (select == 2) assign y_out = c;
    else if (select == 3) assign y_out = d;
    else                  assign y_out = 1'b0;
  end
endmodule
Why the slide puts "Caution!!" next to the sensitivity list投影片為什麼在敏感度列表旁邊標「Caution!!」スライドが感度リストの横に「Caution!!」と書いている理由

The list contains only select, yet the model is correct: when a changes while select is 0, y_out follows. That is because the assign y_out = a; binding is continuous — the block does not have to re-run for the data to propagate. The block only needs to re-run when the choice of source changes, and that depends on select alone.列表裡只有 select,但這個模型是對的:當 select 為 0 而 a 改變時,y_out 會跟著變。原因是 assign y_out = a; 這個繫結是連續的 —— 資料要傳過去,區塊根本不必重跑。區塊只有在來源的選擇改變時才需要重跑,而那只取決於 select。リストには select しかないが、このモデルは正しい:select が 0 のときに a が変化すれば y_out は追従する。assign y_out = a; という結び付きが連続的だからであり、データが伝わるのにブロックが再実行される必要はない。ブロックが再実行される必要があるのはソースの選択が変わるときだけで、それは select だけに依存する。

Now delete the five assign keywords, leaving plain procedural assignments. The sensitivity list becomes wrong: y_out only updates on a select change, and a change on a is ignored until the next select transition. Synthesis builds a mux regardless, so you have manufactured a simulation/synthesis mismatch of the "incomplete sensitivity list" kind from §2.7. The lesson is that the correctness of a sensitivity list cannot be judged without knowing whether the assignments inside are procedural or continuous.現在把那五個 assign 關鍵字刪掉,只留下單純的程序性指派。敏感度列表立刻變成錯的:y_out 只會在 select 改變時更新,而 a 的變化會被忽略,直到下一次 select 轉態為止。合成無論如何都會做出一個多工器,於是你就自製了一個 §2.7 裡「敏感度列表不完整」那種模擬/合成不一致。這裡的教訓是:不知道裡面的指派是程序性還是連續性,就無法判斷一份敏感度列表正不正確。ここで五つの assign キーワードを削り、ただの手続き型代入にしてみる。感度リストはたちまち誤りになる:y_out は select の変化でしか更新されず、a の変化は次の select 遷移まで無視される。合成はどのみちマルチプレクサを作るので、§2.7 の「感度リストが不完全」型のシミュレーション/合成不一致を自分で作り出したことになる。教訓は、中の代入が手続き型か連続型かを知らずに感度リストの正しさは判断できない、ということである。

Example 2 — flip-flop with asynchronous clear and preset Non-synthesized範例 2 — 帶非同步清除與預設的正反器 不可合成例 2 — 非同期クリアとプリセット付きフリップフロップ 合成不可

module Flop_PCA (preset, clear, q, qbar, clock, data);
  input  preset, clear, clock, data;
  output q, qbar;
  reg    q;

  assign qbar = ~q;                   // ordinary continuous assignment on a net

  always @ (negedge clock)
    q = data;                         // ordinary procedural assignment

  always @ (clear or preset)
    begin
      if      (!clear)  assign q = 0; // PCA: clear dominates
      else if (!preset) assign q = 1; // PCA: preset
      else              deassign q;   // clock regains control
    end
endmodule

Walk through a sequence to see the override in action. Suppose clear and preset are both high and the clock is running; q follows data on every falling edge. Now clear goes low at t=100. The second block wakes, takes the first branch and installs assign q = 0. From t=100 onwards q is 0 and stays 0 — the falling edges still wake the first block, it still executes q = data, and the PCA binding discards the write. At t=200 clear returns high, the second block wakes again, both conditions are false, and deassign q removes the binding. q is now 0 and remains 0 until the next falling clock edge, which is the correct behaviour for a flip-flop coming out of clear.走一段時序,就看得到覆寫在運作。假設 clear 與 preset 都是高電位、時脈在跑;q 在每個下降緣跟隨 data。現在 clear 在 t=100 變低。第二個區塊醒來,走第一個分支,安裝 assign q = 0。從 t=100 起 q 就是 0 而且維持 0 —— 下降緣照樣喚醒第一個區塊,它照樣執行 q = data,而 PCA 繫結把那次寫入丟掉。到 t=200 時 clear 回到高,第二個區塊再度醒來,兩個條件都不成立,於是 deassign q 移除繫結。此刻 q 是 0,而且維持 0 直到下一個時脈下降緣 —— 這正是正反器脫離清除狀態後該有的行為。一連の時刻を追えばオーバライドの働きが見える。clear と preset がともに High でクロックが走っているとする。q は立ち下がりエッジごとに data に追従する。ここで t=100 に clear が Low になる。二つ目のブロックが起きて最初の分岐を取り、assign q = 0 を設置する。t=100 以降 q は 0 であり続ける——立ち下がりエッジは相変わらず一つ目のブロックを起こし、それは相変わらず q = data を実行するが、PCA の結び付きがその書き込みを捨てる。t=200 で clear が High に戻ると二つ目のブロックが再び起き、どちらの条件も成り立たないので deassign q が結び付きを外す。このとき q は 0 であり、次のクロック立ち下がりエッジまで 0 のままである。これはクリアから抜けたフリップフロップの正しい振る舞いである。

Note that qbar is a net driven by an ordinary continuous assignment and is unaffected by any of this — it simply tracks ~q throughout. Note too the priority: clear is tested first, so simultaneous assertion of clear and preset resolves to 0. Real cells generally make that combination illegal rather than giving it a defined answer, which is one reason this model is labelled non-synthesisable.注意 qbar 是一個由普通連續指派驅動的 net,完全不受這一切影響 —— 它自始至終就只是追蹤 ~q。也要注意優先順序:clear 先被測試,所以 clear 與 preset 同時成立時結果是 0。真實的單元通常會把這個組合定為不合法,而不是給它一個有定義的答案,這也是這個模型被標為不可合成的原因之一。qbar は通常の連続代入で駆動されるネットであり、以上のいずれの影響も受けない——終始 ~q を追うだけである。優先順位にも注意したい:clear が先に判定されるので、clear と preset が同時にアサートされた場合の結果は 0 になる。実際のセルはこの組合せに定義された答えを与えるのではなく違法とすることが多く、それがこのモデルが合成不可と記される理由の一つである。

3.3 Method 2: force ... release3.3 方法二:force ... release3.3 方法 2:force ... release

force is the heavier hammer, and the rules from slide 14 differ from assign in exactly the ways that make it a debugging tool rather than a modelling construct:force 是比較重的槌子,而第 14 張投影片上的規則與 assign 的差異,剛好就是讓它成為除錯工具而非建模構造的那幾點:force はより重いハンマーであり、スライド 14 の規則が assign と異なる点は、まさにこれをモデリング構文ではなくデバッグ道具にしている点である。

...
force sig_a = 1;            // internal nodes of the DUT, reached hierarchically
force sig_b = 1;
force sig_c = 0;
sig_in1 = 0;
#5 sig_in1 = 1;
#5 sig_in1 = 0;
// insert code to construct tests
release sig_a;
release sig_b;
release sig_c;
...

The figure on slide 15 explains the use case. Inside the test bench, mod_abc produces sig_a and sig_b, mod_xyz produces sig_c and sig_d, and those feed an AND and an OR gate whose outputs sig_f and sig_g drive mod_def. If you want to test the AND/OR glue and mod_def without first solving the much harder problem of coaxing mod_abc and mod_xyz into producing the pattern you need, you force the three internal nodes directly. Combined with hierarchical names — force top.dut.u1.sig_a = 1; — this lets a test bench reach any node in the design, which is both enormously useful and enormously easy to abuse.第 15 張投影片的圖說明了使用情境。在測試平台裡,mod_abc 產生 sig_a 與 sig_b,mod_xyz 產生 sig_c 與 sig_d,它們餵進一個 AND 與一個 OR 閘,其輸出 sig_f 與 sig_g 再驅動 mod_def。如果你想測 AND/OR 這段黏合邏輯與 mod_def,卻不想先去解「怎麼哄 mod_abc 與 mod_xyz 吐出你要的樣式」這個難得多的問題,你就直接 force 那三個內部節點。再配上階層式名稱 —— force top.dut.u1.sig_a = 1; —— 測試平台就能伸手摸到設計裡任何一個節點,既極其好用,也極其容易被濫用。スライド 15 の図が使いどころを説明している。テストベンチの中で mod_abc が sig_a と sig_b を、mod_xyz が sig_c と sig_d を生み、それらが AND ゲートと OR ゲートに入り、その出力 sig_f と sig_g が mod_def を駆動する。mod_abc と mod_xyz に望みのパターンを吐かせるというはるかに難しい問題を先に解かずに、AND/OR のグルー論理と mod_def を試験したければ、その三つの内部ノードを直接 force すればよい。階層名——force top.dut.u1.sig_a = 1;——と組み合わせれば、テストベンチは設計中のどのノードにも手を伸ばせる。きわめて便利であると同時に、きわめて濫用しやすい。

Three things that go wrong with forceforce 會出錯的三件事force でうまくいかなくなる三つのこと
  • Forgetting the release. The node stays stuck for the rest of the run and every later test silently fails in a way that looks like a design bug.忘了寫 release。那個節點在整段執行的其餘時間都卡死,而之後每一個測試都會以看起來像設計臭蟲的方式默默失敗。release の書き忘れ。そのノードは残りの実行中ずっと固定され、以降のテストはすべて、設計のバグのように見える形で黙って失敗する。
  • Forcing a register and expecting it to revert. It does not. If you need it to go back to a known value, write it explicitly after the release.force 一個暫存器,然後期待它會回復。它不會。如果你需要它回到某個已知值,就在 release 之後明確地寫它。レジスタを force して、元に戻ることを期待する。戻らない。既知の値に戻す必要があるなら、release の後で明示的に書き込むこと。
  • Leaving a force in code that is handed to synthesis. Most tools error out; the ones that do not will ignore it, which is worse, because the node you were forcing is now free and the gate-level behaviour differs from the RTL behaviour you verified.把帶著 force 的程式碼交給合成。多數工具會直接報錯;不報錯的那些會忽略它,而那更糟,因為你原本在強制的那個節點現在自由了,閘級行為就與你驗證過的 RTL 行為不同。force の残ったコードを合成に渡す。ほとんどのツールはエラーで止まる。止まらないツールは無視するが、そのほうが悪い。force していたノードが自由になり、ゲートレベルの振る舞いが検証済みの RTL の振る舞いと食い違うからである。

3.4 The priority ladder3.4 優先權階梯3.4 優先順位のはしご

Putting §3.2 and §3.3 together gives a strict precedence order for a reg that is under attack from several directions at once. Highest wins:把 §3.2 與 §3.3 合起來,就得到一個 reg 同時被好幾個方向攻擊時的嚴格優先順序。最高者勝:§3.2 と §3.3 を合わせると、複数の方向から同時に攻められている reg についての厳密な優先順位が得られる。上位が勝つ。

Precedence on a register暫存器上的優先權レジスタ上の優先順位 $$\texttt{force} \;\succ\; \texttt{assign (PCA)} \;\succ\; \text{ordinary procedural assignment}$$

And on a net:而在 net 上:ネット上では:

Precedence on a netnet 上的優先權ネット上の優先順位 $$\texttt{force} \;\succ\; \text{continuous assignment and primitive drivers (resolved among themselves)}$$

The second line is worth a comment. Ordinary net drivers do not override each other — a net with two drivers takes the resolution of the two values, so 0 and 1 together give x on a wire, and a wand/wor/tri resolves differently again (Lecture 3). force does not join that resolution; it bypasses it.第二行值得加個註解。普通的 net 驅動者之間不會互相覆寫 —— 一條有兩個驅動者的 net 取的是兩個值的解析結果,所以 0 與 1 在 wire 上合起來是 x,而 wand/wor/tri 又各自有不同的解析方式(第 3 講)。force 不參加那個解析;它直接繞過去。二行目には註釈をつけておく価値がある。通常のネット駆動源どうしは互いを上書きしない——駆動源が二つあるネットは二つの値の解決結果を取るので、wire 上では 0 と 1 が合わさって x になり、wand/wor/tri はまた別の解決をする(第 3 講)。force はその解決に加わらず、迂回する。

3.5 The assignment-mode table3.5 指派模式對照表3.5 代入モード一覧表

Variable type變數型別変数の型Output of primitive基本元件的輸出プリミティブの出力Continuous assignment連續指派連続代入Procedural assignment
(always / initial)
程序性指派
(always / initial)
手続き型代入
(always / initial)
assign … deassign
(PCA)
assign … deassign
(PCA)
assign … deassign
(PCA)
force … release
(PCA)
force … release
(PCA)
force … release
(PCA)
NetNetネットYes可以可Yes可以可No不可以不可No不可以不可Yes可以可
Register暫存器レジスタComb — No
Seq (UDP) — Yes
組合 — 不可以
循序 (UDP) — 可以
組合せ — 不可
順序 (UDP) — 可
No不可以不可Yes可以可Yes可以可Yes可以可

Five of the ten cells follow directly from §1.4. The sixth needs a note: the "Comb — No / Seq — Yes" entry refers to user-defined primitives from Lecture 3. A combinational UDP output must be declared output and is a net. A sequential UDP output must additionally be declared reg, because a sequential UDP has internal state that must be held between table lookups. That is the only situation in which a primitive's output is a register, which is why the cell reads the way it does.十格裡有五格直接由 §1.4 推出。第六格需要加註:「組合 — 不可以/循序 — 可以」那一格講的是第 3 講的使用者自定義基本元件。組合型 UDP 的輸出必須宣告為 output,它是一條 net。循序型 UDP 的輸出則必須額外宣告為 reg,因為循序 UDP 有內部狀態要在兩次查表之間保持住。那是基本元件的輸出唯一會是暫存器的情況,這也就是那一格會那樣寫的原因。十個のセルのうち五個は §1.4 から直接導かれる。六個目には註が要る:「組合せ — 不可/順序 — 可」のセルは第 3 講のユーザ定義プリミティブを指している。組合せ UDP の出力は output と宣言せねばならず、ネットである。順序 UDP の出力はさらに reg と宣言せねばならない。順序 UDP は表引きと表引きの間で保持すべき内部状態を持つからである。プリミティブの出力がレジスタになるのはこの場合だけであり、だからこのセルはこう書かれている。

Practice 5 — PCA precedence練習 5 — PCA 優先權練習 5 — PCA の優先順位

Problem: At t = 0, r is a reg initialised to 0. The following statements execute in a single initial block. Give the value of r immediately after each line.題目:在 t = 0 時,r 是一個初始化為 0 的 reg。下列敘述在同一個 initial 區塊裡執行。請給出每一行執行完畢後 r 的值。問題:t = 0 の時点で r は 0 に初期化された reg である。次の文は一つの initial ブロックの中で実行される。各行の直後の r の値を示せ。

#1  r = 1;
#1  assign r = 0;
#1  r = 1;
#1  force r = 1;
#1  deassign r;
#1  r = 0;
#1  release r;
#1  r = 0;
Click to reveal solution點擊展開解答クリックで解答を表示
t=1   r = 1 → r = 1. Ordinary write, nothing in the way.t=1   r = 1 → r = 1。普通的寫入,前面沒有東西擋著。t=1   r = 1 → r = 1。通常の書き込みで、邪魔するものはない。
t=2   assign r = 0 → r = 0. PCA binding installed; r now tracks the constant 0.t=2   assign r = 0 → r = 0。PCA 繫結裝上了;r 現在追蹤常數 0。t=2   assign r = 0 → r = 0。PCA の結び付きが設置され、r は定数 0 を追うようになる。
t=3   r = 1 → r = 0. The procedural write executes and is discarded: PCA outranks it.t=3   r = 1 → r = 0。程序性寫入執行了,然後被丟掉:PCA 的位階比它高。t=3   r = 1 → r = 0。手続き型の書き込みは実行されるが捨てられる。PCA のほうが格上である。
t=4   force r = 1 → r = 1. force outranks the PCA.t=4   force r = 1 → r = 1。force 的位階比 PCA 高。t=4   force r = 1 → r = 1。force は PCA より格上である。
t=5   deassign r → r = 1. The PCA binding is removed, but force is still active, so nothing visible changes.t=5   deassign r → r = 1。PCA 繫結被移除,但 force 仍然作用中,所以看得見的部分沒有任何改變。t=5   deassign r → r = 1。PCA の結び付きは外れるが force はまだ有効なので、見える変化は何もない。
t=6   r = 0 → r = 1. Still forced.t=6   r = 0 → r = 1。仍然被強制中。t=6   r = 0 → r = 1。まだ force されたままである。
t=7   release r → r = 1. A released register keeps its forced value; it does not revert. (A released net would snap back to its drivers.)t=7   release r → r = 1。被釋放的暫存器保留被強制的值;它不會回復。(被釋放的 net 則會彈回它的驅動者。)t=7   release r → r = 1。解放されたレジスタは強制された値を保ち、元には戻らない。(解放されたネットなら駆動源の値に跳ね戻る。)
t=8   r = 0 → r = 0. Finally free.t=8   r = 0 → r = 0。終於自由了。t=8   r = 0 → r = 0。ようやく自由になった。

The trap is t=7. Many people expect the release to restore the 0 written at t=6; that write was never performed, so there is nothing to restore.陷阱在 t=7。很多人以為 release 會把 t=6 寫入的 0 還原回來;那次寫入根本沒有被執行過,所以沒有東西可以還原。落とし穴は t=7 である。release が t=6 に書かれた 0 を復元すると思う人は多いが、その書き込みはそもそも実行されていないので、復元すべきものが存在しない。

4. Procedural Timing Controls4. 程序性時間控制4. 手続き型タイミング制御

A procedural thread runs until it hits something that suspends it. Verilog offers five such mechanisms — delay control #, event control @, the event or list, named events, and wait — and they divide cleanly into time-based (you know when it will resume) and event-based (you do not). Understanding which is which is the whole of this section.一個程序性執行緒會一直跑,直到撞上某個讓它暫停的東西。Verilog 提供五種這樣的機制 —— 延遲控制 #、事件控制 @、事件 or 列表、具名事件,以及 wait —— 它們乾淨地分成以時間為基準的(你知道它何時會恢復)與以事件為基準的(你不知道)。搞清楚哪個是哪個,就是這一節的全部。手続き型スレッドは、自分を中断させる何かにぶつかるまで走り続ける。Verilog はその仕組みを五つ用意している——遅延制御 #、イベント制御 @、イベント or リスト、名前付きイベント、そして wait——これらは時間基準(いつ再開するか分かる)とイベント基準(分からない)にきれいに分かれる。どれがどちらかを理解することが、この節のすべてである。

4.1 Delay control # Non-synthesized4.1 延遲控制 # 不可合成4.1 遅延制御 # 合成不可

#n suspends the thread at the point where the operator appears for exactly n time units, then resumes it. The unit is whatever the enclosing `timescale says it is — #5 under `timescale 1ns/1ps is five nanoseconds, under `timescale 10ps/1ps it is fifty picoseconds. Getting the timescale wrong changes every number in your waveform by a constant factor and is a standard lab-session time sink.#n 在這個運算子出現的位置把執行緒暫停剛好 n 個時間單位,然後讓它恢復。單位是外圍 `timescale 所規定的那個 —— 在 `timescale 1ns/1ps 底下 #5 是五奈秒,在 `timescale 10ps/1ps 底下則是五十皮秒。把 timescale 搞錯,會讓你波形上每一個數字都差一個固定倍數,這是實驗課裡標準的時間黑洞。#n は、この演算子が現れた位置でスレッドをちょうど n 時間単位だけ中断し、その後再開させる。単位は外側の `timescale が定めるものである——`timescale 1ns/1ps の下では #5 は 5 ナノ秒、`timescale 10ps/1ps の下では 50 ピコ秒になる。タイムスケールを取り違えると波形上のすべての数値が一定倍だけずれ、これは実験の時間を食いつぶす定番である。

Explicit high and low明確寫出高低電位High と Low を明示する
always
  begin
    #0  clock = 0;
    #50 clock = 1;
    #50;
  end           // period 100
Toggle反轉トグル
always
  begin
    #clock_period/2;
    clock = ~clock;
  end           // clock must be initialised!

The left-hand generator is self-starting but verbose. Trace it: at t=0 the #0 suspends and resumes within the same time slot (see §6.3), then clock = 0; at t=50 clock = 1; the trailing #50 carries the thread to t=100, where the always loops back and the whole sequence repeats. Period 100, duty cycle 50%.左邊那個產生器會自己啟動,但很囉嗦。追一遍:t=0 時 #0 在同一個時槽內暫停又恢復(見 §6.3),然後 clock = 0;t=50 時 clock = 1;結尾的 #50 把執行緒帶到 t=100,always 在那裡繞回去,整段序列重來。週期 100,工作週期 50%。左の生成器は自己起動するが冗長である。追ってみる:t=0 で #0 が同じタイムスロット内で中断と再開を行い(§6.3 参照)、続いて clock = 0。t=50 で clock = 1。末尾の #50 がスレッドを t=100 まで運び、そこで always がループし、全体が繰り返される。周期 100、デューティ比 50%。

The right-hand generator is the idiom you will actually use, and it has a trap: it inverts clock without ever initialising it. A reg starts at x, and ~x is x, so the clock never leaves the unknown state and every flip-flop in the design stays at x forever. The fix is one line — initial clock = 0; — and the symptom, an entire waveform of red x values with a clock that visibly "toggles" nowhere, is one you should learn to recognise on sight. A second trap: #clock_period/2 parses as (#clock_period)/2 in some readings and is in any case bad style; write #(clock_period/2).右邊那個產生器才是你實際會用的寫法,而它有個陷阱:它反轉 clock,卻從來沒有初始化它。reg 的起點是 x,而 ~x 還是 x,所以時脈永遠離不開未知狀態,設計裡每一個正反器也就永遠停在 x。修法只有一行 —— initial clock = 0; —— 而它的症狀,也就是整片紅色 x 值的波形搭配一個看得出來哪裡都沒「反轉」的時脈,你應該練到一眼就認得出來。第二個陷阱:#clock_period/2 在某些讀法下會解析成 (#clock_period)/2,而且無論如何都是壞風格;請寫 #(clock_period/2)。右の生成器が実際に使う書き方だが、落とし穴がある:clock を反転させるのに一度も初期化していないのである。reg の初期値は x であり、~x もまた x なので、クロックは未知状態から永遠に抜け出せず、設計中のすべてのフリップフロップも永遠に x にとどまる。直し方は一行——initial clock = 0;——であり、その症状、すなわち赤い x だらけの波形と、どこも「トグル」していないクロックの組合せは、一目で見分けられるようになっておくべきである。もう一つの落とし穴:#clock_period/2 は読み方によっては (#clock_period)/2 と解釈され、いずれにせよ悪い書き方である。#(clock_period/2) と書くこと。

Delay controls are not synthesisable. A synthesis tool will either ignore them with a warning — producing a netlist whose behaviour differs from your simulation — or refuse the file. Either way, never put a # in RTL you intend to build.延遲控制不可合成。合成工具要不就是一邊警告一邊忽略它們 —— 產生出行為與你的模擬不同的 netlist —— 要不就是拒收這個檔案。無論哪一種,都不要在你打算做成電路的 RTL 裡放 #。遅延制御は合成できない。合成ツールは警告つきで無視する(シミュレーションと振る舞いの異なるネットリストを生む)か、ファイルを受け付けないかのどちらかである。いずれにせよ、実際に作るつもりの RTL に # を置いてはならない。

4.2 Event control @ Non-synthesized as a statement4.2 事件控制 @ 作為敘述時不可合成4.2 イベント制御 @ 文としては合成不可

@ suspends the thread until a specified event occurs. Unlike #, there is no guarantee it will ever resume: if the event never happens, the thread is suspended forever, which is a perfectly legal and occasionally intentional way for a test bench to deadlock.@ 把執行緒暫停到指定的事件發生為止。與 # 不同,它沒有任何保證說它一定會恢復:如果那個事件永遠不發生,執行緒就永遠暫停,這是完全合法、偶爾也是刻意為之的測試平台死結方式。@ は指定したイベントが起こるまでスレッドを中断する。# と違って再開の保証はない:イベントが決して起こらなければスレッドは永久に中断したままで、これはまったく合法であり、ときにはテストベンチをわざとデッドロックさせる手段にもなる。

Example 1 — any change範例 1 — 任何變化例 1 — あらゆる変化
...
@ signal_1 a = b;   // wait for any change on signal_1
...
Example 2 — nested範例 2 — 巢狀例 2 — 入れ子
...
@ (event_a) begin
  ...
  @ (event_b) begin
    ...
  end
end
...

Two points about the first example. @ signal_1 with no edge qualifier triggers on any value change of signal_1, including 0→x and 1→z. And the event control is edge-sensitive in the strict sense: it responds to a transition, not to a level. If signal_1 is already 1 and stays 1, the thread waits forever. That is the fundamental difference from wait in §4.6.關於第一個範例有兩點。沒有邊緣修飾字的 @ signal_1 會在 signal_1 任何值變化時觸發,包括 0→x 與 1→z。而且事件控制在嚴格意義上是邊緣敏感的:它回應的是一次轉態,不是一個電位。如果 signal_1 本來就是 1 而且維持 1,執行緒就永遠等下去。這正是它與 §4.6 的 wait 的根本差別。最初の例について二点ある。エッジ修飾子のない @ signal_1 は signal_1 のあらゆる値変化で発火し、0→x や 1→z も含む。そしてイベント制御は厳密な意味でエッジ感応である:応答するのは遷移であってレベルではない。signal_1 がもともと 1 でそのまま 1 であれば、スレッドは永遠に待つ。これが §4.6 の wait との根本的な違いである。

The nested example shows that event controls compose: the thread waits for event_a, does some work, then waits for event_b. This sequential-waiting style is the foundation of the implicit FSM description covered in §11.3, where each state is simply the point in the code between two event controls.巢狀的那個範例顯示事件控制可以串接:執行緒等 event_a,做一些事,然後等 event_b。這種循序等待的寫法,正是 §11.3 所談的隱式有限狀態機描述的基礎 —— 在那裡,每個狀態不過就是程式碼中兩個事件控制之間的那個位置。入れ子の例は、イベント制御を次々に連ねられることを示している:スレッドは event_a を待ち、いくらか仕事をし、それから event_b を待つ。この逐次待ちのスタイルが §11.3 で扱う暗黙的 FSM 記述の基礎であり、そこでは各状態は単にコード中の二つのイベント制御の間の地点にすぎない。

A missed event is gone forever錯過的事件就永遠消失了取り逃したイベントは永遠に戻らない

If event_b fires while the thread is still busy executing the statements above it, the thread never sees it — there is no queue, no pending flag, nothing. This is the dominant failure mode of implicit state machines and of hand-written bus protocol checkers. Edge-sensitive synchronisation only works when the consumer is guaranteed to be waiting before the producer fires.如果 event_b 在執行緒還忙著執行它上面那些敘述時就發動了,執行緒永遠不會看到它 —— 沒有佇列,沒有待處理旗標,什麼都沒有。這是隱式狀態機與手寫匯流排協定檢查器最主要的失效模式。邊緣敏感的同步只有在「消費者保證在生產者發動之前就已經在等」的情況下才管用。event_b がスレッドのまだ上の文を実行している最中に発火すると、スレッドはそれを決して見ない——キューも保留フラグも何もない。これは暗黙的ステートマシンや手書きのバスプロトコルチェッカの、最大の失敗様式である。エッジ感応の同期は、生産者が発火する前に消費者が必ず待っていると保証できる場合にのみ機能する。

4.3 posedge and negedge4.3 posedge 與 negedge4.3 posedge と negedge

The edge qualifiers narrow an event control from "any change" to "a change in a particular direction". The slide's definition is the standard one and it is wider than most people expect:邊緣修飾字把事件控制從「任何變化」收窄成「朝特定方向的變化」。投影片上的定義是標準定義,而它比多數人想的更寬:エッジ修飾子は、イベント制御を「あらゆる変化」から「特定方向への変化」へ狭める。スライドの定義は標準どおりのものであり、多くの人が思うより範囲が広い。

Qualifier修飾字修飾子Transitions that count算數的轉態数えられる遷移
posedge0→1, 0→x, x→1  (and, treating z like x, 0→z and z→1)0→1、0→x、x→1  (以及把 z 視同 x 時的 0→z 與 z→1)0→1、0→x、x→1  (および z を x と同様に扱った 0→z と z→1)
negedge1→0, 1→x, x→0  (and 1→z, z→0)1→0、1→x、x→0  (以及 1→z、z→0)1→0、1→x、x→0  (および 1→z、z→0)

The rule is simply "the LSB of the signal moved towards the target value", with x and z treated as an intermediate rung on a ladder 0 < x/z < 1. Transitions that stay on the same rung (x→z, z→x) are not edges.規則其實就是「訊號的最低位元朝目標值移動了」,其中 x 與 z 被當成 0 < x/z < 1 這個階梯上的中間一階。停在同一階的轉態(x→z、z→x)不算邊緣。規則は要するに「信号の最下位ビットが目標値のほうへ動いた」であり、x と z は 0 < x/z < 1 というはしごの中間段として扱われる。同じ段にとどまる遷移(x→z、z→x)はエッジではない。

The practical consequence is that a clock which goes 0→x at power-up fires every always @(posedge clk) in the design, loading x into every flip-flop. This is normal and expected; it is why a real design asserts reset for several cycles at the start of simulation before anything meaningful happens. It also means a tri-stated clock line that floats to z will generate spurious edges — one more argument for never gating a clock with a tri-state buffer.實務上的後果是:一個在上電時從 0→x 的時脈,會觸發設計裡每一個 always @(posedge clk),把 x 載進每一個正反器。這是正常而且預期中的;也正因如此,真正的設計會在模擬一開始先讓重置維持數個週期,之後才做有意義的事。這同時表示一條浮接到 z 的三態時脈線會產生假邊緣 —— 這又是一個「絕不要用三態緩衝器去閘控時脈」的理由。実務上の帰結はこうである:電源投入時に 0→x となるクロックは、設計中のすべての always @(posedge clk) を発火させ、すべてのフリップフロップに x を取り込ませる。これは正常で想定どおりであり、だからこそ実際の設計はシミュレーション開始時にまずリセットを数サイクル保持してから意味のある動作に入る。同時に、z にフロートするトライステートのクロック線は偽のエッジを生むということでもある——クロックをトライステートバッファでゲートしてはならない理由がもう一つ増える。

always @ (posedge clock) begin
  #10 b = a;     // samples a 10 units AFTER the edge
end

This example from slide 20 is a trap worth dissecting. Because the #10 is a leading delay control, the thread wakes at the edge, sleeps 10 units, and only then reads a. It therefore does not model a flip-flop with a 10-unit clock-to-Q delay; it models something that samples the input 10 units late. Worse, the block is busy during those 10 units, so a clock edge arriving at t+5 is missed entirely. The construct that actually models clock-to-Q is the intra-assignment form b <= #10 a;, which reads a at the edge and writes b ten units later — see §5.1.第 20 張投影片的這個例子是值得解剖的陷阱。因為 #10 是前置的延遲控制,執行緒在邊緣醒來,睡 10 個單位,然後才讀 a。所以它描述的不是一個 clock-to-Q 延遲 10 單位的正反器;它描述的是一個晚 10 個單位才取樣輸入的東西。更糟的是,區塊在那 10 個單位裡是忙碌的,所以在 t+5 抵達的時脈邊緣會被完全錯過。真正能描述 clock-to-Q 的構造是指派內形式 b <= #10 a;,它在邊緣讀 a,十個單位之後才寫 b —— 見 §5.1。スライド 20 のこの例は解剖する価値のある落とし穴である。#10 が先頭の遅延制御なので、スレッドはエッジで目覚め、10 単位眠り、そのあとで a を読む。したがってこれはクロック・ツー・Q 遅延 10 単位のフリップフロップを表してはおらず、入力を 10 単位遅れてサンプルする何かを表している。さらに悪いことに、その 10 単位の間ブロックは忙しいので、t+5 に到来するクロックエッジは完全に取り逃される。クロック・ツー・Q を本当に表す構文は代入内形式 b <= #10 a; であり、これはエッジで a を読み、10 単位後に b を書く——§5.1 を参照。

4.4 Event or and the asynchronous flip-flop4.4 事件 or 與非同步正反器4.4 イベント or と非同期フリップフロップ

An event control may list several events separated by or (Verilog-2001 also allows a comma). The thread resumes when any one of them fires — it is a logical OR of trigger conditions, not a Boolean expression, and writing @(a || b) instead of @(a or b) is a different and almost always wrong thing to write.一個事件控制可以用 or 列出好幾個事件(Verilog-2001 也允許用逗號)。只要其中任何一個發動,執行緒就恢復 —— 它是觸發條件的邏輯 OR,不是一個布林運算式,而把 @(a or b) 寫成 @(a || b) 是完全不同、而且幾乎一定是錯的寫法。一つのイベント制御は or で複数のイベントを並べられる(Verilog-2001 ではカンマも使える)。そのうちどれか一つが発火すればスレッドは再開する——これはトリガ条件の論理 OR であってブール式ではなく、@(a or b) の代わりに @(a || b) と書くのはまったく別のこと、しかもほぼ確実に誤ったことである。

always @ (posedge clk or negedge set or negedge rst) begin
  if (rst == 0)
    q = 1'b0;          // reset has highest priority
  else if (set == 0)
    q = 1'b1;
  else if (clk == 1)
    q = data_in;       // woken by the clock edge
  else
    q = q;
end

This is the lecture's model of a positive-edge D flip-flop with active-low asynchronous set and reset. The body is a priority decoder over "why was I woken", and the order encodes the priority: reset beats set beats clock. The branches are the interesting part.這是講義中「帶低電位有效非同步 set 與 reset 的正緣 D 型正反器」的模型。本體是一個對「我為什麼被叫醒」做的優先權解碼器,而順序就編碼了優先權:reset 勝過 set,set 勝過 clock。有趣的是那些分支。これは講義にある「Low アクティブの非同期セットとリセットを持つ立ち上がりエッジ D フリップフロップ」のモデルである。本体は「なぜ起こされたのか」に対する優先順位デコーダであり、その順序が優先順位を符号化している:リセットがセットに勝ち、セットがクロックに勝つ。面白いのは分岐のほうである。

When the block wakes, it cannot ask which event fired — Verilog gives it no such query — so it must deduce it by inspecting the signals. If rst is 0, the wake-up must have been (or must now be subordinate to) the reset. If set is 0, likewise. Otherwise both asynchronous inputs are inactive, so the only remaining explanation is a clock edge, and the clk == 1 test confirms it before sampling data_in.區塊醒來時,它無法詢問是哪個事件發動的 —— Verilog 沒有給它這種查詢手段 —— 所以它必須靠檢查訊號來推斷。如果 rst 是 0,喚醒就一定是(或現在一定從屬於)那個 reset。如果 set 是 0,同理。否則兩個非同步輸入都不作用,於是唯一剩下的解釋就是一個時脈邊緣,而 clk == 1 這個測試會在取樣 data_in 之前先確認一次。ブロックが目覚めたとき、どのイベントが発火したかを問い合わせることはできない——Verilog はそのような問い合わせ手段を与えていない——ので、信号を調べて推定するしかない。rst が 0 なら、目覚めの原因はリセットだった(あるいはいまやリセットに従属する)はずである。set が 0 の場合も同様である。どちらでもなければ非同期入力はどちらも非アクティブなので、残る説明はクロックエッジだけであり、clk == 1 の判定が data_in をサンプルする前にそれを確認する。

Why confirm? Because of the x transitions in §4.3. A negedge rst event can be caused by 1→x, after which rst == 0 evaluates to x, which if treats as false (§7.4). Control falls through to the clock branch even though no clock edge occurred. The clk == 1 guard catches that case and the final else q = q; holds the output. Without the guard, an x glitch on reset would spuriously clock data through.為什麼要確認?因為 §4.3 裡那些 x 轉態。一個 negedge rst 事件可能是 1→x 造成的,而在那之後 rst == 0 求值為 x,if 把它當成假(§7.4)。於是控制權掉進時脈分支,即使根本沒有時脈邊緣發生。clk == 1 這道護欄攔住了這種情況,而最後的 else q = q; 則保住輸出。沒有這道護欄,reset 上的一個 x 毛刺就會假性地把資料打進去。なぜ確認するのか。§4.3 の x 遷移があるからである。negedge rst イベントは 1→x によっても起こりうるが、そのとき rst == 0 は x と評価され、if はそれを偽として扱う(§7.4)。クロックエッジなど起きていないのに、制御はクロックの分岐へ落ちてしまう。clk == 1 のガードがこの場合を捕まえ、最後の else q = q; が出力を保持する。ガードがなければ、リセット上の x グリッチが偽のクロックとしてデータを通してしまう。

What you should write instead in synthesisable RTL在可合成的 RTL 裡你該改寫成什麼合成可能な RTL ではこう書くべき
always @ (posedge clk or negedge rst_n)
  if (!rst_n) q <= 1'b0;
  else        q <= data_in;

Non-blocking assignment, one asynchronous control, no clk == 1 guard and no explicit hold branch. Every synthesis tool recognises this exact template and maps it to an asynchronous-reset flop from the standard-cell library. The moment you deviate — by adding a third asynchronous input, by testing a signal that is not in the sensitivity list, or by using blocking assignment — the tool either infers something strange or gives up. Lecture 6 formalises these templates.非阻斷式指派、一個非同步控制、沒有 clk == 1 護欄、也沒有明確的保持分支。每一個合成工具都認得這個確切的樣板,並把它對應到標準單元庫裡的非同步重置正反器。只要你一偏離 —— 加上第三個非同步輸入、測試一個不在敏感度列表裡的訊號,或改用阻斷式指派 —— 工具不是推斷出奇怪的東西,就是直接放棄。第 6 講會把這些樣板形式化。ノンブロッキング代入、非同期制御は一つ、clk == 1 のガードなし、明示的な保持分岐もなし。どの合成ツールもこの形のテンプレートを認識し、スタンダードセルライブラリの非同期リセット付きフリップフロップに対応づける。そこから少しでも外れると——三つ目の非同期入力を加える、感度リストにない信号を判定する、ブロッキング代入を使う——ツールは奇妙なものを推論するか、あきらめるかのどちらかになる。これらのテンプレートは第 6 講で形式化される。

4.5 Named events and the trigger operator Non-synthesized4.5 具名事件與觸發運算子 不可合成4.5 名前付きイベントとトリガ演算子 合成不可

A named event is an abstract synchronisation token with no value, no width and no storage. You declare it with event, you fire it with ->, and you wait for it with @. Its only purpose is to let one part of a design tell another that something happened, without inventing a signal to carry the news.具名事件是一個抽象的同步符記,沒有值、沒有寬度、也沒有儲存。你用 event 宣告它,用 -> 把它發動,用 @ 等它。它唯一的用途,就是讓設計的某一部分告訴另一部分「某件事發生了」,而不必為了傳這個消息去發明一條訊號。名前付きイベントは、値も幅も記憶も持たない抽象的な同期トークンである。event で宣言し、-> で発火させ、@ で待つ。唯一の用途は、知らせを運ぶ信号をわざわざ作らずに、設計のある部分が別の部分に「何かが起きた」と伝えることである。

event up_edge;

always @ (posedge clk)
  -> up_edge;              // fire the event

always @ (up_edge)
  ...                      // woken by the event

Because an event is declared in a module, it can be reached from another module by hierarchical dereferencing, which is what makes the lecture's two-module example work:因為事件是宣告在模組裡的,所以可以用階層式取值從另一個模組伸手取用,而這正是講義中那個雙模組範例能運作的原因:イベントはモジュール内で宣言されるので、階層参照によって別のモジュールから到達できる。講義の二モジュールの例が成り立つのはこのためである。

module top (clk, data, q);
  input  clk, data;
  output q;
  talker   M1 (clk);
  receiver M2 (q, data);
endmodule

module talker (clk);
  input clk;
  event do_it;
  always @ (posedge clk)
    -> do_it;              // trigger
endmodule
module receiver (q, data);
  input  data;
  output q;
  reg    q;
  always @ (top.M1.do_it)  // hierarchical
    q = data;
endmodule

The receiver has no clock port at all. Its only connection to the clock domain is the hierarchical name top.M1.do_it, which reaches across the module boundary without any wire in the port list.那個 receiver 根本沒有時脈埠。它與時脈領域唯一的連結就是階層式名稱 top.M1.do_it,它跨過模組邊界,而埠列表裡沒有任何一條線。この receiver にはクロックポートがまったくない。クロックドメインとの唯一のつながりは階層名 top.M1.do_it であり、これはポートリストに一本の配線もないままモジュール境界を越える。

That is also why named events are not synthesisable: there is no wire, so there is nothing to build. They belong in test benches and in abstract transaction-level models, where "the bus master finished a burst" is a cleaner thing to signal than a hand-rolled handshake. One further property matters in practice: a named event is stateless. Triggering it when nobody is waiting does nothing at all, and there is no way to ask afterwards whether it was triggered. Like the bare @ of §4.2, it is a pure edge.這也正是具名事件不可合成的原因:既然沒有線,就沒有東西可以蓋。它們屬於測試平台,以及抽象的交易層模型 —— 在那裡,「匯流排主控完成了一次叢發」比手工打造的握手訊號更乾淨。還有一個性質在實務上很重要:具名事件是無狀態的。沒有人在等的時候把它觸發,什麼都不會發生,而且事後也沒有辦法問它到底有沒有被觸發過。就像 §4.2 裡那個光禿禿的 @ 一樣,它是純粹的邊緣。名前付きイベントが合成できない理由もこれである:配線がないのだから作るものがない。その居場所はテストベンチと抽象的なトランザクションレベルのモデルであり、そこでは「バスマスタがバースト転送を終えた」と伝えるほうが、手作りのハンドシェイクより素直である。実務上もう一つ重要な性質がある:名前付きイベントは状態を持たない。誰も待っていないときに発火させても何も起こらず、後から発火したかどうかを問い合わせる手段もない。§4.2 の裸の @ と同じく、純粋なエッジである。

4.6 The wait statement Non-synthesized4.6 wait 敘述 不可合成4.6 wait 文 合成不可

wait (expr) statement; suspends the thread until expr is true, then executes the statement. The crucial difference from @ is that wait is level-sensitive: it tests the condition first, and if it is already true the thread does not suspend at all.wait (expr) statement; 把執行緒暫停到 expr 為真為止,然後執行那道敘述。與 @ 的關鍵差別在於 wait 是電位敏感的:它先測條件,如果條件本來就為真,執行緒根本不會暫停。wait (expr) statement; は expr が真になるまでスレッドを中断し、それから文を実行する。@ との決定的な違いは wait がレベル感応であることだ:まず条件を判定し、すでに真であればスレッドはまったく中断しない。

data_out = 0;
wait (enable) data_out = data_in;
data_inenabledata_out wait releases here t
data_in rises first and is ignored; data_out is driven only when enable becomes true, and it samples whatever data_in holds at that moment.data_in 先上升,但被忽略;data_out 只有在 enable 變為真時才被驅動,而且取樣的是那一刻 data_in 所持有的值。data_in が先に立ち上がるが無視される。data_out が駆動されるのは enable が真になった時点だけで、そのときに data_in が保持している値をサンプルする。

The slide labels this "level-sensitive behaviour", and it is the natural way to model the enable input of a transparent latch, a FIFO's not-empty flag, or a handshake that may already have completed by the time the consumer looks. Three cautions:投影片把這稱為「電位敏感行為」,而這正是描述透明閂鎖的 enable 輸入、FIFO 的非空旗標,或是一個在消費者去看的時候可能早就完成的握手的自然方式。有三點要注意:スライドはこれを「レベル感応の振る舞い」と呼ぶ。これは透過ラッチの enable 入力、FIFO の非空フラグ、あるいは消費者が見にいった時点ですでに完了しているかもしれないハンドシェイクを表現する自然な方法である。注意すべき点が三つある。

@ versus wait in one line一行講完 @ 與 wait 的差別@ と wait の違いを一行で

@(cond_signal) asks "has it changed?" and blocks even when the answer is already yes. wait(cond) asks "is it true?" and does not block when the answer is already yes. Use @ for clocks and edges; use wait for flags and handshakes.@(cond_signal) 問的是「它變了嗎?」,就算答案早就是「是」也照樣擋住。wait(cond) 問的是「它為真嗎?」,答案早就是「是」時就不擋。時脈與邊緣用 @;旗標與握手用 wait。@(cond_signal) は「変化したか」を問い、答えがすでに「はい」でもブロックする。wait(cond) は「真か」を問い、答えがすでに「はい」ならブロックしない。クロックやエッジには @ を、フラグやハンドシェイクには wait を使うこと。

Practice 6 — Clock generators練習 6 — 時脈產生器練習 6 — クロック生成器

Problem: For each generator, state the period, the duty cycle, and whether it works at all.題目:對每一個產生器,說出它的週期、工作週期,以及它到底能不能動。問題:各生成器について、周期、デューティ比、そしてそもそも動作するかどうかを述べよ。

(a) initial clk = 0;  always #7 clk = ~clk;
(b) always #7 clk = ~clk;
(c) initial begin clk = 0; forever begin #3 clk = 1; #7 clk = 0; end end
(d) always begin clk = 0; #5; clk = 1; #5; end
Click to reveal solution點擊展開解答クリックで解答を表示

(a) Period 14, duty 50%. Correct and idiomatic.(a) 週期 14,工作週期 50%。正確而且是慣用寫法。(a) 周期 14、デューティ比 50%。正しく、定石どおりである。

(b) Broken. clk starts at x, ~x is x, and it never leaves x. The waveform is a flat unknown and every flop in the design holds x.(b) 壞的。clk 從 x 起步,~x 還是 x,它永遠離不開 x。波形是一條平坦的未知值,設計裡每一個正反器都抓著 x 不放。(b) 壊れている。clk は x から始まり、~x もまた x なので、永遠に x から抜け出せない。波形は平坦な未知値であり、設計中のすべてのフリップフロップが x を抱えたままになる。

(c) Period 10. The thread waits 3, drives 1, waits 7, drives 0 — so the high phase is 7 and the low phase is 3, giving a 70% duty cycle. Reading the delays as the duration of the phase they precede rather than the phase they follow is the standard mistake here.(c) 週期 10。執行緒等 3、驅動 1、等 7、驅動 0 —— 所以高電位相是 7,低電位相是 3,工作週期 70%。把每個延遲讀成「它後面那一相的長度」而不是「它前面那一相的長度」,是這裡的標準錯誤。(c) 周期 10。スレッドは 3 待って 1 を駆動し、7 待って 0 を駆動する——したがって High 期間が 7、Low 期間が 3 で、デューティ比は 70% になる。各遅延を「その後ろの位相の長さ」ではなく「その前の位相の長さ」と読むのが、ここでの定番の誤りである。

(d) Period 10, duty 50%, and it works without an initial because the first statement of every pass assigns a constant rather than inverting. It is slightly wasteful (it re-drives 0 at the top of each loop) but correct.(d) 週期 10,工作週期 50%,而且不需要 initial 也能動,因為每一輪的第一道敘述指派的是一個常數,不是做反轉。它稍微浪費了一點(每圈開頭都重新驅動 0 一次),但是正確的。(d) 周期 10、デューティ比 50%。しかも initial なしで動作する。各周回の最初の文が反転ではなく定数を代入するからである。やや無駄がある(ループの先頭で 0 を駆動し直す)が、正しい。

Practice 7 — wait versus @練習 7 — wait 與 @ 的比較練習 7 — wait と @ の比較

Problem: done is set to 1 at t = 10 by another process and stays 1. Two consumers start at t = 0:題目:done 由另一個行程在 t = 10 設為 1,之後維持 1。兩個消費者都在 t = 0 啟動:問題:done は別のプロセスによって t = 10 で 1 にされ、その後 1 のままである。二つの消費者はどちらも t = 0 に起動する。

initial begin #20; @(done)      $display("A at %0t", $time); end
initial begin #20; wait (done)  $display("B at %0t", $time); end

What does each print? Now repeat with #5; instead of #20;.各自會印出什麼?接著把 #20; 換成 #5; 再做一次。それぞれ何を印字するか。次に #20; を #5; に替えて同じことを行え。

Click to reveal solution點擊展開解答クリックで解答を表示

With #20. Both consumers arrive at their synchronisation point at t = 20, after done has already risen. B prints immediately: B at 20, because wait finds the condition already true. A prints nothing — the only change on done happened at t = 10 and @ has no memory of it. If done never changes again, that initial block is suspended for the rest of the simulation.用 #20 時。兩個消費者都在 t = 20 才抵達各自的同步點,而那時 done 早就升起來了。B 立刻印出:B at 20,因為 wait 發現條件已經為真。A 什麼都不印 —— done 上唯一的變化發生在 t = 10,而 @ 對它沒有任何記憶。如果 done 之後再也不變,那個 initial 區塊就在模擬的其餘時間裡一直暫停。#20 の場合。どちらの消費者も t = 20 になってから同期点に到達し、そのとき done はすでに立ち上がっている。B はただちに B at 20 と印字する。wait が条件をすでに真と見つけるからである。A は何も印字しない——done 上の唯一の変化は t = 10 に起きており、@ はそれを記憶していない。done が二度と変化しなければ、その initial ブロックはシミュレーションの残り全体にわたって中断したままになる。

With #5. Both are waiting before t = 10. A prints A at 10 (the 0→1 change is an event). B also prints B at 10 (the condition becomes true). They agree.用 #5 時。兩者在 t = 10 之前就已經在等了。A 印出 A at 10(0→1 的變化是一個事件)。B 也印出 B at 10(條件變為真)。兩者一致。#5 の場合。どちらも t = 10 より前に待ち状態に入っている。A は A at 10 と印字する(0→1 の変化がイベントである)。B も B at 10 と印字する(条件が真になる)。両者は一致する。

The moral: an edge-sensitive wait is only safe if you can prove the consumer is armed before the producer fires. When you cannot prove it, use wait.教訓是:只有在你能證明消費者在生產者發動之前就已就緒時,邊緣敏感的等待才安全。證明不了的時候,就用 wait。教訓はこうである:エッジ感応の待ちが安全なのは、生産者が発火する前に消費者が構えていると証明できる場合だけである。証明できないなら wait を使うこと。

5. Intra- vs Inter-Assignment Delay5. 指派內延遲與指派間延遲5. 代入内遅延と代入間遅延

5.1 Where the delay sits changes what it means5.1 延遲擺的位置會改變它的意義5.1 遅延を置く位置が意味を変える

The slide's slogan is "postpone assignment, not evaluation", and it is one of the few slogans in this lecture that is both short and complete. Verilog lets you write a delay in two places relative to an assignment, and the two mean entirely different things:投影片的口號是「延後的是指派,不是求值」,而這是本講少數既短又完整的口號之一。Verilog 允許你把延遲寫在指派的兩個位置,而這兩個位置的意義完全不同:スライドの標語は「先送りされるのは代入であって評価ではない」であり、この講義では珍しく短くかつ完全な標語である。Verilog は代入に対して二つの位置に遅延を書かせるが、その二つはまったく別の意味を持つ。

The two positions兩個位置二つの位置 $$\underbrace{\texttt{\#5 a = b;}}_{\text{inter-assignment}} \qquad\text{versus}\qquad \underbrace{\texttt{a = \#5 b;}}_{\text{intra-assignment}}$$

The standard defines the intra-assignment form by an exact rewriting, which is the safest way to reason about it:標準用一個精確的改寫來定義指派內形式,而那是最安全的思考方式:標準は代入内形式を厳密な書き換えによって定義している。これがもっとも安全な考え方である。

What you write你寫的東西書いたもの
a = #5 b;
c = d;
What it means它的意思その意味
begin
  temp = b;      // evaluate NOW
  #5;            // then wait
  a = temp;      // then write the OLD value
end
c = d;           // runs at t+5, because = blocks

The same rewriting works with an event control in place of the delay: e = @(bus) f; reads f immediately, waits for a change on bus, and only then writes e. Slide 25 of Part 1 shows exactly this pair.把延遲換成事件控制,同樣的改寫照樣成立:e = @(bus) f; 會立刻讀 f,等 bus 上的一次變化,然後才寫 e。第 1 部的第 25 張投影片展示的正是這一對。遅延の代わりにイベント制御を置いても同じ書き換えが成り立つ:e = @(bus) f; はただちに f を読み、bus の変化を待ち、それから e に書く。パート 1 のスライド 25 が示しているのはまさにこの対である。

The non-blocking forms differ in one respect only: the thread does not wait for the postponed write, so execution continues at once.非阻斷式形式只在一點上不同:執行緒不等那個被延後的寫入,所以執行會立刻繼續下去。ノンブロッキング形式が違うのは一点だけである:スレッドは先送りされた書き込みを待たず、実行はただちに続く。

initial begin
  @ (posedge clk)
    g <= @ (bus) k;   // sample k at the clock edge, write g at the next bus change
    p <= q;           // executes in the same time slot, not after g is written
end

always begin
  @ (posedge clock)
    g <= @ (bus) k;   // the always block returns to its @ immediately
end

That last block is worth staring at. Because <= does not block, the always returns to the @(posedge clock) at once, ready for the next edge — even though the write to g scheduled by the previous edge may still be outstanding. Several such writes can therefore be in flight simultaneously. That is exactly what a pipeline does, and it is why the non-blocking intra-assignment form is the only correct way to model a propagation delay in a clocked model.最後那個區塊值得盯著看。因為 <= 不阻斷,always 會立刻回到 @(posedge clock),準備好迎接下一個邊緣 —— 即使上一個邊緣所排程的那次對 g 的寫入可能都還沒兌現。因此好幾次這樣的寫入可以同時在途中。這正是管線在做的事,也正是為什麼在時脈同步模型裡,非阻斷式的指派內形式是唯一能正確描述傳播延遲的寫法。最後のブロックはじっくり眺める価値がある。<= はブロックしないので、always はただちに @(posedge clock) に戻り、次のエッジに備える——前のエッジがスケジュールした g への書き込みがまだ果たされていなくてもである。したがってそうした書き込みが同時に複数、飛行中になりうる。これこそパイプラインのしていることであり、クロック同期モデルで伝搬遅延を正しく表せるのがノンブロッキングの代入内形式だけである理由でもある。

5.2 A worked timeline5.2 時間軸實例5.2 タイムラインの実例

Both forms, same stimulus, traced兩種形式,同樣的激勵,逐步追蹤二つの形式を同じ刺激でトレースする

Let b = 0 up to t = 0, b = 1 on [0, 3), b = 0 from t = 3. A thread starts at t = 0 and executes one of the two statements.設 b 在 t = 0 之前為 0,在 [0, 3) 區間為 1,從 t = 3 起為 0。一個執行緒在 t = 0 啟動,執行這兩道敘述其中之一。b は t = 0 まで 0、[0, 3) の区間で 1、t = 3 以降は 0 とする。あるスレッドが t = 0 に起動し、二つの文のいずれかを実行する。

Inter-assignment #5 a = b;. The thread sleeps from t = 0 to t = 5. At t = 5 it evaluates b, which by then is 0, and writes a ← 0. The value b held when the statement started is irrelevant and lost.指派間 #5 a = b;。執行緒從 t = 0 睡到 t = 5。在 t = 5 它求 b,而那時 b 已經是 0,於是寫 a ← 0。敘述開始時 b 所持有的值無關緊要,而且已經弄丟了。代入間 #5 a = b;。スレッドは t = 0 から t = 5 まで眠る。t = 5 で b を評価するが、そのとき b はすでに 0 なので a ← 0 を書く。文の開始時に b が保持していた値は無関係であり、失われている。
Intra-assignment a = #5 b;. At t = 0 the thread evaluates b → 1 and stashes it. At t = 5 it writes a ← 1. The value b holds at t = 5 is irrelevant.指派內 a = #5 b;。在 t = 0 執行緒求 b → 1 並把它藏起來。到 t = 5 寫 a ← 1。b 在 t = 5 所持有的值無關緊要。代入内 a = #5 b;。t = 0 でスレッドは b → 1 を評価して蓄える。t = 5 で a ← 1 を書く。t = 5 に b が保持している値は無関係である。
Both forms block. In both cases the next statement of the thread runs at t = 5, not at t = 0.兩種形式都會阻斷。兩種情況下,執行緒的下一道敘述都在 t = 5 執行,不是在 t = 0。どちらの形式もブロックする。どちらの場合もスレッドの次の文は t = 0 ではなく t = 5 で実行される。
Non-blocking intra-assignment a <= #5 b;. Same stash at t = 0, same write at t = 5, but the next statement runs at t = 0.非阻斷式指派內 a <= #5 b;。在 t = 0 同樣藏值,在 t = 5 同樣寫入,但下一道敘述在 t = 0 就執行。ノンブロッキングの代入内 a <= #5 b;。t = 0 で同じく値を蓄え、t = 5 で同じく書き込むが、次の文は t = 0 に実行される。
b a (#5 a = b) a (a = #5 b) a (a <= #5 b) reads b at t=5 → b is already 0, so a stays 0 read b at t=0 (=1), stashed; written at t=5 same value, same time, but the thread never stopped: next statement ran at t=0 evaluate write 0 3 5 10 t
Inter-assignment delay reads the source late and therefore misses the pulse entirely. Intra-assignment delay reads it at t = 0 and reproduces it 5 units later. The blocking and non-blocking intra forms differ only in whether the thread waits.指派間延遲晚讀來源,所以整個脈衝都漏掉了。指派內延遲在 t = 0 就讀到它,並在 5 個單位之後重現。阻斷式與非阻斷式的指派內形式,差別只在執行緒等不等。代入間遅延はソースを遅く読むので、パルスを丸ごと取り逃す。代入内遅延は t = 0 でそれを読み、5 単位後に再現する。ブロッキングとノンブロッキングの代入内形式の違いは、スレッドが待つかどうかだけである。

5.3 Six models of the same OR gate5.3 同一個 OR 閘的六種模型5.3 同じ OR ゲートの六つのモデル

Slide 27 of Part 1 puts six one-line models of y = a | b on the screen and asks what the difference is. Two of them are crossed out on the slide as bad practice. Here is the full table with the reasoning.第 1 部的第 27 張投影片把 y = a | b 的六個單行模型全放到畫面上,問差別在哪。其中兩個在投影片上被畫叉,標為壞習慣。以下是完整的表,附上理由。パート 1 のスライド 27 は y = a | b の一行モデルを六つ画面に並べ、違いは何かと問う。そのうち二つはスライド上で悪い書き方として×印をつけられている。以下は理由を添えた完全な表である。

Code程式碼コードDelay type延遲類型遅延の種類Behaviour行為振る舞い
assign y = a | b;none無なしZero-delay combinational. Every change on a or b propagates in the same time slot.零延遲組合邏輯。a 或 b 上的每一次變化都在同一個時槽內傳過去。ゼロ遅延の組合せ論理。a や b の変化はすべて同じタイムスロット内で伝わる。
always @(a or b) y = a | b;none無なしIdentical to the above, provided the sensitivity list is complete. Prefer always @*.只要敏感度列表完整,就和上一列完全相同。建議用 always @*。感度リストが完全であれば上の行とまったく同じ。always @* を推奨する。
assign #5 y = a | b;inertial慣性慣性Output follows 5 units later, but any pulse narrower than 5 is swallowed. This is the model of a real gate.輸出晚 5 個單位跟上,但任何窄於 5 的脈衝都會被吞掉。這是真實閘的模型。出力は 5 単位遅れて追従するが、幅が 5 より狭いパルスは飲み込まれる。これが実際のゲートのモデルである。
always @(a or b) #5 y = a | b; avoidalways @(a or b) #5 y = a | b; 避免always @(a or b) #5 y = a | b; 避けるべきinter-assignment指派間代入間Wakes on a change, sleeps 5, then evaluates with whatever the inputs hold at that moment. Changes during the sleep are missed because the thread is not at the @.一有變化就醒來,睡 5,然後才用那一刻輸入所持有的值求值。睡眠期間的變化會被漏掉,因為執行緒並不在 @ 上。変化で目覚め、5 眠り、そのあとでその時点の入力の値で評価する。眠っている間の変化は取り逃される。スレッドが @ にいないからである。
always @(a or b) y = #5 a | b; avoidalways @(a or b) y = #5 a | b; 避免always @(a or b) y = #5 a | b; 避けるべきintra-assignment, blocking指派內,阻斷式代入内、ブロッキングEvaluates at the change, writes 5 later. The thread is blocked for those 5 units, so intervening changes are lost — the output can stick at a stale value.在變化當下求值,5 之後才寫。執行緒在那 5 個單位裡被阻斷,所以期間的變化會遺失 —— 輸出可能卡在一個過時的值上。変化の時点で評価し、5 後に書く。その 5 単位の間スレッドはブロックされるので、途中の変化は失われる——出力が古い値に貼り付いたままになりうる。
always @(a or b) y <= #5 a | b;transport傳輸伝送Evaluates at the change, schedules the write, returns to the @ immediately. Every change is reproduced 5 units later, including arbitrarily narrow pulses.在變化當下求值,排好寫入,然後立刻回到 @。每一次變化都會在 5 個單位之後重現,包括任意窄的脈衝。変化の時点で評価し、書き込みを予約し、ただちに @ へ戻る。あらゆる変化が 5 単位後に再現され、いくら狭いパルスも含まれる。

5.4 Inertial versus transport delay5.4 慣性延遲與傳輸延遲5.4 慣性遅延と伝送遅延

Those two words name a real physical distinction and it is worth being precise about them, because they are the reason the table above has two "correct" rows rather than one.這兩個詞指的是一個真實的物理區別,值得講精確一點,因為它們正是上面那張表有兩列「正確」而不是只有一列的原因。この二語は実在する物理的区別を指しており、正確に述べておく価値がある。上の表に「正しい」行が一つではなく二つある理由がこれだからである。

Inertial delay is what a CMOS gate actually exhibits. The gate has capacitance; a pulse at the input shorter than the gate's propagation delay does not deliver enough charge to swing the output, so it is filtered out. Verilog implements this for continuous assignments and gate primitives: when a new output value is scheduled, any previously scheduled output transition that has not yet occurred is cancelled. So in assign #5 y = a|b;, a 0→1 at t=2 schedules y←1 at t=7, and a 1→0 at t=4 cancels that pending event and schedules y←0 at t=9. Since y was already 0, the net result is that nothing happens at all and the 2-unit pulse vanishes.慣性延遲才是 CMOS 閘真正表現出來的行為。閘有電容;輸入端一個比閘傳播延遲還短的脈衝,送不出足夠的電荷去擺動輸出,所以它被濾掉了。Verilog 替連續指派與閘級基本元件實作了這個行為:當一個新的輸出值被排程時,任何先前排好但尚未發生的輸出轉態都會被取消。所以在 assign #5 y = a|b; 裡,t=2 的一次 0→1 會把 y←1 排在 t=7,而 t=4 的一次 1→0 取消那個待處理事件,改把 y←0 排在 t=9。既然 y 本來就是 0,淨結果就是什麼都沒發生,那個 2 單位寬的脈衝消失了。慣性遅延こそ CMOS ゲートが実際に示す振る舞いである。ゲートには容量がある。ゲートの伝搬遅延より短い入力パルスは、出力を振り切るだけの電荷を届けられないので濾し取られる。Verilog は連続代入とゲートプリミティブについてこれを実装している:新しい出力値がスケジュールされると、すでに予約済みでまだ起きていない出力遷移は取り消される。したがって assign #5 y = a|b; では、t=2 の 0→1 が y←1 を t=7 に予約し、t=4 の 1→0 がその保留イベントを取り消して y←0 を t=9 に予約する。y はもともと 0 なので、正味では何も起こらず、幅 2 単位のパルスは消える。

Transport delay is what an ideal lossless transmission line would do: every transition arrives, just later. Verilog has no transport keyword, but y <= #5 expr; gives you exactly this behaviour, because each non-blocking assignment schedules an independent update event and nothing cancels anything. A 2-unit pulse at t = 2 appears faithfully as a 2-unit pulse at t = 7.傳輸延遲則是一條理想無損傳輸線會做的事:每一次轉態都會抵達,只是晚一點。Verilog 沒有 transport 這個關鍵字,但 y <= #5 expr; 給你的正是這個行為,因為每一個非阻斷式指派都排一個獨立的更新事件,而沒有任何東西會取消任何東西。t = 2 的一個 2 單位脈衝,會忠實地在 t = 7 變成一個 2 單位脈衝。伝送遅延は理想的な無損失伝送線路が行うことである:あらゆる遷移が、ただ遅れて到着する。Verilog に transport というキーワードはないが、y <= #5 expr; がまさにこの振る舞いを与える。ノンブロッキング代入はそれぞれ独立した更新イベントを予約し、何も何かを取り消さないからである。t = 2 の幅 2 単位のパルスは、t = 7 に幅 2 単位のパルスとして忠実に現れる。

Which one do you want?你要的是哪一種?どちらが必要か

For a gate-level or cell-level model of real logic, you want inertial — use assign #d, or better, let the synthesis tool and the SDF back-annotation supply the numbers. For a behavioural model of a wire, a pipeline register's clock-to-Q, or any place where you must not lose narrow pulses, you want transport — use y <= #d expr;. Delays inside a blocking always give you neither: they give you an event-losing model that resembles nothing physical.要做真實邏輯的閘級或單元級模型,你要的是慣性 —— 用 assign #d,或者更好的做法是讓合成工具與 SDF 反標註去提供那些數字。要做一條導線的行為模型、一個管線暫存器的 clock-to-Q,或任何不能漏掉窄脈衝的地方,你要的是傳輸 —— 用 y <= #d expr;。放在阻斷式 always 裡的延遲兩者都不是:它給你的是一個會丟事件、而且不像任何實體的模型。実際の論理のゲートレベル/セルレベルのモデルが欲しいなら慣性である——assign #d を使うか、より望ましくは合成ツールと SDF のバックアノテーションに数値を供給させる。配線の振る舞いモデル、パイプラインレジスタのクロック・ツー・Q、あるいは狭いパルスを落としてはならない場所なら伝送である——y <= #d expr; を使う。ブロッキングの always の中の遅延はそのどちらでもない:イベントを落とし、物理的に何にも似ていないモデルを与えるだけである。

5.5 Repeated intra-assignment delay Non-synthesized5.5 重複的指派內延遲 不可合成5.5 繰り返し代入内遅延 合成不可

The intra-assignment timing control may be a repeat clause, which waits for a counted number of events rather than a fixed delay.指派內的時間控制也可以是一個 repeat 子句,它等的是「數到幾次事件」而不是一個固定的延遲。代入内のタイミング制御は repeat 節でもよい。これは固定の遅延ではなく、指定回数のイベントを待つ。

What you write你寫的東西書いたもの
reg_a = repeat (5) @ (negedge clock) reg_b;
Exactly equivalent to完全等價於完全に等価なもの
begin
  temp = reg_b;          // sample NOW
  @ (negedge clock);
  @ (negedge clock);
  @ (negedge clock);
  @ (negedge clock);
  @ (negedge clock);
  reg_a = temp;          // write the sampled value
end

The expansion makes three things obvious that the one-liner hides. First, the value transferred is the one reg_b held when the statement started, five edges ago. Second, the five edges are counted from the moment the statement starts, so an edge occurring at exactly that instant may or may not count depending on ordering within the time slot. Third, the construct is a pure delay line — it is how you say "this value arrives five clocks later" in a test bench without building a shift register.展開式讓三件被單行寫法藏起來的事情變得明顯。第一,被搬運的值是敘述開始時、也就是五個邊緣之前 reg_b 所持有的那個值。第二,那五個邊緣是從敘述開始的那一刻起算的,所以剛好發生在那一瞬間的邊緣算不算得到,要看時槽之內的順序。第三,這個構造就是一條純粹的延遲線 —— 它就是你在測試平台裡說「這個值五個時脈之後抵達」而不必自己搭一個移位暫存器的方法。展開形は、一行の書き方が隠している三つのことを明らかにする。第一に、運ばれる値は文が開始した時点、すなわち五エッジ前に reg_b が保持していた値である。第二に、五つのエッジは文の開始時点から数えられるので、ちょうどその瞬間に起こるエッジが数えられるかどうかはタイムスロット内の順序に依存する。第三に、この構文は純粋な遅延線であり、シフトレジスタを自分で組まずに「この値は五クロック後に届く」とテストベンチで言うための手段である。

module repeater;
  reg clock;
  reg reg_a, reg_b;

  initial clock = 0;
  always #5 clock = ~clock;

  initial begin
    #5  reg_a = 1;
    #10 reg_a = 0;
    #5  reg_a = 1;
    #20 reg_a = 0;
  end

  initial #100 $finish;

  initial begin
    #10 reg_b = repeat (5) @ (posedge clock) reg_a;
  end
endmodule
Practice 8 — A glitch through six models練習 8 — 一個毛刺通過六種模型練習 8 — グリッチが六つのモデルを通る

Problem: b = 0 throughout and all outputs have settled to 0 before t = 0. Input a goes 0→1 at t = 2 and 1→0 at t = 4. Sketch y for each of the six descriptions of §5.3.題目:b 全程為 0,而且所有輸出在 t = 0 之前都已穩定在 0。輸入 a 在 t = 2 由 0→1,在 t = 4 由 1→0。請為 §5.3 的六種描述各畫出 y。問題:b は終始 0 であり、すべての出力は t = 0 より前に 0 に落ち着いている。入力 a は t = 2 で 0→1、t = 4 で 1→0 となる。§5.3 の六つの記述それぞれについて y を描け。

Click to reveal solution點擊展開解答クリックで解答を表示
a assign assign #5 #5 y= y = #5 y <= #5 02479t
The always @(a or b) y = a | b; form is identical to the first assign row and is not drawn separately.always @(a or b) y = a | b; 這個形式與第一列的 assign 完全相同,因此沒有另外畫出來。always @(a or b) y = a | b; の形式は最初の assign の行とまったく同じなので、別途は描いていない。
  • Zero-delay (assign or always): y = 1 on [2, 4). The pulse passes straight through.零延遲(assign 或 always):y 在 [2, 4) 為 1。脈衝直接穿過去。ゼロ遅延(assign または always):y は [2, 4) で 1。パルスはそのまま通り抜ける。
  • assign #5: the rise at t = 2 schedules y←1 at t = 7; the fall at t = 4 cancels it (inertial) and schedules y←0 at t = 9, which is a no-op. y stays 0 — the 2-unit pulse is narrower than the 5-unit delay and is swallowed.assign #5:t = 2 的上升把 y←1 排在 t = 7;t = 4 的下降把它取消(慣性),改把 y←0 排在 t = 9,而那是個空操作。y 維持 0 —— 2 單位的脈衝比 5 單位的延遲窄,所以被吞掉了。assign #5:t = 2 の立ち上がりが y←1 を t = 7 に予約し、t = 4 の立ち下がりがそれを取り消して(慣性)y←0 を t = 9 に予約するが、これは何もしないのと同じである。y は 0 のまま——幅 2 単位のパルスは遅延 5 単位より狭いので飲み込まれる。
  • #5 y = a|b;: woken at t = 2, sleeps until t = 7, evaluates then — a is already back to 0, so y ← 0. The fall at t = 4 arrived while the thread was asleep and was lost. Here the loss is harmless and the answer happens to be right.#5 y = a|b;:在 t = 2 被叫醒,睡到 t = 7,然後才求值 —— a 早就回到 0 了,所以 y ← 0。t = 4 的下降在執行緒睡著時抵達,被弄丟了。這裡的遺失是無害的,答案剛好是對的。#5 y = a|b;:t = 2 に起こされ、t = 7 まで眠り、それから評価する——a はすでに 0 に戻っているので y ← 0。t = 4 の立ち下がりはスレッドが眠っている間に到来し、失われた。ここでは失っても無害で、答えはたまたま正しい。
  • y = #5 a|b;: evaluates 1 at t = 2, blocks until t = 7, writes y ← 1. The fall at t = 4 was lost, so nothing ever writes y back to 0. The output is permanently stuck at 1. This is the dangerous one.y = #5 a|b;:在 t = 2 求出 1,阻斷到 t = 7,寫 y ← 1。t = 4 的下降被弄丟了,所以沒有任何東西把 y 寫回 0。輸出永久卡在 1。這個才是危險的那一個。y = #5 a|b;:t = 2 で 1 を評価し、t = 7 までブロックし、y ← 1 を書く。t = 4 の立ち下がりは失われたので、y を 0 に戻すものが何もない。出力は 1 に貼り付いたままになる。これが危険なほうである。
  • y <= #5 a|b;: schedules 1 at t = 7 and, after returning to the @ immediately, schedules 0 at t = 9. Faithful transport of the pulse to [7, 9).y <= #5 a|b;:把 1 排在 t = 7,立刻回到 @ 之後再把 0 排在 t = 9。脈衝被忠實地傳輸到 [7, 9)。y <= #5 a|b;:1 を t = 7 に予約し、ただちに @ へ戻ったのち 0 を t = 9 に予約する。パルスは [7, 9) へ忠実に伝送される。

Note the asymmetry: the inertial model loses the pulse but ends in the right state; the blocking intra-assignment model loses the pulse and ends in the wrong state. Missing an event and leaving the model desynchronised from reality is far worse than filtering a glitch.注意這裡的不對稱:慣性模型漏掉了脈衝,但最後停在正確的狀態;阻斷式指派內模型漏掉了脈衝,而且最後停在錯誤的狀態。漏掉一個事件、讓模型與現實失去同步,遠比濾掉一個毛刺糟糕得多。ここにある非対称性に注目したい:慣性モデルはパルスを失うが、最終的に正しい状態に落ち着く。ブロッキング代入内モデルはパルスを失い、しかも誤った状態に落ち着く。イベントを取り逃してモデルを現実から外れたままにするほうが、グリッチを濾し取るよりはるかに悪い。

Practice 9 — Repeater timing練習 9 — 重複器時序練習 9 — リピータのタイミング

Problem: For the repeater module above, when does reg_b leave x, and to what value? What would change if the statement read #10 reg_b = repeat (5) @ (negedge clock) reg_a;?題目:對於上面那個 repeater 模組,reg_b 在什麼時候離開 x?變成什麼值?如果那道敘述改成 #10 reg_b = repeat (5) @ (negedge clock) reg_a;,答案會有什麼不同?問題:上の repeater モジュールについて、reg_b はいつ x を離れ、どの値になるか。文が #10 reg_b = repeat (5) @ (negedge clock) reg_a; であったら何が変わるか。

Click to reveal solution點擊展開解答クリックで解答を表示
reg_a: 1 at t = 5, 0 at 15, 1 at 20, 0 at 40.reg_a:t = 5 時為 1、15 時為 0、20 時為 1、40 時為 0。reg_a:t = 5 で 1、15 で 0、20 で 1、40 で 0。
clock starts at 0 and toggles every 5, so rising edges occur at t = 5, 15, 25, 35, 45, 55, … and falling edges at t = 10, 20, 30, 40, 50.clock 從 0 起步,每 5 反轉一次,所以上升緣出現在 t = 5、15、25、35、45、55、…,下降緣出現在 t = 10、20、30、40、50。clock は 0 から始まり 5 ごとにトグルするので、立ち上がりエッジは t = 5、15、25、35、45、55、… に、立ち下がりエッジは t = 10、20、30、40、50 に現れる。
At t = 10 the statement starts and samples the RHS: reg_a = 1.在 t = 10 敘述開始,取樣右手邊:reg_a = 1。t = 10 で文が開始し、右辺をサンプルする:reg_a = 1。
It then waits for five rising edges strictly after t = 10: 15, 25, 35, 45, 55.接著它等嚴格晚於 t = 10 的五個上升緣:15、25、35、45、55。続いて t = 10 より厳密に後の五つの立ち上がりエッジを待つ:15、25、35、45、55。

reg_b becomes 1 at t = 55, even though reg_a has been 0 since t = 40. The value travelled, not the signal.reg_b 在 t = 55 變成 1,儘管 reg_a 從 t = 40 起就一直是 0。移動的是那個值,不是那個訊號。reg_b は t = 55 で 1 になる。reg_a は t = 40 以来ずっと 0 であるにもかかわらずである。旅をしたのは値であって信号ではない。

With negedge: the statement still starts at t = 10, which is itself a falling edge. Whether that edge counts depends on whether the initial block's statement is executed before or after the clock generator's assignment within time slot 10 — an ordering the standard does not fix. If it counts, the five edges are 10, 20, 30, 40, 50 and reg_b becomes 1 at t = 50; if it does not, they are 20, 30, 40, 50, 60 and the answer is t = 60. This is a genuine race, and the right response in a test bench is to offset the stimulus so it never coincides with a clock edge.改成 negedge 時:敘述依然在 t = 10 開始,而 t = 10 本身就是一個下降緣。那個邊緣算不算得到,取決於在時槽 10 之內,initial 區塊的敘述是在時脈產生器的指派之前還是之後執行 —— 而標準並沒有固定這個順序。如果算得到,五個邊緣就是 10、20、30、40、50,reg_b 在 t = 50 變成 1;如果算不到,就是 20、30、40、50、60,答案是 t = 60。這是真正的競爭,而測試平台裡正確的應對方式,是把激勵錯開,讓它永遠不會和時脈邊緣重合。negedge にした場合:文はやはり t = 10 で開始するが、t = 10 自体が立ち下がりエッジである。そのエッジが数えられるかどうかは、タイムスロット 10 の中で initial ブロックの文がクロック生成器の代入より前に実行されるか後かで決まり、標準はその順序を定めていない。数えられるなら五つのエッジは 10、20、30、40、50 となり reg_b は t = 50 で 1 になる。数えられないなら 20、30、40、50、60 となり答えは t = 60 である。これは正真正銘の競合であり、テストベンチでの正しい対処は、刺激をずらしてクロックエッジと決して重ならないようにすることである。

6. The Simulation Scheduling Model6. 模擬排程模型6. シミュレーションのスケジューリングモデル

6.1 The lecture's six steps, and what they mean6.1 講義中的六個步驟,以及它們的意思6.1 講義の六つのステップとその意味

Slide 30 of Part 1 compresses the whole event queue into six bullets describing one time step. Here they are, annotated with the region of §2.1 that each one corresponds to.第 1 部的第 30 張投影片把整個事件佇列壓縮成描述一個時間步的六個條目。以下就是它們,並標註每一條對應到 §2.1 的哪一個區域。パート 1 のスライド 30 は、イベントキュー全体を一つのタイムステップを述べる六つの箇条に圧縮している。以下にそれらを挙げ、各項が §2.1 のどの領域に対応するかを註記する。

  1. Evaluate expressions on the RHS. (Active) This is where every right-hand side in the time slot, blocking or not, is read.求出右手邊的運算式。(Active)這就是時槽內每一個右手邊 —— 不論阻斷與否 —— 被讀取的地方。右辺の式を評価する。(Active)タイムスロット内のあらゆる右辺が、ブロッキングであるか否かを問わず読まれるのがここである。
  2. Execute blocking assignments. (Active) The write happens immediately as part of the same event, so steps 1 and 2 interleave rather than running as two separate passes.執行阻斷式指派。(Active)寫入是同一個事件的一部分,立刻發生,所以步驟 1 與 2 是交錯進行的,而不是兩趟分開跑。ブロッキング代入を実行する。(Active)書き込みは同じイベントの一部としてただちに起こるので、ステップ 1 と 2 は二回の別々のパスではなく互い違いに進む。
  3. Execute non-blocking assignments that do not have intra-assignment timing control. (NBA) The updates whose values were read in step 1.執行沒有指派內時間控制的非阻斷式指派。(NBA)也就是那些值在步驟 1 被讀走的更新。代入内タイミング制御を持たないノンブロッキング代入を実行する。(NBA)ステップ 1 で値が読まれた更新のことである。
  4. Execute past assignments that were scheduled to execute in the current time step. (NBA) These are the a <= #d b; updates scheduled at some earlier time whose delay expires now.執行過去排定要在目前時間步執行的指派。(NBA)這些是早些時候排程、而延遲在此刻到期的 a <= #d b; 更新。現在のタイムステップで実行するよう過去に予約された代入を実行する。(NBA)これは以前にスケジュールされ、いま遅延が満了した a <= #d b; の更新である。
  5. Execute $monitor. (Postponed) With the parenthetical note that $display runs where it is encountered, which is to say in the Active region, before the non-blocking updates.執行 $monitor。(Postponed)括號裡還附註說 $display 是在遇到它的地方執行的,也就是在 Active 區域,先於非阻斷式更新。$monitor を実行する。(Postponed)括弧書きで、$display は出会った場所、すなわち Active 領域で、ノンブロッキング更新より前に走ると註記されている。
  6. Advance simulation time.推進模擬時間。シミュレーション時間を進める。
Where the slide list is incomplete投影片的清單哪裡不完整スライドの一覧が不完全な点

The list omits the Inactive (#0) region, which sits between steps 2 and 3, and it does not mention that steps 1–2 can loop many times within one time slot as blocking assignments trigger further processes. It also implies that steps 3 and 4 are distinct passes; in the standard they are one region, and the only reason the lecture separates them is to emphasise that a delayed non-blocking assignment lands in the NBA region of its target time, not of the time it was issued. Neither simplification changes any answer you will be asked for, but if a question turns on #0, use the five-region model.這份清單略掉了位在步驟 2 與 3 之間的 Inactive(#0)區域,也沒有提到步驟 1–2 可以在一個時槽之內循環很多次 —— 因為阻斷式指派會再觸發更多行程。它還暗示步驟 3 與 4 是兩趟不同的處理;在標準裡它們是同一個區域,而講義之所以把它們分開,只是為了強調:一個帶延遲的非阻斷式指派,落在它目標時間的 NBA 區域,不是落在它被發出的那個時間。這兩處簡化都不會改變你被問到的任何答案,但如果題目的關鍵在 #0,就請用五區域模型。この一覧はステップ 2 と 3 の間にある Inactive(#0)領域を省いており、ブロッキング代入がさらにプロセスを起動することでステップ 1–2 が一つのタイムスロット内で何度もループしうることにも触れていない。またステップ 3 と 4 が別々のパスであるかのように読めるが、標準では両者は一つの領域であり、講義がそれらを分けているのは、遅延つきノンブロッキング代入が発行された時刻ではなく目標時刻の NBA 領域に落ちることを強調するためにすぎない。どちらの簡略化も問われる答えを変えはしないが、設問が #0 を軸にしているなら五領域モデルを使うこと。

6.2 $display versus $monitor versus $strobe6.2 $display、$monitor 與 $strobe 的比較6.2 $display と $monitor と $strobe

These three system tasks exist precisely because of the region structure, and choosing the wrong one is the fastest way to misdiagnose a design.這三個系統任務之所以存在,正是因為區域結構,而選錯一個是誤診設計最快的途徑。この三つのシステムタスクが存在するのはまさに領域構造があるからであり、選択を誤ることは設計を誤診する最短の道である。

initial begin
  a = 1;
  b = 0;
  a <= b;
  b <= a;
  $display("a=%b b=%b", a, b);
end
// display: a=1 b=0
initial begin
  a = 1;
  b = 0;
  a <= b;
  b <= a;
  $monitor("a=%b b=%b", a, b);
end
// monitor: a=0 b=1

Both blocks perform the same swap. The left one prints a=1 b=0 because $display is an Active-region event executed where it stands, at which point the two NBA updates are still pending. The right one prints a=0 b=1 because $monitor is a Postponed-region event executed after the whole time slot has settled.兩個區塊做的是同一個交換。左邊那個印出 a=1 b=0,因為 $display 是一個 Active 區域事件,在它所在的位置就執行,而那時兩個 NBA 更新都還沒兌現。右邊那個印出 a=0 b=1,因為 $monitor 是 Postponed 區域事件,在整個時槽穩定下來之後才執行。二つのブロックは同じスワップを行う。左は a=1 b=0 と印字する。$display が Active 領域のイベントであり、書かれた位置で実行され、その時点で二つの NBA 更新がまだ保留中だからである。右は a=0 b=1 と印字する。$monitor が Postponed 領域のイベントであり、タイムスロット全体が落ち着いた後に実行されるからである。

Task任務タスクRegion區域領域Fires發動時機発火のしかたUse it for適用於用途
$displayActiveActiveActiveOnce, where it stands一次,在它所在的位置一度、書かれた位置でTracing control flow; printing a message at a known point in a sequence. Never for checking the result of a non-blocking assignment.追蹤控制流程;在一段序列中某個已知的點印出訊息。絕不要拿來檢查非阻斷式指派的結果。制御フローの追跡、一連の処理の既知の地点でのメッセージ出力。ノンブロッキング代入の結果を調べるのに使ってはならない。
$strobePostponedPostponedPostponedOnce, at the end of the current time slot一次,在目前時槽結束時一度、現在のタイムスロットの終わりにPrinting the settled state at a particular instant — the correct tool for "what did the flops load on this edge?"印出某個瞬間穩定下來的狀態 —— 要問「這個邊緣上正反器載進了什麼?」,它才是正確的工具。ある瞬間に落ち着いた状態の出力——「このエッジでフリップフロップは何を取り込んだか」を問うのに正しい道具である。
$monitorPostponedPostponedPostponedOnce when armed, then at the end of every time slot in which an argument changed啟動時一次,之後在每一個有引數改變的時槽結束時各一次起動時に一度、その後は引数が変化したタイムスロットの終わりごとに一度A continuous trace of a small set of signals. Only one $monitor can be active at a time; a second call replaces the first.對一小組訊號做連續追蹤。同一時間只能有一個 $monitor 作用中;第二次呼叫會取代第一次。少数の信号を継続的に追跡する用途。$monitor は同時に一つしか有効にならず、二度目の呼び出しが最初のものを置き換える。
$writeActiveActiveActiveOnce, where it stands一次,在它所在的位置一度、書かれた位置でSame as $display but without the trailing newline.與 $display 相同,但不加結尾換行。$display と同じだが、末尾の改行を付けない。

Two further details that bite in the lab. $monitor is a singleton: arming a second one silently disables the first, and $monitoroff/$monitoron exist to suspend it around noisy phases. And $strobe and $monitor both run after the NBA region, so if a $strobe argument is written by a delayed non-blocking assignment landing in a later time slot, the $strobe will show the old value — being in the Postponed region only protects you against same-slot NBA updates.還有兩個在實驗室裡會咬人的細節。$monitor 是單例:啟動第二個會默默停掉第一個,而 $monitoroff/$monitoron 的存在就是為了在吵雜的階段前後把它暫停。另外 $strobe 與 $monitor 都在 NBA 區域之後才跑,所以如果某個 $strobe 的引數是由一個落在較晚時槽的延遲非阻斷式指派所寫的,那麼 $strobe 顯示的會是舊值 —— 待在 Postponed 區域只保護你不受同一時槽的 NBA 更新所害。実験室で噛みつく細部がさらに二つある。$monitor はシングルトンである:二つ目を起動すると最初のものが黙って無効になり、$monitoroff/$monitoron は騒がしい局面の前後でそれを止めるために存在する。また $strobe と $monitor はどちらも NBA 領域の後に走るので、ある $strobe の引数がより後のタイムスロットに落ちる遅延つきノンブロッキング代入によって書かれる場合、$strobe は古い値を示す——Postponed 領域にいることが守ってくれるのは、同一スロット内の NBA 更新に対してだけである。

6.3 Zero-delay loops and the meaning of #06.3 零延遲迴圈與 #0 的意義6.3 ゼロ遅延ループと #0 の意味

#0 is not "no delay". It is "move this event from the Active region to the Inactive region of the same time slot", which means: run after everything that is currently pending, but before any non-blocking update and without advancing time. It is a way of saying "let the others go first".#0 不是「沒有延遲」。它是「把這個事件從 Active 區域搬到同一時槽的 Inactive 區域」,意思是:在目前所有待處理的東西之後才跑,但在任何非阻斷式更新之前,而且不推進時間。它是一種說「讓別人先走」的方式。#0 は「遅延なし」ではない。「このイベントを Active 領域から同じタイムスロットの Inactive 領域へ移す」という意味であり、すなわち現在保留中のすべての後に走り、しかしどのノンブロッキング更新よりも前に走り、時間は進めない、ということである。「他の者に先に行かせる」と言うための手段である。

The carry look-ahead adder of §7.6 uses it for exactly that reason. The always block computes the carry chain from g and p, which are produced by continuous assignments. When a changes, both the continuous assignments and the always block become active in the same time slot, in unspecified order. Without the #0, the block might read the old g and p and compute a carry chain from stale data, which then gets corrected on the next pass — producing a transient glitch at best, and on some simulators a stable wrong answer. The #0 defers the block until the Active region is empty, by which time g and p have settled.§7.6 的超前進位加法器正是為了這個理由而用它。那個 always 區塊從 g 與 p 算出進位鏈,而 g 與 p 是由連續指派產生的。當 a 改變時,連續指派與 always 區塊在同一個時槽一起變成作用中,順序未定義。沒有 #0 的話,區塊可能讀到舊的 g 與 p,用過時的資料算出一條進位鏈,然後在下一趟才被更正 —— 好一點的情況是產生一個暫態毛刺,在某些模擬器上則是一個穩定的錯誤答案。#0 把這個區塊推遲到 Active 區域空了之後,而那時 g 與 p 已經穩定。§7.6 のキャリー先見加算器はまさにこの理由でこれを使っている。その always ブロックは g と p からキャリーチェーンを計算するが、g と p は連続代入が生む。a が変化すると、連続代入と always ブロックが同じタイムスロットで順序未定義のままアクティブになる。#0 がなければ、ブロックは古い g と p を読んで陳腐なデータからキャリーチェーンを計算し、次のパスで訂正されることになる——よくて一過性のグリッチ、シミュレータによっては安定した誤答が出る。#0 はこのブロックを Active 領域が空になるまで先送りし、その時点では g と p は落ち着いている。

#0 is a smell, not a solution#0 是一種氣味,不是解法#0 は臭いであって解決ではない

Using #0 to fix an ordering problem means you have a race that you have papered over rather than removed. It does not work if two processes both use #0, it is not synthesisable, and it changes the Inactive region into a second unordered free-for-all. The structural fix for the adder is to compute g and p inside the same block, or to use non-blocking assignment so the ordering cannot matter. The lecture's use of #0 is a historical idiom worth recognising and not worth copying.用 #0 去修一個順序問題,代表你把一個競爭糊起來了,而不是把它移除了。如果兩個行程都用 #0 它就失效,它不可合成,而且它把 Inactive 區域變成第二個無序的混戰場。加法器在結構上的正確修法,是把 g 與 p 算在同一個區塊裡,或是改用非阻斷式指派讓順序根本無從影響結果。講義裡用 #0 是一個值得認得、但不值得照抄的歷史慣用法。#0 で順序の問題を直すというのは、競合を取り除いたのではなく上塗りしたということである。二つのプロセスがどちらも #0 を使えば効かず、合成もできず、しかも Inactive 領域を二つ目の無秩序な乱戦の場に変えてしまう。この加算器に対する構造的な正しい直し方は、g と p を同じブロック内で計算するか、順序が影響しえないようノンブロッキング代入を使うことである。講義における #0 の使用は、見分けられるようになる価値はあっても真似する価値はない歴史的な慣用である。

Practice 10 — Predict the printout練習 10 — 預測印出結果練習 10 — 出力を予測する

Problem: Give the exact output, in order.題目:請依序給出確切的輸出。問題:正確な出力を順序どおりに示せ。

initial begin
  x = 0;
  x <= 1;
  $display("D  x=%b", x);
  $strobe ("S  x=%b", x);
  #0 $display("D0 x=%b", x);
  #1 $display("D1 x=%b", x);
end
Click to reveal solution點擊展開解答クリックで解答を表示
D  x=0
D0 x=0
S  x=1
D1 x=1
D — Active region of time 0, before the NBA update. x is still 0.D —— 時間 0 的 Active 區域,在 NBA 更新之前。x 仍然是 0。D —— 時刻 0 の Active 領域、NBA 更新より前。x はまだ 0 である。
D0 — the #0 moves this to the Inactive region, which still precedes NBA. Prints 0. This is the line that catches people who read #0 as "end of time step".D0 —— #0 把它搬到 Inactive 區域,而那仍然在 NBA 之前。印出 0。這一行專門逮那些把 #0 讀成「時間步結尾」的人。D0 —— #0 がこれを Inactive 領域へ移すが、そこはまだ NBA より前である。0 を印字する。この行は #0 を「タイムステップの終わり」と読む人を捕まえる。
S — Postponed region of time 0, after NBA. Prints 1. Note it appears after D0 in the output even though it is textually earlier, because the Postponed region runs last.S —— 時間 0 的 Postponed 區域,在 NBA 之後。印出 1。注意它在輸出中出現在 D0 之後,儘管它在文字上比較早,因為 Postponed 區域最後才跑。S —— 時刻 0 の Postponed 領域、NBA の後。1 を印字する。記述上は先にあるのに出力では D0 の後に現れる点に注意したい。Postponed 領域が最後に走るからである。
D1 — time 1. Prints 1.D1 —— 時間 1。印出 1。D1 —— 時刻 1。1 を印字する。

If $strobe were replaced by $monitor, you would additionally get a second monitor line at the end of any later time slot in which x changed.如果把 $strobe 換成 $monitor,你還會在之後每一個 x 有變化的時槽結尾多拿到一行 monitor 輸出。$strobe を $monitor に置き換えれば、以降 x が変化したタイムスロットの終わりごとに monitor の行がさらに一行ずつ加わる。

7. Activity Flow Control7. 活動流程控制7. アクティビティのフロー制御

Everything so far has been about when a thread runs. This section is about which statements it runs: the conditional operator, the three case variants, if ... else, the four loops, and disable. All of them are ordinary structured-programming constructs, and the only reason they need a section of their own is that each one has a synthesis consequence that has no counterpart in software.到目前為止談的都是執行緒什麼時候跑。這一節談的是它跑哪些敘述:條件運算子、三種 case 變體、if ... else、四種迴圈,以及 disable。它們全都是普通的結構化程式設計構造,而它們之所以需要獨立一節,唯一的理由是每一個都帶有一個在軟體裡找不到對應物的合成後果。ここまではスレッドがいつ走るかの話だった。この節はスレッドがどの文を走らせるかの話である:条件演算子、三種類の case、if ... else、四種類のループ、そして disable。どれもありふれた構造化プログラミングの構文であり、これらに独立した節が要る理由はただ一つ、どれもソフトウェアには対応物のない合成上の帰結を伴うことである。

7.1 The conditional operator7.1 條件運算子7.1 条件演算子

cond ? a : b is an expression, so it can appear anywhere an expression can — on the right of a continuous assignment, as a module port connection, inside a bigger expression, and (the point of slide 33) on the right of a procedural assignment.cond ? a : b 是一個運算式,所以它可以出現在任何運算式能出現的地方 —— 連續指派的右邊、模組埠的連接、一個更大的運算式裡面,以及(第 33 張投影片的重點)程序性指派的右邊。cond ? a : b は式なので、式が書ける場所ならどこにでも書ける——連続代入の右辺、モジュールのポート接続、より大きな式の内部、そして(スライド 33 の要点)手続き型代入の右辺である。

always @ (posedge clock)
  q = s ? a : b;

Two behaviours worth knowing. First, if cond evaluates to x or z, the operator does not pick a branch arbitrarily — it evaluates both arms and returns their bitwise merge, where matching bits pass through and differing bits become x. So with s = 1'bx, a = 4'b1100 and b = 4'b1010, the result is 4'b1xx0. That is a genuinely useful property: an unknown select on a mux produces unknowns only on the bits that actually disagree, which keeps x propagation from exploding. Second, synthesis maps a chain of nested ?: to a priority mux, exactly like a chain of else if, so the usual area-versus-depth trade-off applies.有兩個行為值得知道。第一,如果 cond 求值為 x 或 z,這個運算子不會任意挑一邊 —— 它會把兩邊都求出來,然後回傳它們的逐位元合併:相同的位元照樣通過,不同的位元變成 x。所以在 s = 1'bx、a = 4'b1100、b = 4'b1010 時,結果是 4'b1xx0。這是一個真正有用的性質:多工器上一個未知的選擇訊號,只會在真正不一致的位元上產生未知值,這能避免 x 的傳播爆炸開來。第二,合成會把一串巢狀的 ?: 對應成一個優先權多工器,與一串 else if 完全相同,所以通常的面積與深度取捨照樣適用。知っておく価値のある振る舞いが二つある。第一に、cond が x や z と評価された場合、この演算子はどちらかの腕を勝手に選んだりしない——両方を評価し、ビットごとのマージを返す。一致するビットはそのまま通り、食い違うビットは x になる。したがって s = 1'bx、a = 4'b1100、b = 4'b1010 なら結果は 4'b1xx0 である。これは本当に有用な性質である:マルチプレクサの選択線が未知でも、実際に食い違うビットにしか未知値が出ないので、x の伝播が爆発せずに済む。第二に、合成は入れ子になった ?: の連鎖を、else if の連鎖とまったく同じく優先順位マルチプレクサに対応づけるので、通常の面積対段数のトレードオフがそのまま当てはまる。

7.2 The case statement7.2 case 敘述7.2 case 文

case compares the selector expression against each item in turn and executes the first that matches. "Match" means a complete bit-wise identity over all four values: a selector bit of x matches only an item bit of x, and a selector bit of z matches only z.case 把選擇運算式依序與每一個項目比對,執行第一個相符的。「相符」的意思是在四種值上逐位元完全相同:選擇式的某個位元若是 x,就只與項目中同樣是 x 的位元相符;若是 z,就只與 z 相符。case は選択式を各項目と順に比較し、最初に一致したものを実行する。「一致」とは四値すべてにわたるビット単位の完全一致を意味する:選択式のあるビットが x なら項目側の x とだけ一致し、z なら z とだけ一致する。

always @ (a or b or c or d or select)
begin
  case (select)
    0: y = a;
    1: y = b;
    2: y = c;
    3: y = d;
    default: y = 1'bx;
  endcase
end

The slide's first bullet — "expression and case item must have the same bit length" — deserves expansion, because what actually happens is subtler than an error. Verilog zero-extends the narrower operand to the width of the wider, so comparing a 2-bit select against the unsized literal 0 works fine. The trouble starts with x and z: an unsized literal such as 'bx is extended with x, while a sized one such as 2'bx is 2'bxx, and 4'b01 is 4'b0001. Mismatched widths therefore do not produce a compile error; they produce items that can never match. Always size your case items.投影片的第一個條目 ——「運算式與 case 項目必須有相同的位元長度」—— 值得展開,因為實際發生的事比一個錯誤訊息微妙得多。Verilog 會把較窄的運算元零延伸到較寬者的寬度,所以拿一個 2 位元的 select 去比對無尺寸字面值 0 完全沒問題。麻煩從 x 與 z 開始:像 'bx 這種無尺寸字面值會用 x 延伸,而像 2'bx 這種有尺寸的就是 2'bxx,4'b01 則是 4'b0001。所以寬度不符並不會產生編譯錯誤;它產生的是永遠不可能相符的項目。請一律替你的 case 項目標上尺寸。スライドの最初の箇条——「式と case 項目はビット長が同じでなければならない」——は展開しておく価値がある。実際に起きることはエラーよりずっと微妙だからである。Verilog は狭いほうのオペランドを広いほうの幅までゼロ拡張するので、2 ビットの select をサイズなしリテラル 0 と比較するのは問題なく動く。やっかいなのは x と z である:'bx のようなサイズなしリテラルは x で拡張されるが、2'bx のようなサイズ付きは 2'bxx であり、4'b01 は 4'b0001 である。したがって幅の不一致はコンパイルエラーを生まず、決して一致しえない項目を生む。case 項目には必ずサイズを付けること。

The second bullet is the one that matters for the back end. If the case list is incomplete — some selector values hit no item and there is no default — then in a combinational block the variable keeps its previous value for those inputs, and "keeps its previous value" is the definition of a latch. Synthesis duly infers one.第二個條目才是對後端真正重要的。如果 case 列表不完整 —— 有些選擇值對不到任何項目,而且沒有 default —— 那麼在組合邏輯區塊裡,變數就會對那些輸入保留它先前的值,而「保留先前的值」正是閂鎖的定義。合成於是照辦,推斷出一個閂鎖。後段工程にとって本当に重要なのは二つ目の箇条である。case のリストが不完全で——どの項目にも当たらない選択値があり、default もない——場合、組合せ論理ブロックではそれらの入力に対して変数が以前の値を保つ。そして「以前の値を保つ」はラッチの定義である。合成はそのとおりにラッチを推論する。

The slide crosses out the word "latch". Here is the precise rule.投影片把「latch」這個字畫掉了。以下是精確的規則。スライドは「latch」という語に×印をつけている。以下が正確な規則である。

An incomplete case only infers a latch in a combinational block — one triggered by always @* or a level-sensitive list. In a clocked block (always @(posedge clk)) an incomplete case is perfectly fine: the target is already a flip-flop, and "keeps its previous value" is what a flip-flop with no load enable does. No extra hardware appears.不完整的 case 只有在組合邏輯區塊裡才會推斷出閂鎖 —— 也就是由 always @* 或一個電位敏感列表觸發的區塊。在時脈同步區塊(always @(posedge clk))裡,不完整的 case 完全沒問題:目標本來就是一個正反器,而「保留先前的值」正是一個沒有載入致能的正反器會做的事。不會多出任何硬體。不完全な case がラッチを推論させるのは組合せ論理ブロック——always @* またはレベル感応リストで起動されるブロック——の中だけである。クロック同期ブロック(always @(posedge clk))では不完全な case はまったく問題ない:対象はもともとフリップフロップであり、「以前の値を保つ」はロードイネーブルのないフリップフロップがすることそのものである。余分なハードウェアは現れない。

So the defensive rule is narrower than "always write default": in combinational blocks, cover every case or assign a default value to every output at the top of the block; in clocked blocks, write only the branches you mean. A latch in an ASIC flow is genuinely bad news — it is level-sensitive, static timing analysis has to treat it as a transparent window rather than a clean endpoint, and scan insertion cannot test through it.所以防禦性的規則比「永遠要寫 default」更窄:在組合邏輯區塊裡,要嘛涵蓋每一種情況,要嘛在區塊開頭給每一個輸出指派一個預設值;在時脈同步區塊裡,只寫你真正想要的分支。在 ASIC 流程裡出現閂鎖是真正的壞消息 —— 它是電位敏感的,靜態時序分析必須把它當成一個透明視窗而不是一個乾淨的端點,而且掃描插入無法穿過它做測試。したがって防御的な規則は「つねに default を書け」よりも狭い:組合せ論理ブロックではすべての場合を網羅するか、ブロックの先頭ですべての出力に既定値を代入する。クロック同期ブロックでは意図した分岐だけを書く。ASIC フローでラッチが現れるのは本当に悪い知らせである——レベル感応であり、静的タイミング解析はそれをきれいな終端ではなく透過する窓として扱わねばならず、スキャン挿入もそこを通して試験できない。

7.3 casex and casez7.3 casex 與 casez7.3 casex と casez

These two variants relax the matching rule by declaring certain bit values to be "don't care" — and, crucially, the don't-care applies to both sides of the comparison, not just to the items.這兩個變體宣告某些位元值為「不在乎」,藉此放寬比對規則 —— 而關鍵在於,這個「不在乎」適用於比較的兩邊,不是只適用於項目那一邊。この二つの変種は、特定のビット値を「ドントケア」と宣言することで一致規則を緩める——そして肝心なのは、そのドントケアが比較の両辺に適用され、項目側だけではないという点である。

Statement敘述文Bit values treated as don't care被當成不在乎的位元值ドントケアとして扱われるビット値In items項目中項目側In the selector選擇式中選択式側
casenone無なし——
casezz (and ?, which is a synonym for z in a literal)z(以及 ?,在字面值裡它是 z 的同義字)z(および ?。リテラル中では z の同義語である)yes是はいyes是はい
casexx and z (and ?)x 與 z(以及 ?)x と z(および ?)yes是はいyes是はい
always @ (opcode or a or b or c)
casex (opcode)
  4'b1zzx : begin  // bits 2:0 are don't care
              out = a;
            end
  4'b01?? : begin  // bits 1:0 are don't care
              out = b;
            end
  4'b001? : begin  // bit 0 is don't care
              out = c;
            end
  default : begin
              out = 4'b0;
            end
endcase

Read the items as masks. 4'b1zzx means "bit 3 must be 1, the rest are free"; 4'b01?? means "bits 3:2 must be 01"; 4'b001? means "bits 3:1 must be 001". Because items are tested top to bottom and the first match wins, the three masks are implicitly prioritised, and the design works out to a leading-one detector over the opcode.把這些項目讀成遮罩。4'b1zzx 的意思是「位元 3 必須是 1,其餘自由」;4'b01?? 的意思是「位元 3:2 必須是 01」;4'b001? 的意思是「位元 3:1 必須是 001」。因為項目是由上往下測試、第一個相符者勝出,這三個遮罩就隱含了優先權,而整個設計算下來等於是對 opcode 做一個前導 1 偵測器。項目はマスクとして読むとよい。4'b1zzx は「ビット 3 は 1、残りは自由」、4'b01?? は「ビット 3:2 は 01」、4'b001? は「ビット 3:1 は 001」を意味する。項目は上から順に判定され最初に一致したものが勝つので、三つのマスクには暗黙の優先順位がつき、設計全体としては opcode に対する先行 1 検出器になる。

Why casex is dangerous and casez usually is not為什麼 casex 危險,而 casez 通常不危險casex が危険で casez はたいてい危険でない理由

The don't-care is symmetric. With casex, a selector bit of x matches anything, so an opcode of 4'bxxxx — which in real life means "this register was never reset" or "this bus is floating" — matches the very first item and the design appears to work. The bug is invisible in simulation and survives into silicon.那個「不在乎」是對稱的。用 casex 時,選擇式的某個位元若是 x,它就與任何東西相符,所以一個 4'bxxxx 的 opcode —— 在現實中代表「這個暫存器從來沒被重置」或「這條匯流排在浮接」—— 會與第一個項目相符,而設計看起來一切正常。這個臭蟲在模擬裡是隱形的,而且會一路活到矽片上。このドントケアは対称である。casex では選択式のビットが x なら何とでも一致するので、4'bxxxx という opcode——現実には「このレジスタは一度もリセットされていない」か「このバスがフロートしている」を意味する——が最初の項目に一致し、設計は正常に見えてしまう。このバグはシミュレーションでは不可視で、そのままシリコンまで生き延びる。

casez has the same hazard in principle, but a z on an internal selector is far rarer than an x, so in practice it behaves. Write casez with ? for don't-cares, reserve casex for the rare cases where you genuinely want x to be wild, and consider adding an explicit if (^opcode === 1'bx) $display("unknown opcode"); assertion in the test bench.casez 原理上有同樣的危險,但內部選擇訊號上出現 z 比出現 x 罕見得多,所以實務上它表現得還好。請用 casez 搭配 ? 來寫不在乎,把 casex 留給你真的希望 x 當萬用字元的罕見情況,並考慮在測試平台裡加上一個明確的 if (^opcode === 1'bx) $display("unknown opcode"); 斷言。casez も原理上は同じ危険を持つが、内部の選択信号に z が現れることは x よりはるかにまれなので、実際には行儀よく振る舞う。ドントケアには casez と ? を使い、casex は本当に x をワイルドにしたいまれな場合に取っておき、テストベンチには明示的な if (^opcode === 1'bx) $display("unknown opcode"); のアサーションを加えることを検討するとよい。

Practice 11 — casex matching練習 11 — casex 的比對練習 11 — casex のマッチング

Problem: For the casex above, what is out when opcode = (a) 4'b1010 (b) 4'b0110 (c) 4'b0011 (d) 4'b0001 (e) 4'b0x10 (f) 4'bxxxx? (g) Which answers change if casex is replaced by casez?題目:對於上面那個 casex,當 opcode = (a) 4'b1010 (b) 4'b0110 (c) 4'b0011 (d) 4'b0001 (e) 4'b0x10 (f) 4'bxxxx 時,out 各是什麼?(g) 如果把 casex 換成 casez,哪些答案會變?問題:上の casex について、opcode = (a) 4'b1010 (b) 4'b0110 (c) 4'b0011 (d) 4'b0001 (e) 4'b0x10 (f) 4'bxxxx のとき out はそれぞれ何か。(g) casex を casez に替えるとどの答えが変わるか。

Click to reveal solution點擊展開解答クリックで解答を表示

(a) a — bit 3 is 1, matches 1zzx.(a) a —— 位元 3 是 1,與 1zzx 相符。(a) a —— ビット 3 が 1 なので 1zzx に一致する。

(b) b — bits 3:2 are 01, matches 01??.(b) b —— 位元 3:2 是 01,與 01?? 相符。(b) b —— ビット 3:2 が 01 なので 01?? に一致する。

(c) c — bits 3:1 are 001, matches 001?.(c) c —— 位元 3:1 是 001,與 001? 相符。(c) c —— ビット 3:1 が 001 なので 001? に一致する。

(d) 4'b0 — bits 3:1 are 000, no item matches, falls to default.(d) 4'b0 —— 位元 3:1 是 000,沒有項目相符,落到 default。(d) 4'b0 —— ビット 3:1 が 000 でどの項目にも一致せず、default に落ちる。

(e) b — bit 3 is 0 so the first item fails; bit 2 is x, which casex treats as wild, so 01?? matches. (It would also match 001?, but items are tested in order.)(e) b —— 位元 3 是 0,所以第一個項目失敗;位元 2 是 x,而 casex 把它當成萬用字元,所以 01?? 相符。(它同樣也會與 001? 相符,但項目是依序測試的。)(e) b —— ビット 3 が 0 なので最初の項目は外れる。ビット 2 は x で、casex はこれをワイルドと扱うので 01?? に一致する。(001? にも一致するが、項目は順に判定される。)

(f) a — every selector bit is wild, so the first item matches. This is the hazard: a completely unknown opcode silently selects a.(f) a —— 選擇式的每一個位元都是萬用字元,所以第一個項目相符。這就是那個危險:一個完全未知的 opcode 會默默選中 a。(f) a —— 選択式のすべてのビットがワイルドなので最初の項目に一致する。これが例の危険である:完全に未知の opcode が黙って a を選んでしまう。

(g) With casez, (a)–(d) are unchanged. In (e) the x in bit 2 is no longer wild: it fails to match the 1 of 01?? and the 0 of 001?, so out = 4'b0 via default. In (f) nothing matches and out = 4'b0. Both of these are much better diagnostics than silently selecting a.(g) 改用 casez 時,(a)–(d) 不變。(e) 裡位元 2 的 x 不再是萬用字元:它對不上 01?? 的 1,也對不上 001? 的 0,所以 out 經由 default 成為 4'b0。(f) 裡沒有任何項目相符,out = 4'b0。這兩個結果作為診斷訊息,都比默默選中 a 好太多。(g) casez にすると (a)–(d) は変わらない。(e) ではビット 2 の x がもはやワイルドではなく、01?? の 1 にも 001? の 0 にも一致しないので、out は default 経由で 4'b0 となる。(f) ではどれにも一致せず out = 4'b0 である。どちらも、黙って a を選ぶよりはるかに良い診断である。

7.4 if ... else

Two rules from slide 36, both of which have consequences elsewhere in the lecture.第 36 張投影片有兩條規則,兩條都在本講其他地方產生後果。スライド 36 には二つの規則があり、どちらも本講義の他の箇所に帰結を持つ。

always @ (posedge clk) begin
  // if reset is asserted
  if (reset == 1'b0) begin
    counter <= 4'b0000;
  // if counter is enabled and up count is asserted
  end else if (enable == 1'b1 && up_en == 1'b1) begin
    counter <= counter + 1'b1;
  // if counter is enabled and down count is asserted
  end else if (enable == 1'b1 && down_en == 1'b1) begin
    counter <= counter - 1'b1;
  // if counting is disabled
  end else begin
    counter <= counter;   // redundant code
  end
end

Three observations. The final branch is marked "redundant code" on the slide and it is: in a clocked block a register holds its value automatically, so counter <= counter; costs nothing and says nothing. Writing it is harmless and some coding standards require it for symmetry. In a combinational block the same line would be actively harmful — it is the textbook way to write a latch on purpose.三點觀察。最後那個分支在投影片上被標為「多餘的程式碼」,而它確實是:在時脈同步區塊裡,暫存器本來就會自動保住它的值,所以 counter <= counter; 不花成本,也什麼都沒說。寫它無害,而且有些程式碼規範為了對稱還要求寫。但在組合邏輯區塊裡,同樣這一行就會積極地造成傷害 —— 它正是教科書上「故意寫出一個閂鎖」的方法。観察が三つある。最後の分岐はスライドで「冗長なコード」と記されており、実際そのとおりである:クロック同期ブロックではレジスタは自動的に値を保つので、counter <= counter; はコストもかからず何も語らない。書いても害はなく、対称性のためにこれを要求するコーディング規約もある。しかし組合せ論理ブロックでは同じ一行が積極的に害をなす——これは教科書どおりの「わざとラッチを書く」方法だからである。

The chain is a priority structure, not a parallel one: if both up_en and down_en are asserted, up-count wins, and the synthesised logic is a cascade of 2-to-1 muxes whose depth grows with the number of branches. If the conditions are known to be mutually exclusive, a case with a (* parallel_case *)-style structure, or simply a case ({up_en, down_en}), will give the tool permission to build a flat mux instead.這個鏈是一個優先權結構,不是平行結構:如果 up_en 與 down_en 同時成立,上數勝出,而合成出來的邏輯是一串 2 選 1 多工器,其深度隨分支數成長。如果這些條件已知互斥,改用帶 (* parallel_case *) 風格結構的 case,或乾脆寫成 case ({up_en, down_en}),就等於給工具許可去建一個扁平的多工器。この連鎖は並列構造ではなく優先順位構造である:up_en と down_en が同時にアサートされればアップカウントが勝ち、合成される論理は 2 対 1 マルチプレクサの縦続で、その段数は分岐数とともに増える。条件が互いに排他的だと分かっているなら、(* parallel_case *) 風の構造を持つ case にするか、単に case ({up_en, down_en}) と書けば、ツールに平坦なマルチプレクサを作る許可を与えることになる。

Finally, note that reset is tested inside a block sensitive only to posedge clk, so this is a synchronous reset. Moving it to the sensitivity list as always @(posedge clk or negedge reset) would make it asynchronous and change the cell the tool picks. Lecture 6 covers the trade-off.最後要注意,reset 是在一個只對 posedge clk 敏感的區塊裡被測試的,所以這是同步重置。把它搬到敏感度列表裡變成 always @(posedge clk or negedge reset),就會讓它變成非同步的,也會改變工具挑選的單元。第 6 講會談這個取捨。最後に、reset が posedge clk にしか感応しないブロックの中で判定されている点に注意したい。つまりこれは同期リセットである。これを感度リストへ移して always @(posedge clk or negedge reset) とすれば非同期になり、ツールが選ぶセルも変わる。このトレードオフは第 6 講で扱う。

7.5 The four loops7.5 四種迴圈7.5 四種類のループ

Verilog has repeat, for, while and forever. For simulation they behave as you expect. For synthesis there is exactly one rule that matters: a loop is synthesisable only if the tool can unroll it into a fixed number of iterations at elaboration time. A loop whose bound depends on a signal value is not synthesisable, because hardware has no program counter.Verilog 有 repeat、for、while 與 forever。就模擬而言,它們的行為一如你所預期。就合成而言,真正重要的規則只有一條:一個迴圈只有在工具能夠於展開階段把它攤成固定次數的迭代時,才是可合成的。界限取決於訊號數值的迴圈不可合成,因為硬體沒有程式計數器。Verilog には repeat、for、while、forever がある。シミュレーションでは予想どおりに振る舞う。合成について重要な規則はただ一つ:ループが合成可能なのは、エラボレーション時にツールが固定回数の反復へ展開できる場合だけである。境界が信号の値に依存するループは合成できない。ハードウェアにプログラムカウンタはないからである。

repeat — a fixed countrepeat — 固定次數repeat — 固定回数
parameter memory_size = 1024;
...
word_address = 0;
repeat (memory_size)
  begin
    memory[word_address] = 0;
    word_address = word_address + 1;
  end
while — count the ones in awhile — 數 a 裡有幾個 1while — a の中の 1 を数える
begin
  reg [7:0] temp;
  counter = 0;
  temp = a;
  while (temp)
    begin
      if (temp[0]) counter = counter + 1;
      // or: counter = counter + temp[0];
      temp = temp >> 1;
    end
end

The repeat example is a memory initialiser: 1024 iterations known at compile time, but it writes a memory array, so it belongs in a test bench or in the behavioural model of a RAM, not in RTL. The while example is a population counter and is the more instructive one. Its trip count depends on the position of the most significant 1 in a, so it runs between 0 and 8 times. A synthesis tool cannot unroll it. Rewriting it as a for (i = 0; i < 8; i = i + 1) counter = counter + a[i]; makes the bound static, unrolls to an 8-input adder tree, and synthesises fine.repeat 這個例子是一個記憶體初始化器:1024 次迭代在編譯期就已知,但它寫的是一個記憶體陣列,所以它該待在測試平台或 RAM 的行為模型裡,不該待在 RTL 裡。while 那個例子是一個人口計數器,也更有教育意義。它的迴圈次數取決於 a 裡最高位 1 的位置,所以會跑 0 到 8 次。合成工具攤不開它。把它改寫成 for (i = 0; i < 8; i = i + 1) counter = counter + a[i];,界限就變成靜態的,會攤成一棵 8 輸入的加法樹,合成得很順利。repeat の例はメモリ初期化である:1024 回の反復はコンパイル時に分かっているが、メモリ配列に書き込むので、居場所は RTL ではなくテストベンチか RAM の振る舞いモデルである。while の例はポピュレーションカウンタで、こちらのほうが教育的である。繰り返し回数は a の最上位の 1 の位置に依存し、0 回から 8 回まで変わる。合成ツールはこれを展開できない。for (i = 0; i < 8; i = i + 1) counter = counter + a[i]; と書き直せば境界が静的になり、8 入力の加算器ツリーへ展開されて問題なく合成される。

for — integer indexfor — 用 integer 當索引for — integer のインデックス
reg [15:0] data;
integer k;

for (k = 4; k > 0; k = k - 1)
  begin
    data[k+10] = 0;
    data[k+2]  = 1;
  end
for — 4-bit index (trap!)for — 用 4 位元索引(陷阱!)for — 4 ビットのインデックス(落とし穴!)
reg [3:0] k;

for (k = 0; k <= 15; k = k + 1)
  ...

The right-hand loop never terminates. A 4-bit k can hold 0 through 15, so k <= 15 is a tautology; when k is 15, k + 1 wraps to 0 and the loop starts over. The simulator spins forever at the current simulation time and the run appears to hang. The fix is to widen the index — integer k; gives you 32 bits — or to change the bound to k < 15 and handle the last iteration separately. This failure mode is not specific to loop indices: any comparison between a counter and the maximum value its width can represent is a tautology.右邊那個迴圈永遠不會結束。一個 4 位元的 k 能裝 0 到 15,所以 k <= 15 是恆真式;當 k 是 15 時,k + 1 繞回 0,迴圈重新開始。模擬器在目前的模擬時間上永遠空轉,執行看起來就像當掉了。修法是把索引加寬 —— integer k; 給你 32 位元 —— 或把界限改成 k < 15 再另外處理最後一次迭代。這種失效模式不是迴圈索引獨有的:任何把計數器與它的位寬所能表示的最大值做比較,都是恆真式。右のループは決して終わらない。4 ビットの k は 0 から 15 までしか取れないので k <= 15 は恒真であり、k が 15 のとき k + 1 は 0 に巻き戻ってループが最初からやり直される。シミュレータは現在のシミュレーション時刻のまま永久に空回りし、実行はハングしたように見える。直し方はインデックスを広げる(integer k; なら 32 ビット)か、境界を k < 15 にして最後の反復を別扱いにすることである。この失敗様式はループインデックスに限らない:カウンタとその幅で表せる最大値との比較は、どれも恒真式である。

Practice 12 — Loop traps練習 12 — 迴圈陷阱練習 12 — ループの落とし穴

(a) Which bits of data does the left-hand for loop of §7.5 write, and to what? (b) What happens with the 4-bit index loop? (c) With a = 8'b1011_0010, what does the while loop leave in counter, and how many iterations does it run? (d) Which of the four loops in §7.5 are synthesisable as written?(a) §7.5 左邊那個 for 迴圈寫的是 data 的哪些位元?寫成什麼?(b) 4 位元索引那個迴圈會發生什麼事?(c) 當 a = 8'b1011_0010 時,while 迴圈在 counter 裡留下什麼?它跑了幾次迭代?(d) §7.5 那四個迴圈裡,依原樣來看哪些是可合成的?(a) §7.5 の左の for ループは data のどのビットに何を書くか。(b) 4 ビットインデックスのループでは何が起こるか。(c) a = 8'b1011_0010 のとき while ループは counter に何を残し、何回反復するか。(d) §7.5 の四つのループのうち、書かれたままで合成可能なのはどれか。

Click to reveal solution點擊展開解答クリックで解答を表示

(a) k takes 4, 3, 2, 1. data[k+10] covers bits 14, 13, 12, 11 and is set to 0; data[k+2] covers bits 6, 5, 4, 3 and is set to 1. Bits 15, 10..7, 2..0 are untouched and keep whatever they held.(a) k 依序取 4、3、2、1。data[k+10] 涵蓋位元 14、13、12、11,被設為 0;data[k+2] 涵蓋位元 6、5、4、3,被設為 1。位元 15、10..7、2..0 沒被動到,保留原本的值。(a) k は 4、3、2、1 の順に値を取る。data[k+10] はビット 14、13、12、11 を覆って 0 に設定し、data[k+2] はビット 6、5、4、3 を覆って 1 に設定する。ビット 15、10..7、2..0 は触れられず、元の値を保つ。

(b) Infinite loop at zero simulation time. With reg [3:0] k, the condition k <= 15 can never be false, and k + 1 wraps 15→0. The simulator hangs.(b) 在零模擬時間上的無窮迴圈。用 reg [3:0] k 時,條件 k <= 15 永遠不可能為假,而 k + 1 會從 15 繞回 0。模擬器就掛住了。(b) シミュレーション時間ゼロのまま無限ループする。reg [3:0] k では条件 k <= 15 が偽になりえず、k + 1 は 15→0 と巻き戻る。シミュレータはハングする。

(c) a has four 1 bits, so counter = 4. The loop runs until temp becomes zero; since the MSB is 1, that takes 8 shifts. (For a = 8'b0000_0001 it would take only 1.)(c) a 裡有四個 1 位元,所以 counter = 4。迴圈一直跑到 temp 變成零為止;因為最高位元是 1,那需要 8 次移位。(若 a = 8'b0000_0001,就只要 1 次。)(c) a には 1 のビットが四つあるので counter = 4。ループは temp が零になるまで回り、最上位ビットが 1 なのでそれには 8 回のシフトを要する。(a = 8'b0000_0001 なら 1 回で済む。)

(d) The repeat and the integer-indexed for have compile-time-constant trip counts and unroll, so they are structurally synthesisable — though the repeat example writes a memory array, which most tools will not map to logic. The while loop has a data-dependent bound and is not synthesisable. The 4-bit for is not synthesisable either, since it does not terminate.(d) repeat 與用 integer 當索引的 for 迴圈次數是編譯期常數、可以攤開,所以在結構上是可合成的 —— 不過 repeat 那個例子寫的是記憶體陣列,多數工具不會把它對應成邏輯。while 迴圈的界限相依於資料,不可合成。4 位元的 for 也不可合成,因為它根本不會終止。(d) repeat と integer インデックスの for は繰り返し回数がコンパイル時定数で展開できるので、構造的には合成可能である——ただし repeat の例はメモリ配列に書き込むので、ほとんどのツールはこれを論理に対応づけない。while ループは境界がデータ依存なので合成できない。4 ビットの for も終了しないので合成できない。

7.6 Worked example: the 4-bit carry look-ahead adder7.6 實例:4 位元超前進位加法器7.6 実例:4 ビットキャリー先見加算器

A ripple-carry adder's delay grows linearly with width because bit $i$ cannot compute its sum until bit $i-1$ has produced its carry. Carry look-ahead breaks the chain by computing, for each bit position, two signals that depend only on the operands:漣波進位加法器的延遲隨位寬線性成長,因為位元 $i$ 在位元 $i-1$ 產出進位之前算不出自己的和。超前進位打斷這條鏈的方式,是替每一個位元位置算出兩個只相依於運算元的訊號:リプルキャリー加算器の遅延は幅に比例して増える。ビット $i$ はビット $i-1$ がキャリーを出すまで自分の和を計算できないからである。キャリー先見はこの連鎖を断ち切るために、各ビット位置についてオペランドだけに依存する二つの信号を計算する。

Generate, propagate, carry, sum產生、傳遞、進位、總和生成・伝播・キャリー・和 $$g_i = a_i\,b_i,\qquad p_i = a_i \oplus b_i,\qquad c_{i+1} = g_i + p_i\,c_i,\qquad s_i = p_i \oplus c_i$$

In words: position $i$ generates a carry when both operand bits are 1, and propagates an incoming carry when exactly one is. Expanding the recurrence gives each carry as a two-level function of the operands and $c_0$:用文字說:位置 $i$ 在兩個運算元位元都是 1 時產生一個進位,在剛好只有一個是 1 時傳遞進來的進位。把這個遞迴式展開,每一個進位就成為運算元與 $c_0$ 的兩層函數:言葉で言えば、位置 $i$ は両方のオペランドビットが 1 のときキャリーを生成し、ちょうど一方が 1 のとき入ってくるキャリーを伝播する。この漸化式を展開すると、各キャリーはオペランドと $c_0$ の二段の関数として表される。

Expanded carries for a 4-bit block4 位元區塊的進位展開式4 ビットブロックのキャリー展開 $$\begin{aligned} c_1 &= g_0 + p_0 c_0\\ c_2 &= g_1 + p_1 g_0 + p_1 p_0 c_0\\ c_3 &= g_2 + p_2 g_1 + p_2 p_1 g_0 + p_2 p_1 p_0 c_0\\ c_4 &= g_3 + p_3 g_2 + p_3 p_2 g_1 + p_3 p_2 p_1 g_0 + p_3 p_2 p_1 p_0 c_0 \end{aligned}$$

Every carry is now a sum of products of inputs, so in principle all four are available after two gate delays instead of eight. The cost is fan-in: $c_4$ needs a five-input OR and a five-input AND, which is why real adders use 4-bit look-ahead blocks joined by a second level of look-ahead rather than one 32-bit expansion.現在每一個進位都是輸入的積之和,所以原則上四個進位在兩個閘延遲之後就都可用,而不是八個。代價是扇入:$c_4$ 需要一個五輸入 OR 與一個五輸入 AND,這就是為什麼真實的加法器用的是以第二層超前進位串起來的 4 位元超前進位區塊,而不是一個 32 位元的大展開式。いまや各キャリーは入力の積和なので、原理的には四つとも八ゲート遅延ではなく二ゲート遅延で得られる。代償はファンインである:$c_4$ には 5 入力 OR と 5 入力 AND が要る。実際の加算器が 32 ビットを一気に展開せず、4 ビットの先見ブロックを第二段の先見でつなぐ構成を採るのはこのためである。

a[3:0] b[3:0] c_in carry generate g = a & b carry propagate p = a ^ b continuous assignments always @ (a or b or c_in) : carry_generation carrychain[i] = g[i] | (p[i] & carrychain[i-1]) event-activated procedural block shiftedcarry = {carrychain, c_in} 5 bits: [0]=c_in, [i]=c_i sum = p ^ shiftedcarry c_out = shiftedcarry[4] p reused
The module as the slide draws it: three description styles stacked. Continuous assignments make g and p, an event-triggered procedural block makes the carry chain, continuous assignments make the sum and carry-out.投影片畫出來的那個模組:三種描述風格疊在一起。連續指派做出 g 與 p,事件觸發的程序區塊做出進位鏈,連續指派再做出總和與進位輸出。スライドが描くとおりのモジュール:三つの記述スタイルが積み重なっている。連続代入が g と p を作り、イベント駆動の手続き型ブロックがキャリーチェーンを作り、連続代入が和とキャリー出力を作る。
module Add_prop_gen (sum, c_out, a, b, c_in);   // 4-bit look-ahead carry adder
  output [3:0] sum;                             // behavioral model
  output       c_out;
  input  [3:0] a, b;
  input        c_in;

  reg  [3:0] carrychain;
  wire [3:0] g = a & b;   // carry generate,  continuous assignment, bitwise and
  wire [3:0] p = a ^ b;   // carry propagate, continuous assignment, bitwise xor

  always @ (a or b or c_in)               // event "or"
    begin : carry_generation              // usage: block name
      integer i;
      #0 carrychain[0] = g[0] + (p[0] & c_in);   // needed for simulation
      for (i = 1; i <= 3; i = i + 1)
        begin
          carrychain[i] = g[i] | (p[i] & carrychain[i-1]);
        end
    end

  wire [4:0] shiftedcarry = {carrychain, c_in};  // concatenation
  wire [3:0] sum   = p ^ shiftedcarry;           // summation
  wire       c_out = shiftedcarry[4];            // carry out, bit select
endmodule

Five details in that code are worth unpicking, because each one is a general technique rather than a trick for adders.那段程式碼裡有五個細節值得拆開來看,因為每一個都是通用技巧,不是加法器專用的花招。このコードには解きほぐす価値のある細部が五つある。どれも加算器専用の小技ではなく、一般的な技法だからである。

How to write this module today今天該怎麼寫這個模組いまならこのモジュールをどう書くか
always @* begin
  carrychain[0] = g[0] | (p[0] & c_in);
  for (i = 1; i <= 3; i = i + 1)
    carrychain[i] = g[i] | (p[i] & carrychain[i-1]);
end

always @* adds g and p to the sensitivity list automatically, so the block re-runs when they settle and the #0 becomes unnecessary. The for loop has static bounds and unrolls into the three-level cascade. Note that this writes the carry chain as a ripple of three gate pairs, not as the flat two-level expansion above — the name is "look-ahead" but the code is a recurrence, and it is the synthesis tool's job to flatten it. On a cell-based flow it will, subject to the fan-in limits of the library.always @* 會自動把 g 與 p 加進敏感度列表,所以區塊會在它們穩定時重跑,#0 就變成不必要。那個 for 迴圈界限是靜態的,會攤成三層串接。要注意這段把進位鏈寫成三對閘的漣波,而不是上面那個扁平的兩層展開式 —— 名字叫「超前進位」,但程式碼是一個遞迴式,把它攤平是合成工具的工作。在 cell-based 流程上它會做,只要不超過單元庫的扇入上限。always @* は g と p を自動的に感度リストへ入れるので、それらが落ち着いたときにブロックが再実行され、#0 は不要になる。for ループは境界が静的なので三段の縦続に展開される。ここではキャリーチェーンが、上の平坦な二段展開ではなく三対のゲートのリップルとして書かれている点に注意したい——名前は「先見」だがコードは漸化式であり、それを平坦化するのは合成ツールの仕事である。セルベースのフローなら、ライブラリのファンイン上限の範囲内でそうしてくれる。

Practice 13 — Carry look-ahead arithmetic練習 13 — 超前進位加法器的運算練習 13 — キャリー先見加算器の算術

Problem: With a = 4'b1011, b = 4'b0110 and c_in = 1, compute g, p, the carry chain, shiftedcarry, sum and c_out, and check against ordinary binary addition.題目:當 a = 4'b1011、b = 4'b0110、c_in = 1 時,請算出 g、p、進位鏈、shiftedcarry、sum 與 c_out,並與普通的二進位加法核對。問題:a = 4'b1011、b = 4'b0110、c_in = 1 のとき、g、p、キャリーチェーン、shiftedcarry、sum、c_out を求め、通常の 2 進加算と照合せよ。

Click to reveal solution點擊展開解答クリックで解答を表示
g = a & b = 1011 & 0110 = 4'b0010, so $g_1 = 1$ and the rest are 0.g = a & b = 1011 & 0110 = 4'b0010,所以 $g_1 = 1$,其餘為 0。g = a & b = 1011 & 0110 = 4'b0010。したがって $g_1 = 1$ で、残りは 0 である。
p = a ^ b = 1011 ^ 0110 = 4'b1101, so $p_0 = 1$, $p_1 = 0$, $p_2 = 1$, $p_3 = 1$.p = a ^ b = 1011 ^ 0110 = 4'b1101,所以 $p_0 = 1$、$p_1 = 0$、$p_2 = 1$、$p_3 = 1$。p = a ^ b = 1011 ^ 0110 = 4'b1101。したがって $p_0 = 1$、$p_1 = 0$、$p_2 = 1$、$p_3 = 1$ である。
$c_1 = g_0 + p_0c_0 = 0 + 1\cdot 1 = 1$.$c_1 = g_0 + p_0c_0 = 0 + 1\cdot 1 = 1$。$c_1 = g_0 + p_0c_0 = 0 + 1\cdot 1 = 1$。
$c_2 = g_1 + p_1c_1 = 1 + 0\cdot 1 = 1$.$c_2 = g_1 + p_1c_1 = 1 + 0\cdot 1 = 1$。$c_2 = g_1 + p_1c_1 = 1 + 0\cdot 1 = 1$。
$c_3 = g_2 + p_2c_2 = 0 + 1\cdot 1 = 1$.$c_3 = g_2 + p_2c_2 = 0 + 1\cdot 1 = 1$。$c_3 = g_2 + p_2c_2 = 0 + 1\cdot 1 = 1$。
$c_4 = g_3 + p_3c_3 = 0 + 1\cdot 1 = 1$.$c_4 = g_3 + p_3c_3 = 0 + 1\cdot 1 = 1$。$c_4 = g_3 + p_3c_3 = 0 + 1\cdot 1 = 1$。
carrychain = 4'b1111, so shiftedcarry = {1111, 1} = 5'b11111.carrychain = 4'b1111,所以 shiftedcarry = {1111, 1} = 5'b11111。carrychain = 4'b1111 なので shiftedcarry = {1111, 1} = 5'b11111 となる。
sum = p ^ shiftedcarry[3:0] = 1101 ^ 1111 = 4'b0010; c_out = shiftedcarry[4] = 1.

Check: $11 + 6 + 1 = 18 = 10010_2$, i.e. a carry-out of 1 and a 4-bit sum of 0010. Correct.核對:$11 + 6 + 1 = 18 = 10010_2$,也就是進位輸出為 1,4 位元的和為 0010。正確。検算:$11 + 6 + 1 = 18 = 10010_2$、すなわちキャリー出力が 1 で 4 ビットの和が 0010 である。正しい。

Notice how $c_1$ was propagated from c_in through $p_0$, $c_2$ was generated at bit 1, and $c_3$, $c_4$ were propagated onwards. That is the whole intuition behind the two signals.注意 $c_1$ 是從 c_in 經由 $p_0$ 傳遞過來的,$c_2$ 是在位元 1 產生的,而 $c_3$、$c_4$ 則是繼續往上傳遞。這就是那兩個訊號背後的全部直覺。$c_1$ が c_in から $p_0$ を通って伝播し、$c_2$ がビット 1 で生成され、$c_3$ と $c_4$ がさらに伝播していった点に注目したい。これが二つの信号の直観のすべてである。

7.7 forever and disable7.7 forever 與 disable7.7 forever と disable

forever inside a named block放在具名區塊裡的 forever名前付きブロック内の forever
initial
  begin : clock_loop
    clock = 0;
    forever
      #10 clock = ~clock;
  end

initial
  #100 disable clock_loop;
disable — find the first onedisable — 找出第一個disable — 最初の一つを見つける
module find_first_one (a, trigger, i);
  input  [15:0] a;
  input         trigger;
  output [4:0]  i;
  reg    [4:0]  i;

  always @ trigger
    begin : search
      for (i = 0; i < 16; i = i + 1)
        if (a[i] == 1) disable search;
    end
endmodule

The left pair answers the slide's question "what is the difference between always and forever?". always is a module-level construct: it starts at time 0, cannot be preceded by setup code, and cannot be stopped. forever is a statement: it can appear anywhere inside a procedural block, so it can be preceded by initialisation (here clock = 0;, which is why this version does not need a separate initial), it can be entered conditionally, and because it sits inside a named block it can be terminated with disable. For a clock generator you want forever whenever you intend to stop the clock partway through a test.左邊那一對回答了投影片的問題「always 與 forever 有什麼差別?」。always 是模組層級的構造:它在時間 0 啟動,前面不能放設定用的程式碼,而且停不下來。forever 是一道敘述:它可以出現在程序區塊內的任何地方,所以前面可以放初始化(這裡是 clock = 0;,這也是為什麼這個版本不需要另外一個 initial),它可以被有條件地進入,而且因為它待在一個具名區塊裡,可以用 disable 終止。對時脈產生器來說,只要你打算在測試中途把時脈停掉,你要的就是 forever。左の対はスライドの問い「always と forever の違いは何か」に答えている。always はモジュールレベルの構文である:時刻 0 に起動し、前に準備コードを置けず、止めることもできない。forever は文である:手続き型ブロック内のどこにでも書けるので前に初期化を置け(ここでは clock = 0;。この版に別途 initial が要らないのはこのためである)、条件つきで入ることもでき、名前付きブロックの中にあるので disable で終了させられる。クロック生成器については、テストの途中でクロックを止めるつもりなら forever を使うべきである。

The right-hand module answers the second question, "what is the difference between disable and $finish?". disable name; terminates the named block or task, discarding whatever it was doing, and execution resumes at the statement following the block — the rest of the simulation is unaffected. $finish ends the entire simulation run and returns control to the shell. $stop is the third member of the family: it suspends the run and drops into the simulator's interactive prompt, which is what you want in a debug session and never what you want in a regression.右邊那個模組回答第二個問題:「disable 與 $finish 有什麼差別?」。disable name; 終止指定的具名區塊或任務,把它正在做的事全部丟掉,執行從該區塊之後的那道敘述繼續 —— 模擬的其餘部分不受影響。$finish 結束整段模擬執行,把控制權交回殼層。$stop 是這一家族的第三個成員:它暫停執行,掉進模擬器的互動提示符,那是除錯時你想要的,而在回歸測試裡永遠不是你想要的。右のモジュールは二つ目の問い「disable と $finish の違いは何か」に答えている。disable name; は指定した名前付きブロックまたはタスクを終了させ、途中の処理を破棄し、そのブロックの次の文から実行が再開される——シミュレーションの他の部分には影響しない。$finish はシミュレーションの実行全体を終わらせ、制御をシェルへ返す。$stop はこの一族の三人目である:実行を中断してシミュレータの対話プロンプトに落とす。デバッグ中には望ましく、回帰テストでは決して望ましくない。

The slide writes a bare disable;投影片寫的是光禿禿的 disable;スライドは裸の disable; と書いている

Slide 42 shows if (a[i] == 1) disable; with no argument. That is not legal Verilog — disable must name a block or a task. The code above adds the block name search, which is both legal and clearer. Also note the width of i: it is 5 bits, not 4, precisely so that it can hold the value 16 that the loop reaches when no bit of a is set. A 4-bit i would wrap to 0 and the module would report "bit 0 is set" for an all-zero input.第 42 張投影片寫的是 if (a[i] == 1) disable;,沒有引數。那不是合法的 Verilog —— disable 必須指名一個區塊或一個任務。上面的程式碼補上了區塊名 search,既合法又清楚。另外要注意 i 的寬度:它是 5 位元,不是 4,正是為了能裝下「a 一個位元都沒設」時迴圈會走到的 16 這個值。4 位元的 i 會繞回 0,而這個模組就會對一個全零的輸入回報「位元 0 有設」。スライド 42 は if (a[i] == 1) disable; と引数なしで書いている。これは合法な Verilog ではない——disable はブロックかタスクを名指ししなければならない。上のコードはブロック名 search を補っており、合法かつ明快である。i の幅にも注意したい:4 ビットではなく 5 ビットである。a のどのビットも立っていないときにループが到達する値 16 を保持できるようにするためである。4 ビットの i は 0 に巻き戻り、このモジュールは全零の入力に対して「ビット 0 が立っている」と報告してしまう。

8. Parallel Activity Flow and Race Conditions8. 平行活動流程與競爭條件8. 並列アクティビティフローと競合状態

8.1 fork ... join

Everything inside begin ... end executes sequentially in one thread. Everything inside fork ... join executes as separate concurrent threads, all starting at the same instant, and the join does not complete until every one of them has finished. The construct is not supported by synthesis — there is no way to build "a thread" — and lives entirely in test benches.begin ... end 裡的一切都在同一個執行緒裡循序執行。fork ... join 裡的一切則以各自獨立的並行執行緒執行,全部在同一瞬間啟動,而 join 要等到它們每一個都結束才算完成。這個構造不被合成支援 —— 根本沒有辦法蓋出「一條執行緒」—— 它完全活在測試平台裡。begin ... end の中はすべて一つのスレッドで逐次実行される。fork ... join の中はすべて別個の並行スレッドとして実行され、全部が同じ瞬間に起動し、join はそのすべてが終わるまで完了しない。この構文は合成では扱えず——「スレッド」を作る方法などない——完全にテストベンチの住人である。

fork — delays are absolutefork — 延遲是絕對的fork — 遅延は絶対時刻
fork
  #50  a = 1;
  #100 a = 0;
  #150 a = 1;
  #200 a = 0;
join
begin — delays accumulatebegin — 延遲會累加begin — 遅延は積み上がる
begin
  #50 a = 1;
  #50 a = 0;
  #50 a = 1;
  #50 a = 0;
end

Both produce the same waveform, and the slide pairs them to make exactly that point: inside a fork every delay is measured from the moment the fork was entered, so the numbers are absolute times; inside a begin each delay is measured from the previous statement, so the numbers are increments. The fork version is often more readable for a stimulus schedule, because you can read the firing time of each line directly instead of running a mental sum. Both complete at t = 200: the begin because 50×4 = 200, the fork because join waits for the slowest thread.兩者產生相同的波形,而投影片把它們配成一對,要講的正是這一點:在 fork 裡,每一個延遲都是從進入 fork 的那一刻起算的,所以那些數字是絕對時間;在 begin 裡,每一個延遲都是從上一道敘述起算的,所以那些數字是增量。就一份激勵時程表而言,fork 版本往往比較好讀,因為你可以直接讀出每一行的發動時間,不必在心裡做加總。兩者都在 t = 200 完成:begin 是因為 50×4 = 200,fork 是因為 join 要等最慢的那條執行緒。どちらも同じ波形を生み、スライドが両者を並べているのはまさにこの点を言うためである:fork の中では各遅延が fork に入った瞬間から測られるので、数値は絶対時刻である。begin の中では各遅延が直前の文から測られるので、数値は増分である。刺激のスケジュールとしては fork 版のほうが読みやすいことが多い。各行の発火時刻を暗算の足し算なしに直接読めるからである。どちらも t = 200 で完了する:begin は 50×4 = 200 だから、fork は join が最も遅いスレッドを待つからである。

Three properties of fork that the slide lists and that follow from the event-queue model of §2.1:投影片列出的 fork 三個性質,它們都可以從 §2.1 的事件佇列模型推出來:スライドが挙げる fork の三つの性質。いずれも §2.1 のイベントキューのモデルから導かれる。

8.2 Anatomy of a race8.2 競爭的解剖8.2 競合の解剖

The lecture's definition is good: a race condition exists when the value of a target signal is ambiguous because different assignment statements occur simultaneously. The operative word is simultaneously — in the same time slot and in the same region, where the standard imposes no order.講義的定義不錯:當不同的指派敘述同時發生,使得某個目標訊號的值變得有歧義時,就存在一個競爭條件。關鍵字是同時 —— 在同一個時槽、同一個區域裡,而標準在那裡沒有規定順序。講義の定義は良い:異なる代入文が同時に起こることで、ある目標信号の値が曖昧になるとき、競合状態が存在する。効いている語は同時にである——同じタイムスロットの同じ領域の中であり、そこに標準は順序を課していない。

Race有競爭競合あり
fork
  #150 a = b;
  #150 c = a;
join
No race沒有競爭競合なし
fork
  a = #150 b;
  c = #150 a;
join

In the left version, both threads sleep until t = 150 and then both become Active events in the same time slot. If the simulator picks the first thread first, a takes the value of b and then c takes the new a, i.e. c ends up equal to b. If it picks the second first, c takes the old a. Both are legal simulations of the same source, and nothing in the language lets you predict or control which you get. Different simulators give different answers; the same simulator with optimisation on and off can give different answers.在左邊的版本裡,兩條執行緒都睡到 t = 150,然後兩者都在同一個時槽成為 Active 事件。如果模擬器先挑第一條執行緒,a 取到 b 的值,接著 c 取到新的 a,也就是 c 最後等於 b。如果它先挑第二條,c 取到的是舊的 a。兩者都是同一份原始碼的合法模擬,而語言裡沒有任何東西能讓你預測或控制你會拿到哪一個。不同的模擬器給不同的答案;同一個模擬器開不開最佳化也可能給不同的答案。左の版では、二つのスレッドがどちらも t = 150 まで眠り、そこで同じタイムスロットの Active イベントになる。シミュレータが一つ目のスレッドを先に選べば、a は b の値を取り、続いて c は新しい a を取る。つまり c は最終的に b と等しくなる。二つ目を先に選べば、c は古い a を取る。どちらも同じソースの合法なシミュレーションであり、どちらになるかを予測したり制御したりする手段は言語にない。シミュレータが違えば答えも違い、同じシミュレータでも最適化の有無で答えが変わりうる。

In the right version, both RHS are evaluated by the intra-assignment rule at t = 0, long before either write. c therefore receives the value a held at t = 0, deterministically, and the pair behaves like a one-deep shift. The order in which the two writes land at t = 150 no longer matters, because neither write feeds the other's read.在右邊的版本裡,兩個右手邊都依指派內規則在 t = 0 就求好了值,遠早於任何一次寫入。因此 c 確定地收到 a 在 t = 0 所持有的值,這一對的行為就像一級移位。兩次寫入在 t = 150 落地的先後不再重要,因為兩次寫入都不餵給對方的讀。右の版では、二つの右辺がどちらも代入内の規則に従って t = 0 に評価されており、どちらの書き込みよりはるかに早い。したがって c は t = 0 に a が保持していた値を確定的に受け取り、この対は一段のシフトのように振る舞う。二つの書き込みが t = 150 に落ちる順序はもはや問題にならない。どちらの書き込みも相手の読み出しに供給していないからである。

8.3 Three ways to kill a race8.3 消除競爭的三種方法8.3 競合を消す三つの方法

Generalising from that example, there are exactly three mechanisms, and it is worth knowing all three because they apply in different situations.從那個例子推廣出去,機制剛好有三種,而三種都值得知道,因為它們適用於不同的情境。あの例から一般化すると、機構はちょうど三つある。三つとも知っておく価値がある。それぞれ別の状況に当てはまるからである。

  1. Separate the reads from the writes in time. That is what intra-assignment delay does above: all the reads happen in one time slot and all the writes in a later one, so the order within each slot is irrelevant.在時間上把讀與寫分開。這就是上面指派內延遲所做的事:所有的讀都發生在一個時槽,所有的寫都發生在較晚的另一個時槽,於是每個時槽內部的順序都無關緊要。読み出しと書き込みを時間的に分ける。上の代入内遅延がしているのがこれである:すべての読み出しが一つのタイムスロットで、すべての書き込みがより後のタイムスロットで起こるので、各スロット内の順序は無関係になる。
  2. Separate the reads from the writes by region. That is what non-blocking assignment does. fork #150 a <= b; #150 c <= a; join has both reads in the Active region of t = 150 and both writes in the NBA region, so c gets the old a regardless of thread order. This is the same mechanism that makes a bank of flip-flops well-defined, and it is why RTL written with <= throughout is race-free by construction.用區域把讀與寫分開。這就是非阻斷式指派所做的事。fork #150 a <= b; #150 c <= a; join 的兩次讀都在 t = 150 的 Active 區域,兩次寫都在 NBA 區域,所以不論執行緒順序如何,c 拿到的都是舊的 a。這和讓一排正反器定義良好的機制是同一個,也正是為什麼通篇用 <= 寫出來的 RTL 在構造上就不會有競爭。読み出しと書き込みを領域で分ける。ノンブロッキング代入がしているのがこれである。fork #150 a <= b; #150 c <= a; join では二つの読み出しが t = 150 の Active 領域に、二つの書き込みが NBA 領域に入るので、スレッドの順序にかかわらず c は古い a を得る。これはフリップフロップ群を well-defined にする機構と同じであり、全体を <= で書いた RTL が構造的に競合を持たない理由でもある。
  3. Impose an explicit order. Replace the fork with a begin, or add a handshake — a named event, a wait on a flag — so that one thread cannot proceed until the other has finished. This is the right answer when the two threads really do have a dependency rather than merely coinciding.明確規定一個順序。把 fork 換成 begin,或加上一個握手 —— 一個具名事件、一個對旗標的 wait —— 讓其中一條執行緒在另一條結束之前無法前進。當兩條執行緒之間真的有相依性、而不是只是碰巧同時發生時,這才是正確的答案。明示的に順序を課す。fork を begin に替えるか、ハンドシェイク——名前付きイベント、フラグに対する wait——を加えて、一方のスレッドが他方の終了まで進めないようにする。二つのスレッドが単に時刻を共有しているのではなく本当に依存関係を持つ場合は、これが正しい答えである。
The most common race in real designs is not in a fork真實設計裡最常見的競爭不在 fork 裡実際の設計でもっとも多い競合は fork の中にはない

It is a clock-and-data race between two always blocks. One block writes q on posedge clk with a blocking assignment; another reads q on the same edge. They are both Active events at the same instant, so the reader may see the old or the new q. Using <= in every clocked block removes the entire class of bug — mechanism 2 above — which is the deepest reason the rule of §2.6 exists. It is not merely a style preference; it is the thing that makes a multi-process RTL model deterministic.它是兩個 always 區塊之間的時脈與資料競爭。一個區塊在 posedge clk 用阻斷式指派寫 q;另一個在同一個邊緣讀 q。兩者在同一瞬間都是 Active 事件,所以讀的那一方可能看到舊的 q,也可能看到新的。在每一個時脈同步區塊都用 <=,就能把這一整類臭蟲消除掉 —— 也就是上面的機制 2 —— 這才是 §2.6 那條規則存在的最深層理由。它不只是風格偏好;它是讓一個多行程 RTL 模型具有確定性的那個東西。それは二つの always ブロックの間のクロックとデータの競合である。一方のブロックが posedge clk でブロッキング代入によって q を書き、もう一方が同じエッジで q を読む。両者は同じ瞬間の Active イベントなので、読む側は古い q を見るかもしれないし新しい q を見るかもしれない。すべてのクロック同期ブロックで <= を使えばこの種のバグが丸ごと消える——上の機構 2 である——これこそ §2.6 の規則が存在する最も深い理由である。単なる好みの問題ではなく、複数プロセスからなる RTL モデルを決定的にしているものなのである。

Practice 14 — fork timing and races練習 14 — fork 的時序與競爭練習 14 — fork のタイミングと競合

(a) When does each block in §8.1 finish? (b) Rewrite the racing fork using non-blocking assignments and say what c gets. (c) For the code below, at what time does the join complete and what is z? (d) Is there a race in (c)?(a) §8.1 裡每個區塊各在什麼時候結束?(b) 用非阻斷式指派改寫那個有競爭的 fork,並說出 c 會拿到什麼。(c) 對於下面這段程式碼,join 在什麼時候完成?z 是多少?(d) (c) 裡有競爭嗎?(a) §8.1 の各ブロックはいつ終わるか。(b) 競合のある fork をノンブロッキング代入で書き直し、c が何を得るか述べよ。(c) 下のコードについて、join はいつ完了し、z は何になるか。(d) (c) に競合はあるか。

fork
  #10 x = 1;
  begin #5 y = 2; #10 y = 3; end
  #20 z = x + y;
join
Click to reveal solution點擊展開解答クリックで解答を表示

(a) Both at t = 200. The fork because join waits for the longest thread (200); the begin because 50 + 50 + 50 + 50 = 200.(a) 兩者都在 t = 200。fork 是因為 join 要等最長的那條執行緒(200);begin 是因為 50 + 50 + 50 + 50 = 200。(a) どちらも t = 200。fork は join が最長のスレッド(200)を待つから、begin は 50 + 50 + 50 + 50 = 200 だからである。

(b) fork #150 a <= b; #150 c <= a; join. Both RHS are read in the Active region of t = 150 before either NBA update fires, so c deterministically receives the old a — the same answer the intra-assignment version gives, by a different mechanism.(b) fork #150 a <= b; #150 c <= a; join。兩個右手邊都在 t = 150 的 Active 區域、任何一次 NBA 更新發動之前被讀走,所以 c 確定地收到舊的 a —— 與指派內版本相同的答案,但機制不同。(b) fork #150 a <= b; #150 c <= a; join。二つの右辺はどちらも t = 150 の Active 領域で、いずれの NBA 更新よりも前に読まれるので、c は確定的に古い a を受け取る——代入内版と同じ答えだが、機構は異なる。

(c) Thread 1 sets x = 1 at t = 10. Thread 2 sets y = 2 at t = 5 and y = 3 at t = 15, finishing at t = 15. Thread 3 evaluates x + y = 1 + 3 = 4 at t = 20. The join completes at t = 20, the time at which the last thread finishes.(c) 執行緒 1 在 t = 10 設 x = 1。執行緒 2 在 t = 5 設 y = 2、在 t = 15 設 y = 3,於 t = 15 結束。執行緒 3 在 t = 20 求 x + y = 1 + 3 = 4。join 在 t = 20 完成,也就是最後一條執行緒結束的時刻。(c) スレッド 1 は t = 10 で x = 1 とする。スレッド 2 は t = 5 で y = 2、t = 15 で y = 3 とし、t = 15 に終わる。スレッド 3 は t = 20 で x + y = 1 + 3 = 4 を評価する。join は最後のスレッドが終わる t = 20 に完了する。

(d) No. The three threads perform their assignments at t = 5, 10, 15 and 20 — four distinct time slots, with no two writes and no write-plus-read coinciding. Change the third line to #15 z = x + y; and you create one immediately, because the read of y would share a time slot with the write of y.(d) 沒有。三條執行緒在 t = 5、10、15、20 做它們的指派 —— 四個不同的時槽,沒有兩次寫、也沒有寫與讀重合。把第三行改成 #15 z = x + y;,你立刻就製造出一個競爭,因為對 y 的讀會與對 y 的寫共用一個時槽。(d) ない。三つのスレッドは t = 5、10、15、20 で代入を行う——四つの別々のタイムスロットであり、書き込みどうしも、書き込みと読み出しも重なっていない。三行目を #15 z = x + y; に変えれば直ちに競合が生まれる。y の読み出しが y の書き込みと同じタイムスロットを共有するからである。

9. Tasks and Functions9. 任務與函式9. タスクと関数

Part 2 of the lecture opens with the two subprogram constructs. Their purpose is the same as in any language — execute a common procedure from several places, and give it a readable name — but the two differ in a set of rules that is small, rigid, and almost entirely motivated by one question: may this subprogram consume simulation time?講義的第 2 部以這兩個副程式構造開場。它們的目的和任何語言裡的一樣 —— 從好幾個地方執行同一段共用程序,並給它一個好讀的名字 —— 但兩者在一組又小又死板的規則上不同,而那組規則幾乎完全出自同一個問題:這個副程式可不可以消耗模擬時間?講義のパート 2 はこの二つのサブプログラム構文から始まる。目的はどの言語でも同じ——共通の手続きを複数の場所から実行し、読みやすい名前を与える——だが、両者は小さく厳格な規則の集合によって異なり、その規則はほぼすべて一つの問いに由来する:このサブプログラムはシミュレーション時間を消費してよいか。

9.1 The two constructs side by side9.1 兩種構造並排比較9.1 二つの構文を並べて見る

task ... endtaskfunction ... endfunction
Arguments引數引数Zero or more, of type input, output or inout零個或多個,型別可為 input、output 或 inout零個以上。型は input、output、inoutAt least one, and input only至少一個,而且只能是 input少なくとも一つ、しかも input のみ
Return value回傳值戻り値None; results come back through output/inout arguments沒有;結果透過 output/inout 引數帶回來なし。結果は output/inout 引数で返るExactly one, returned by assigning to the function's own name剛好一個,藉由對函式自己的名字指派來回傳ちょうど一つ。関数自身の名前に代入することで返す
Timing controls時間控制タイミング制御Allowed: #, @, wait, named events允許:#、@、wait、具名事件可:#、@、wait、名前付きイベントForbidden禁止禁止
Simulation time模擬時間シミュレーション時間May execute over non-zero time可以跨越非零的時間執行非零の時間にまたがって実行されうるAlways executes in zero time永遠在零時間內執行完つねにゼロ時間で実行される
How it is invoked如何呼叫呼び出し方As a statement, from a procedural block以一道敘述的形式,從程序區塊呼叫手続き型ブロックから文としてAs an expression, anywhere an expression is legal以一個運算式的形式,出現在任何運算式合法的地方式として、式が合法な場所ならどこでも
May call可以呼叫呼び出せる相手Other tasks and functions其他任務與函式他のタスクと関数Other functions only (a task could consume time)只能呼叫其他函式(任務可能消耗時間)他の関数のみ(タスクは時間を消費しうるため)

Every row of that table follows from the last one but two. A function is invoked from inside an expression, and an expression has to produce a value now — there is no way to suspend the evaluation of y = f(a) + g(b) halfway through and resume it at the next clock edge. So a function may not contain a timing control, may not call a task, and necessarily completes in zero simulation time. A task, by contrast, is invoked as a statement from a thread that is perfectly capable of suspending, so it may contain anything a procedural block may contain.那張表的每一列,都是從倒數第三列推出來的。函式是從運算式內部被呼叫的,而運算式必須當下就產出一個值 —— 沒有辦法把 y = f(a) + g(b) 的求值做到一半暫停,下一個時脈邊緣再接下去。所以函式不得含有時間控制、不得呼叫任務,而且必然在零模擬時間內完成。任務則相反,它是以一道敘述的形式、從一條完全有能力暫停的執行緒裡被呼叫的,所以它可以含有程序區塊所能含有的任何東西。この表の各行は、下から三番目の行から導かれる。関数は式の内部から呼ばれ、式はいま値を出さねばならない——y = f(a) + g(b) の評価を途中で中断して次のクロックエッジで再開する方法はない。だから関数はタイミング制御を含めず、タスクを呼べず、必然的にシミュレーション時間ゼロで完了する。対してタスクは、中断する能力を十分に備えたスレッドから文として呼ばれるので、手続き型ブロックが含みうるものは何でも含んでよい。

That same distinction decides what each construct means to a synthesis tool. A function describes a combinational computation: inputs in, one value out, no time elapsed, so it is a cone of logic and nothing more. A task may or may not be synthesisable depending on what is inside it — a task containing only assignments is just an inlined block of logic, while a task containing @(posedge clk) is a multi-cycle behaviour that most tools reject.同樣這個區別也決定了兩種構造對合成工具的意義。函式描述的是一個組合邏輯運算:輸入進去,一個值出來,沒有時間流逝,所以它就是一個邏輯錐,如此而已。任務可不可合成則取決於裡面放了什麼 —— 只含指派的任務不過是一塊被內嵌進來的邏輯,而含有 @(posedge clk) 的任務是一個跨週期的行為,多數工具會拒絕。この同じ区別が、両構文が合成ツールにとって何を意味するかも決める。関数は組合せ演算を記述する:入力が入り、値が一つ出て、時間は流れない。だから論理コーンそのもの以上の何物でもない。タスクが合成可能かどうかは中身次第である——代入だけのタスクはインライン展開される論理の塊にすぎないが、@(posedge clk) を含むタスクは複数サイクルにわたる振る舞いであり、ほとんどのツールが拒否する。

9.2 Task characteristics9.2 任務的特性9.2 タスクの特性

9.3 Function characteristics9.3 函式的特性9.3 関数の特性

The implicit return variable is a real variable那個隱含的回傳變數是一個真正的變數暗黙の戻り値変数は本物の変数である

Because align is an ordinary reg, you may read it as well as write it, which is what makes the word-aligner example below work: it assigns align = word; and then repeatedly tests and shifts align itself. That is legal and idiomatic. What is not legal is using the name as a value before you have assigned it, or declaring another object with the same name in the same scope.因為 align 是一個普通的 reg,你既可以寫它也可以讀它,而這正是下面那個字組對齊器範例能運作的原因:它先 align = word;,然後反覆測試並移位 align 自己。這是合法而且慣用的。不合法的是:在尚未指派它之前就把這個名字當成值來用,或在同一個範圍裡宣告另一個同名的物件。align はごく普通の reg なので、書くだけでなく読むこともできる。下のワードアライナの例が成り立つのはこのためである:まず align = word; とし、その後 align 自身を繰り返し判定してシフトする。これは合法かつ定石どおりである。合法でないのは、代入する前にこの名前を値として使うことと、同じスコープで同名の別のオブジェクトを宣言することである。

9.4 Example: a task that counts ones9.4 範例:一個數 1 的任務9.4 例:1 を数えるタスク

module bit_counter (data, count);
  input  [7:0] data;
  output [3:0] count;
  reg    [3:0] count;

  always @ (data)
    count_ones_in_data (data, count);
  task count_ones_in_data;
    input  [7:0] a;
    output [3:0] c;
    reg    [3:0] c;
    reg    [7:0] tmp;

    begin c = 0; tmp = a;
      while (tmp)
        begin
          c = c + tmp[0];
          tmp = tmp >> 1;
        end
    end
  endtask
endmodule

This is the while loop of §7.5 wrapped in a task. The call count_ones_in_data (data, count); associates data with the first declared port a and count with the second, c, purely by position. On entry data is copied into a; on exit c is copied back into count. The local tmp exists only to be destroyed by the shifting, leaving the caller's data untouched — which is the point of pass-by-value.這就是 §7.5 那個 while 迴圈包成任務的樣子。呼叫 count_ones_in_data (data, count); 時,純粹依位置把 data 對應到第一個宣告的埠 a,把 count 對應到第二個埠 c。進入時 data 被複製進 a;離開時 c 被複製回 count。區域變數 tmp 的存在只是為了被移位毀掉,讓呼叫端的 data 毫髮無傷 —— 這正是傳值的意義。これは §7.5 の while ループをタスクで包んだものである。呼び出し count_ones_in_data (data, count); は、純粋に位置によって data を最初に宣言されたポート a に、count を二番目の c に対応づける。入るときに data が a へコピーされ、出るときに c が count へコピーし返される。ローカル変数 tmp はシフトによって壊されるためだけに存在し、呼び出し側の data は無傷のまま残る——これが値渡しの要点である。

Two things in this slide to fix before you reuse it這張投影片裡有兩件事,在你重複使用它之前要先修好このスライドで再利用する前に直すべき二点

First, the line reg [3:0] c; duplicates the output [3:0] c; declaration. Task output ports are already variables; the redundant declaration is accepted by some tools and rejected by others. Write output reg [3:0] c; or simply delete the line.第一,reg [3:0] c; 這一行與 output [3:0] c; 的宣告重複了。任務的輸出埠本來就是變數;這個多餘的宣告有些工具接受,有些工具拒絕。請寫 output reg [3:0] c;,或乾脆把那一行刪掉。第一に、reg [3:0] c; の行は output [3:0] c; の宣言と重複している。タスクの出力ポートはもともと変数である。この冗長な宣言は受け付けるツールもあれば拒否するツールもある。output reg [3:0] c; と書くか、その行を削ること。

Second, and more seriously, this task is static (see §9.7). There is exactly one copy of a, c and tmp in the whole simulation. The task happens to be called from only one place here, so nothing goes wrong — but two concurrent calls would share tmp and corrupt each other.第二,而且更嚴重的是,這個任務是靜態的(見 §9.7)。整個模擬裡 a、c 與 tmp 各只有一份。這裡的任務剛好只從一個地方被呼叫,所以沒出事 —— 但兩次並行的呼叫會共用 tmp,互相破壞對方的資料。第二に、こちらのほうが深刻だが、このタスクはstatic である(§9.7 参照)。シミュレーション全体で a、c、tmp はそれぞれ一つしか存在しない。ここではたまたま一か所からしか呼ばれないので問題にならないが、二つの呼び出しが並行すれば tmp を共有して互いのデータを壊す。

9.5 Example: a function in a continuous assignment9.5 範例:在連續指派裡使用函式9.5 例:連続代入の中で関数を使う

module word_aligner (w_in, w_out);
  input  [7:0] w_in;
  output [7:0] w_out;

  assign w_out = align (w_in);          // function call in an expression

  function [7:0] align;
    input [7:0] word;
    begin
      align = word;
      if (align != 0)
        while (align[7] == 0)
          align = align << 1;
    end
  endfunction
endmodule

The function normalises a byte by shifting it left until the most significant bit is 1 — the integer half of a floating-point normalisation step. Three things to notice.這個函式把一個位元組左移到最高位元為 1 為止,藉此做正規化 —— 這是浮點數正規化步驟的整數部分。有三點要注意。この関数は 1 バイトを最上位ビットが 1 になるまで左シフトして正規化する——浮動小数点の正規化ステップの整数部分にあたる。注意すべき点が三つある。

9.6 Example: a linear feedback shift register9.6 範例:線性回授移位暫存器9.6 例:線形帰還シフトレジスタ

An LFSR is a shift register whose serial input is formed by XOR-ing selected taps of its own state. With the right tap set it visits all $2^N - 1$ non-zero states before repeating, which makes it the standard cheap pseudo-random pattern generator for built-in self test — the application the slide cites.LFSR 是一個移位暫存器,它的序列輸入是由自己狀態中若干個選定抽頭 XOR 出來的。只要抽頭組選得對,它會在重複之前走遍全部 $2^N - 1$ 個非零狀態,這使它成為內建自我測試裡標準的廉價偽隨機樣式產生器 —— 也就是投影片所引用的應用。LFSR は、自分自身の状態から選んだタップを XOR した値を直列入力とするシフトレジスタである。タップの組を正しく選べば、繰り返すまでに $2^N - 1$ 個のすべての非零状態を巡る。これが、組込み自己テストにおける標準的で安価な擬似乱数パターン生成器となる理由であり、スライドが挙げている用途である。

The lecture's structure is the external-XOR (Fibonacci) form drawn right-to-left. Using the slide's 1-based labels: $c_N = 1$ always, meaning the bit shifted out at $Y[0]$ is fed back to the leftmost stage; and for $j = 1 \ldots N-1$, if $c_j = 1$ the input of stage $j-1$ is $Y[j] \oplus Y[0]$, otherwise it is simply $Y[j]$.講義裡的結構是右到左畫出來的外部 XOR(Fibonacci)形式。沿用投影片以 1 起算的標號:$c_N = 1$ 恆成立,意思是從 $Y[0]$ 移出去的位元被回授到最左邊那一級;而對 $j = 1 \ldots N-1$,若 $c_j = 1$,則第 $j-1$ 級的輸入是 $Y[j] \oplus Y[0]$,否則就只是 $Y[j]$。講義の構成は右から左へ描かれた外部 XOR(フィボナッチ)形式である。スライドの 1 始まりのラベルに従うと、$c_N = 1$ はつねに成り立ち、$Y[0]$ から出ていくビットが最左段へ帰還することを意味する。そして $j = 1 \ldots N-1$ について、$c_j = 1$ なら段 $j-1$ の入力は $Y[j] \oplus Y[0]$、そうでなければ単に $Y[j]$ である。

feedback Y[0] stage N-1 + c Y[j] stage j · · · + c stage 1 stage 0 Y[0] Each tap coefficient c is 1 (XOR the feedback in) or 0 (plain shift). Clock and reset lines omitted.
The Fibonacci LFSR of slide 8. Data shifts right; the bit leaving stage 0 is both the output and the feedback, XOR-ed into every stage whose tap coefficient is 1.第 8 張投影片的 Fibonacci LFSR。資料向右移;離開第 0 級的那個位元既是輸出也是回授,被 XOR 進每一個抽頭係數為 1 的級。スライド 8 のフィボナッチ LFSR。データは右へシフトし、段 0 から出るビットは出力であると同時に帰還でもあり、タップ係数が 1 のすべての段に XOR される。
module Autonomous_LFSR1 (Clock, Reset, Y);
  parameter             Length = 8;
  parameter             initial_state  = 8'b1001_0001;
  parameter [Length-1:1] Tap_Coefficient = 7'b100_1111;
  input                 Clock, Reset;
  output [Length-1:0]   Y;
  reg    [Length-1:0]   Y;

  always @ (posedge Clock)
    begin
      if (!Reset) Y = initial_state;       // synchronous, active-low reset
      else        Y = LFSR_Value (Y);      // function call
    end

  function [Length-1:0] LFSR_Value;
    input   [Length-1:0] LFSR_state;
    integer Cell_ptr;
    begin
      for (Cell_ptr = Length-2; Cell_ptr >= 0; Cell_ptr = Cell_ptr - 1)
        if (Tap_Coefficient [Cell_ptr + 1] == 1)      // same as c in the figure
          LFSR_Value [Cell_ptr] = LFSR_state [Cell_ptr + 1] ^ LFSR_state [0];
        else
          LFSR_Value [Cell_ptr] = LFSR_state [Cell_ptr + 1];
      LFSR_Value [Length-1] = LFSR_state [0];
    end
  endfunction
endmodule

The function is a pure next-state computation: give it the current state, it returns the next one. Several teaching points are packed into eleven lines.這個函式是一個純粹的次態運算:給它目前的狀態,它回傳下一個。十一行裡塞進了好幾個教學重點。この関数は純粋な次状態計算である:現在の状態を与えれば次の状態を返す。十一行の中に教育上の要点がいくつも詰め込まれている。

The first three states, verified前三個狀態的驗證最初の三状態を検証する

With Tap_Coefficient = 7'b100_1111 the taps, indexed 7 down to 1, are $c_7 = 1$, $c_6 = 0$, $c_5 = 0$, $c_4 = 1$, $c_3 = 1$, $c_2 = 1$, $c_1 = 1$. Starting from Y = 8'b1001_0001 (0x91) the feedback bit is Y[0] = 1.當 Tap_Coefficient = 7'b100_1111 時,以 7 到 1 索引的抽頭是 $c_7 = 1$、$c_6 = 0$、$c_5 = 0$、$c_4 = 1$、$c_3 = 1$、$c_2 = 1$、$c_1 = 1$。從 Y = 8'b1001_0001(0x91)出發,回授位元是 Y[0] = 1。Tap_Coefficient = 7'b100_1111 のとき、7 から 1 の添字で見たタップは $c_7 = 1$、$c_6 = 0$、$c_5 = 0$、$c_4 = 1$、$c_3 = 1$、$c_2 = 1$、$c_1 = 1$ である。Y = 8'b1001_0001(0x91)から始めると帰還ビットは Y[0] = 1 である。

Bit 6: $c_7 = 1$ → $Y[7] \oplus Y[0] = 1 \oplus 1 = 0$.   Bit 5: $c_6 = 0$ → $Y[6] = 0$.   Bit 4: $c_5 = 0$ → $Y[5] = 0$.位元 6:$c_7 = 1$ → $Y[7] \oplus Y[0] = 1 \oplus 1 = 0$。 位元 5:$c_6 = 0$ → $Y[6] = 0$。 位元 4:$c_5 = 0$ → $Y[5] = 0$。ビット 6:$c_7 = 1$ → $Y[7] \oplus Y[0] = 1 \oplus 1 = 0$。 ビット 5:$c_6 = 0$ → $Y[6] = 0$。 ビット 4:$c_5 = 0$ → $Y[5] = 0$。
Bit 3: $c_4 = 1$ → $Y[4] \oplus Y[0] = 1 \oplus 1 = 0$.   Bit 2: $c_3 = 1$ → $0 \oplus 1 = 1$.   Bit 1: $c_2 = 1$ → $0 \oplus 1 = 1$.   Bit 0: $c_1 = 1$ → $0 \oplus 1 = 1$.位元 3:$c_4 = 1$ → $Y[4] \oplus Y[0] = 1 \oplus 1 = 0$。 位元 2:$c_3 = 1$ → $0 \oplus 1 = 1$。 位元 1:$c_2 = 1$ → $0 \oplus 1 = 1$。 位元 0:$c_1 = 1$ → $0 \oplus 1 = 1$。ビット 3:$c_4 = 1$ → $Y[4] \oplus Y[0] = 1 \oplus 1 = 0$。 ビット 2:$c_3 = 1$ → $0 \oplus 1 = 1$。 ビット 1:$c_2 = 1$ → $0 \oplus 1 = 1$。 ビット 0:$c_1 = 1$ → $0 \oplus 1 = 1$。
Bit 7 = $Y[0] = 1$. Result: 8'b1000_0111 = 0x87, which is what the slide's waveform shows.位元 7 = $Y[0] = 1$。結果:8'b1000_0111 = 0x87,正是投影片波形顯示的值。ビット 7 = $Y[0] = 1$。結果は 8'b1000_0111 = 0x87 であり、スライドの波形が示す値と一致する。
Repeating from 0x87 (feedback 1) gives 8'b1000_1100 = 0x8C; repeating from 0x8C (feedback 0) gives 8'b0100_0110 = 0x46. All three match the slide.從 0x87 再做一次(回授 1)得到 8'b1000_1100 = 0x8C;從 0x8C 再做一次(回授 0)得到 8'b0100_0110 = 0x46。三個都與投影片相符。0x87 からもう一度行う(帰還 1)と 8'b1000_1100 = 0x8C、0x8C からもう一度行う(帰還 0)と 8'b0100_0110 = 0x46 となる。三つともスライドと一致する。
The all-zero state is a trap全零狀態是個陷阱全零状態は落とし穴である

If an LFSR ever reaches the all-zero state it stays there forever, because every XOR input is 0. That is why the reset value is 8'b1001_0001 rather than zero, and why a maximal-length LFSR has a period of $2^N - 1$ rather than $2^N$. If you build one of these for a self-test pattern generator, make sure the reset is wired and that nothing can load zero into it.如果一個 LFSR 曾經走到全零狀態,它就永遠停在那裡,因為每一個 XOR 的輸入都是 0。這就是為什麼重置值是 8'b1001_0001 而不是零,也是為什麼最大長度 LFSR 的週期是 $2^N - 1$ 而不是 $2^N$。如果你要做一個這樣的東西當自我測試樣式產生器,請確定重置有接好,而且沒有任何東西能把零載進去。LFSR がひとたび全零状態に至れば、XOR の入力がすべて 0 になるため永遠にそこにとどまる。リセット値が零ではなく 8'b1001_0001 である理由も、最大長 LFSR の周期が $2^N$ ではなく $2^N - 1$ である理由もこれである。自己テストのパターン生成器としてこれを作るなら、リセットが配線されていること、そして零を書き込めるものが何もないことを確かめること。

9.7 automatic versus static storage9.7 automatic 與靜態儲存9.7 automatic と静的記憶

The slides do not mention this, but it is the single most common way a task or function goes wrong in a real test bench, so it belongs in any honest treatment.投影片沒有提到這件事,但它是真實測試平台裡任務或函式出錯最常見的單一原因,所以任何誠實的講法都該把它寫進來。スライドはこれに触れていないが、実際のテストベンチでタスクや関数がおかしくなる最も多い単一の原因なので、誠実な解説なら必ず含めるべきである。

By default, a Verilog task or function is static. Its arguments and local variables are allocated once, at elaboration time, and every call uses the same storage. For a function called from one continuous assignment that is irrelevant. For a task called from two concurrent threads it is fatal: thread A writes tmp, suspends at a delay, thread B overwrites tmp, thread A resumes and reads thread B's value. The failure is intermittent and depends on timing, which makes it exactly the kind of bug that survives a whole lab session.預設情況下,Verilog 的任務或函式是靜態的。它的引數與區域變數在展開階段就配置一次,而每一次呼叫都用同一塊儲存。對一個只從單一連續指派呼叫的函式而言,這無關緊要。對一個從兩條並行執行緒呼叫的任務而言,這是致命的:執行緒 A 寫 tmp,在某個延遲處暫停,執行緒 B 覆蓋 tmp,執行緒 A 恢復後讀到的是執行緒 B 的值。這種失效是間歇性的,而且相依於時序,所以它正是那種能撐過一整堂實驗課的臭蟲。既定では Verilog のタスクや関数はstatic である。引数とローカル変数はエラボレーション時に一度だけ確保され、どの呼び出しも同じ記憶域を使う。一つの連続代入からしか呼ばれない関数なら、これはどうでもよい。二つの並行スレッドから呼ばれるタスクでは致命的である:スレッド A が tmp に書き、遅延で中断し、スレッド B が tmp を上書きし、スレッド A は再開してスレッド B の値を読む。この故障は間欠的でタイミングに依存するため、実験の一コマを丸ごと生き延びる類のバグになる。

IEEE 1364-2001 added the automatic keyword, which allocates the arguments and locals afresh on each call, as a stack frame:IEEE 1364-2001 加入了 automatic 關鍵字,它會在每次呼叫時重新配置引數與區域變數,就像一個堆疊框:IEEE 1364-2001 は automatic キーワードを追加した。これは呼び出しごとに引数とローカル変数を、スタックフレームのように新たに確保する。

Static — shared storagestatic — 共用儲存static — 記憶域を共有
task send_byte;
  input [7:0] d;
  integer i;
  begin
    for (i = 0; i < 8; i = i + 1)
      begin
        sdo = d[i];
        @(posedge sclk);
      end
  end
endtask
Automatic — per-call storageautomatic — 每次呼叫各有儲存automatic — 呼び出しごとの記憶域
task automatic send_byte;
  input [7:0] d;
  integer i;
  begin
    for (i = 0; i < 8; i = i + 1)
      begin
        sdo = d[i];
        @(posedge sclk);
      end
  end
endtask

The left task takes eight clock cycles. Call it from two threads inside a fork and both share i and d: the loop counter advances twice per cycle, the data byte is whatever the later caller supplied, and the transaction is garbage. The right task gives each call its own i and d and behaves correctly.左邊那個任務要跑八個時脈週期。從一個 fork 裡的兩條執行緒呼叫它,兩者就共用 i 與 d:迴圈計數器每週期前進兩次,資料位元組是後呼叫者給的那個,整筆交易是垃圾。右邊那個任務讓每次呼叫各有自己的 i 與 d,行為正確。左のタスクは八クロックかかる。fork の中の二つのスレッドから呼べば両者が i と d を共有する:ループカウンタは 1 サイクルに二回進み、データバイトは後から呼んだ側のものになり、トランザクションはでたらめになる。右のタスクは呼び出しごとに自分の i と d を持ち、正しく振る舞う。

static (default)static(預設)static(既定)automatic
Storage儲存記憶域One copy, allocated at elaboration一份,在展開階段配置一つだけ。エラボレーション時に確保One copy per call, on the stack每次呼叫一份,配置在堆疊上呼び出しごとに一つ。スタック上に確保
Concurrent calls並行呼叫並行呼び出しCorrupt each other互相破壞互いを壊すIndependent彼此獨立互いに独立
Recursion遞迴再帰Impossible — the frame is overwritten不可能 —— 框會被覆蓋不可能——フレームが上書きされるWorks可行動作する
Hierarchical reference to locals以階層式名稱存取區域變數ローカル変数への階層参照Allowed (top.dut.t1.i)允許(top.dut.t1.i)可(top.dut.t1.i)Not allowed; $monitor cannot watch them either不允許;$monitor 也看不到它們不可。$monitor からも見えない
Synthesis合成合成Supported支援対応Supported for non-recursive use by modern tools現代工具在非遞迴用法下支援非再帰の用法なら現代のツールは対応

The rule of thumb is short: make every task and function in a test bench automatic unless you have a specific reason not to. The only common reasons not to are that you want a local variable to persist between calls (a call counter, say) or that you want to probe a local hierarchically from a monitor.經驗法則很短:測試平台裡的每一個任務與函式都寫成 automatic,除非你有特定理由不這麼做。常見的不這麼做的理由只有兩個:你想讓某個區域變數在兩次呼叫之間保留下來(例如一個呼叫次數計數器),或你想從某個監看器以階層式名稱去探測那個區域變數。経験則は短い:テストベンチのタスクと関数はすべて automatic にせよ。そうしない特別な理由がない限り。そうしない一般的な理由は二つだけである:ローカル変数を呼び出しの間で保持させたい(たとえば呼び出し回数のカウンタ)、あるいはモニタからそのローカル変数を階層名で覗きたい、である。

9.8 Choosing between a task, a function and a module9.8 任務、函式與模組之間如何取捨9.8 タスク・関数・モジュールの選び分け

Use a使用使うものwhen時機使う場面typical example典型範例典型例
functionyou need one value computed from inputs, in zero time, inside an expression你需要在零時間內、從輸入算出一個值,而且要寫在運算式裡入力から一つの値をゼロ時間で計算し、それを式の中で使いたいときparity, sign extension, next-state of an LFSR, address decode同位元、符號延伸、LFSR 的次態、位址解碼パリティ、符号拡張、LFSR の次状態、アドレスデコード
taskthe procedure takes time, or returns more than one value, or has side effects on module signals這段程序會花時間、或要回傳不只一個值、或對模組訊號有副作用手続きが時間を消費する、二つ以上の値を返す、あるいはモジュールの信号に副作用を持つときa bus-write transaction, a reset sequence, a self-checking compare-and-report一次匯流排寫入交易、一段重置序列、一個自我檢查的比對與回報バス書き込みトランザクション、リセットシーケンス、自己チェック付きの比較と報告
module instance模組實例モジュールインスタンスthe thing is a piece of hardware that should appear in the netlist hierarchy這個東西是一塊硬體,應該出現在 netlist 的階層裡それがハードウェアの一部であり、ネットリストの階層に現れるべきときan adder, a FIFO, a controller一個加法器、一個 FIFO、一個控制器加算器、FIFO、コントローラ

The third row is worth emphasising because tasks and functions are flattened — they do not create hierarchy, they do not create separately named instances, and a function used twice becomes two independent copies of the logic, not one shared block. If you want sharing, instantiate a module and multiplex its inputs.第三列值得強調,因為任務與函式是會被攤平的 —— 它們不產生階層,不產生各自具名的實例,而一個被用兩次的函式會變成兩份獨立的邏輯,不是一個共用的區塊。如果你想要共用,就實例化一個模組並對它的輸入做多工。三行目は強調しておく価値がある。タスクと関数は平坦化されるからである——階層を作らず、個別に名前の付いたインスタンスも作らず、二度使われた関数は共有される一つのブロックではなく独立した二つの論理になる。共有したいならモジュールをインスタンス化し、その入力をマルチプレクサで切り替えること。

Practice 15 — Legal or illegal?練習 15 — 合法還是不合法?練習 15 — 合法か違法か

Problem: For each fragment, say whether it is legal, and if not, why.題目:對每一個片段,說出它是否合法;若不合法,說明原因。問題:各断片について合法かどうかを述べ、違法ならその理由を示せ。

(a) function [3:0] f; input [3:0] x; begin #5 f = x; end endfunction
(b) task t; output [3:0] y; begin y = 4'h3; end endtask
(c) function f2; begin f2 = 1'b1; end endfunction
(d) assign w = my_task(a);
(e) always @* my_task(a, w);
(f) function [7:0] g; input [7:0] x; begin g = h(x) + 1; end endfunction
    function [7:0] h; input [7:0] x; begin h = x << 1; end endfunction
(g) function automatic integer fact; input integer n;
      begin if (n <= 1) fact = 1; else fact = n * fact(n-1); end
    endfunction
Click to reveal solution點擊展開解答クリックで解答を表示

(a) Illegal. A function may not contain a timing control. Remove the #5, or make it a task.(a) 不合法。函式不得含有時間控制。把 #5 拿掉,或改成任務。(a) 違法。関数はタイミング制御を含めない。#5 を外すか、タスクにすること。

(b) Legal. A task may have zero inputs and only outputs.(b) 合法。任務可以沒有輸入,只有輸出。(b) 合法。タスクは入力を持たず出力だけでもよい。

(c) Illegal. A function must have at least one input argument. (It is also pointless — a function with no inputs could only return a constant or read module state, and for the latter you want an always block.)(c) 不合法。函式至少要有一個輸入引數。(而且也沒有意義 —— 沒有輸入的函式只能回傳常數或讀模組狀態,而後者你應該用 always 區塊。)(c) 違法。関数には入力引数が少なくとも一つ要る。(意味もない——入力のない関数は定数を返すかモジュールの状態を読むくらいしかできず、後者なら always ブロックを使うべきである。)

(d) Illegal. A task is a statement, not an expression; it cannot appear on the right of a continuous assignment. Only a function can.(d) 不合法。任務是一道敘述,不是運算式;它不能出現在連續指派的右邊。只有函式可以。(d) 違法。タスクは文であって式ではないので、連続代入の右辺に書けない。書けるのは関数だけである。

(e) Legal, provided the task contains no timing control — a task call is a procedural statement and always @* is a procedural block. Whether it synthesises depends on the body.(e) 合法,前提是那個任務不含時間控制 —— 任務呼叫是一道程序性敘述,而 always @* 是一個程序區塊。它能不能合成,取決於本體。(e) 合法。ただしそのタスクがタイミング制御を含まないことが条件である——タスク呼び出しは手続き型文であり、always @* は手続き型ブロックである。合成できるかどうかは本体次第である。

(f) Legal. A function may call another function, because the callee also completes in zero time.(f) 合法。函式可以呼叫另一個函式,因為被呼叫者同樣在零時間內完成。(f) 合法。関数は別の関数を呼べる。呼ばれる側も同じくゼロ時間で完了するからである。

(g) Legal in IEEE 1364-2001 and it computes a factorial. Recursion needs automatic: with static storage the recursive call would overwrite the caller's n and fact and the result would be nonsense. It will not synthesise — recursion has no static unrolling.(g) 在 IEEE 1364-2001 裡合法,而且它算的是階乘。遞迴需要 automatic:用靜態儲存時,遞迴呼叫會覆蓋呼叫端的 n 與 fact,結果就是一團亂。它不可合成 —— 遞迴沒有靜態的攤開方式。(g) IEEE 1364-2001 では合法であり、階乗を計算する。再帰には automatic が要る:静的記憶では再帰呼び出しが呼び出し側の n と fact を上書きし、結果はでたらめになる。合成はできない——再帰には静的な展開がない。

Practice 16 — LFSR by hand練習 16 — 手算 LFSR練習 16 — LFSR を手で計算する

Problem: Take a 4-bit version of the same function: Length = 4, Tap_Coefficient = 3'b101 (so $c_3 = 1$, $c_2 = 0$, $c_1 = 1$), starting state Y = 4'b1001. Compute the next two states. Then say what happens if the start state is 4'b0000.題目:取同一個函式的 4 位元版本:Length = 4、Tap_Coefficient = 3'b101(所以 $c_3 = 1$、$c_2 = 0$、$c_1 = 1$),起始狀態 Y = 4'b1001。請算出接下來兩個狀態。然後說明若起始狀態是 4'b0000 會發生什麼事。問題:同じ関数の 4 ビット版を取る:Length = 4、Tap_Coefficient = 3'b101(したがって $c_3 = 1$、$c_2 = 0$、$c_1 = 1$)、初期状態 Y = 4'b1001。次の二つの状態を求めよ。さらに初期状態が 4'b0000 のとき何が起こるか述べよ。

Click to reveal solution點擊展開解答クリックで解答を表示
State 0: Y = 4'b1001, so $Y[3]=1$, $Y[2]=0$, $Y[1]=0$, $Y[0]=1$. Feedback $= Y[0] = 1$.狀態 0:Y = 4'b1001,所以 $Y[3]=1$、$Y[2]=0$、$Y[1]=0$、$Y[0]=1$。回授 $= Y[0] = 1$。状態 0:Y = 4'b1001 なので $Y[3]=1$、$Y[2]=0$、$Y[1]=0$、$Y[0]=1$。帰還 $= Y[0] = 1$。
Bit 2 (tap $c_3 = 1$): $Y[3] \oplus Y[0] = 1 \oplus 1 = 0$.位元 2(抽頭 $c_3 = 1$):$Y[3] \oplus Y[0] = 1 \oplus 1 = 0$。ビット 2(タップ $c_3 = 1$):$Y[3] \oplus Y[0] = 1 \oplus 1 = 0$。
Bit 1 (tap $c_2 = 0$): $Y[2] = 0$.位元 1(抽頭 $c_2 = 0$):$Y[2] = 0$。ビット 1(タップ $c_2 = 0$):$Y[2] = 0$。
Bit 0 (tap $c_1 = 1$): $Y[1] \oplus Y[0] = 0 \oplus 1 = 1$.位元 0(抽頭 $c_1 = 1$):$Y[1] \oplus Y[0] = 0 \oplus 1 = 1$。ビット 0(タップ $c_1 = 1$):$Y[1] \oplus Y[0] = 0 \oplus 1 = 1$。
Bit 3 $= Y[0] = 1$. Assembling bit 3 down to bit 0 gives 4'b1001 — the same state we started from.位元 3 $= Y[0] = 1$。把位元 3 到位元 0 組起來,得到 4'b1001 —— 和我們出發的狀態一樣。ビット 3 $= Y[0] = 1$。ビット 3 から 0 まで組み立てると 4'b1001 となり、出発した状態と同じである。
State 2 is therefore 4'b1001 as well. This seed and tap set form a fixed point of period 1, which is a useful reminder that not every tap polynomial is maximal-length. For $N = 4$ the primitive polynomials are $x^4 + x + 1$ and $x^4 + x^3 + 1$, and the coefficient set used here is neither.因此狀態 2 也是 4'b1001。這組種子與抽頭構成一個週期為 1 的不動點,提醒我們不是每一組抽頭多項式都是最大長度的。對 $N = 4$ 而言,本原多項式是 $x^4 + x + 1$ 與 $x^4 + x^3 + 1$,而這裡用的係數組兩個都不是。したがって状態 2 も 4'b1001 である。この種とタップの組は周期 1 の不動点をなしており、どのタップ多項式も最大長であるとは限らないことを思い出させてくれる。$N = 4$ の原始多項式は $x^4 + x + 1$ と $x^4 + x^3 + 1$ であり、ここで使った係数の組はそのどちらでもない。
From 4'b0000: the feedback bit is 0 and every XOR input is 0, so the next state is 4'b0000 forever. This is the lock-up state that every LFSR has, and it is why the design resets to a non-zero seed.從 4'b0000 出發:回授位元是 0,每一個 XOR 的輸入也都是 0,所以次態永遠是 4'b0000。這就是每個 LFSR 都有的鎖死狀態,也是為什麼這個設計要重置成一個非零的種子。4'b0000 から始めた場合:帰還ビットは 0 で、XOR の入力もすべて 0 なので、次状態は永遠に 4'b0000 である。これがどの LFSR にもあるロックアップ状態であり、設計が非零の種にリセットされる理由である。

The lesson to carry away is that the Verilog is only half the design: the tap set has to come from a primitive polynomial table, and the code gives you no warning when it does not.要帶走的教訓是:Verilog 只佔設計的一半;抽頭組必須來自一份本原多項式表,而當它不是時,程式碼不會給你任何警告。持ち帰るべき教訓はこうである:Verilog は設計の半分でしかない。タップの組は原始多項式の表から取らねばならず、そうでないときにコードは何の警告もしてくれない。

10. Timing Analysis and Timing Checks10. 時序分析與時序檢查10. タイミング解析とタイミングチェック

10.1 Static versus dynamic timing analysis10.1 靜態時序分析與動態時序分析10.1 静的タイミング解析と動的タイミング解析

Having a functionally correct netlist is half the job; the other half is proving that every signal arrives in time. There are two ways to do it, and the lecture contrasts them because they fail in opposite directions.有一份功能正確的 netlist 只完成了一半工作;另一半是證明每一個訊號都及時抵達。做這件事有兩種方法,而講義把它們對比起來,因為它們是往相反方向失效的。機能的に正しいネットリストがあるのは仕事の半分であり、残りの半分はすべての信号が間に合って到着することの証明である。その方法は二つあり、講義が両者を対比させているのは、失敗の向きが互いに逆だからである。

Dynamic timing analysis means running a gate-level simulation with real cell delays back-annotated from an SDF file, and watching for violations. Its strength is that it only reports paths the stimulus actually exercises, so it never complains about a path the design can never take. Its weakness is the same sentence read the other way: if your stimulus fails to exercise a functional path, a violation on that path is simply never seen. You cannot write enough vectors to cover every path in a modern block, so dynamic analysis alone can never sign off timing.動態時序分析指的是用從 SDF 檔案反標註進來的真實單元延遲跑一次閘級模擬,並盯著有沒有違規。它的強項是只會回報激勵真正走過的路徑,所以它絕不會抱怨一條設計根本走不到的路徑。它的弱點就是同一句話反過來讀:如果你的激勵沒有走過某條功能路徑,那條路徑上的違規就根本不會被看見。你不可能為現代區塊裡的每一條路徑寫出足夠的向量,所以光靠動態分析永遠不可能簽掉時序。動的タイミング解析とは、SDF ファイルからバックアノテートした実際のセル遅延でゲートレベルシミュレーションを走らせ、違反を見張ることである。強みは、刺激が実際に通った経路しか報告しないため、設計が決して取りえない経路について文句を言わない点である。弱点は同じ文を裏返したものである:刺激がある機能経路を通らなければ、その経路上の違反は決して見えない。現代のブロックのすべての経路を覆うベクタを書くことは不可能なので、動的解析だけでタイミングをサインオフすることはできない。

Static timing analysis (STA) does not simulate at all. It walks the netlist as a graph, propagates arrival times forward from every start point and required times backward from every end point, and reports the worst slack. Because it ignores logic function entirely, it is exhaustive: every path is examined and nothing can be missed. Its weakness is the mirror image: it also examines paths that can never be sensitised — false paths — and reports violations on them.靜態時序分析(STA)根本不做模擬。它把 netlist 當成一張圖來走,從每一個起點把到達時間往前傳,從每一個終點把需求時間往後傳,然後回報最差的餘裕。因為它完全不管邏輯功能,所以它是窮盡的:每一條路徑都會被檢查,什麼都漏不掉。它的弱點正是這面鏡子的另一側:它同樣會檢查那些永遠無法被激發的路徑 —— 偽路徑 —— 並回報它們上面的違規。静的タイミング解析(STA)はシミュレーションを一切行わない。ネットリストをグラフとしてたどり、各始点から到着時間を前向きに、各終点から要求時間を後ろ向きに伝播させ、最悪のスラックを報告する。論理機能を完全に無視するので網羅的である:すべての経路が調べられ、見落としは起こらない。弱点はその鏡像である:決して活性化されえない経路——フォルスパス——も調べてしまい、そこでの違反を報告する。

The slide's false-path example is the standard one. Two multiplexers, Mux1 and Mux2, are driven by the same select signal Sel. A path that enters Mux1 on In0 and leaves Mux2 on In1 requires Sel to hold two different values at once, so no input vector can ever activate it. STA counts it anyway, and if it happens to be the longest path in the block you will spend a week optimising something that does not exist. The remedy is to tell the tool: set_false_path constraints are how a designer injects the functional knowledge that STA structurally lacks.投影片的偽路徑例子是標準的那一個。兩個多工器 Mux1 與 Mux2 由同一個選擇訊號 Sel 驅動。一條從 In0 進入 Mux1、從 In1 離開 Mux2 的路徑,要求 Sel 同時持有兩個不同的值,所以沒有任何輸入向量能激發它。STA 照樣把它算進去,而萬一它剛好是這個區塊裡最長的路徑,你就會花一週去最佳化一個不存在的東西。對策是去告訴工具:set_false_path 約束就是設計者把 STA 在結構上欠缺的功能知識注入進去的方式。スライドのフォルスパスの例は定番のものである。二つのマルチプレクサ Mux1 と Mux2 が同一の選択信号 Sel で駆動されている。In0 から Mux1 に入り In1 から Mux2 を出る経路は、Sel が同時に二つの異なる値を持つことを要求するので、どんな入力ベクタでも活性化できない。STA はそれでも数え上げるので、それがたまたまブロック中で最長の経路だったなら、存在しないものの最適化に一週間を費やすことになる。対策はツールに教えることである:set_false_path 制約こそ、STA が構造的に欠いている機能的知識を設計者が注入する手段である。

Static靜態静的Dynamic動態動的
Needs stimulus?需要激勵嗎?刺激が要るかNo不需要不要Yes需要必要
Path coverage路徑涵蓋率経路のカバレッジExhaustive窮盡網羅的Only what the vectors exercise只涵蓋向量走過的部分ベクタが通った分だけ
False paths偽路徑フォルスパスReported unless excluded by hand除非人工排除,否則會被回報手で除外しない限り報告されるNever reported永遠不會被回報決して報告されない
Runtime執行時間実行時間Minutes for a large block大區塊數分鐘大きなブロックで数分Hours to days數小時到數天数時間から数日
Catches functional bugs?抓得到功能臭蟲嗎?機能バグを捕まえられるかNo抓不到捕まえられないYes抓得到捕まえられる

In an industrial flow you use both: STA for sign-off on timing, gate-level simulation for sign-off on function and on things STA cannot model — asynchronous interfaces, reset release, and the X-propagation behaviour of uninitialised state.在業界流程裡兩者都用:時序簽核用 STA,功能簽核以及 STA 無法建模的東西 —— 非同步介面、重置釋放,以及未初始化狀態的 X 傳播行為 —— 則用閘級模擬。産業用のフローでは両方を使う:タイミングのサインオフには STA を、機能のサインオフと STA がモデル化できないもの——非同期インタフェース、リセット解除、未初期化状態の X 伝播の振る舞い——にはゲートレベルシミュレーションを使う。

10.2 Arrival time, required time, slack10.2 到達時間、需求時間、餘裕10.2 到着時間・要求時間・スラック

STA annotates every node of the netlist graph with three numbers, written on the slide in the order arrival / required / slack:STA 替 netlist 圖的每一個節點標上三個數字,投影片上按照到達/需求/餘裕的順序寫出來:STA はネットリストグラフの各ノードに三つの数値を註記する。スライドでは到着/要求/スラックの順に書かれている。

The three numbers三個數字三つの数値 $$\text{slack} \;=\; \text{required arrival time} \;-\; \text{arrival time}$$

The critical path is the path along which the slack is most negative (or, if everything passes, least positive). Note that slack is a property of a node, not only of a path: the same negative number propagates along the whole critical path, which is exactly how you read a timing report to find where to optimise.關鍵路徑是餘裕最負的那條路徑(若全部都過關,就是餘裕最不正的那條)。要注意餘裕是節點的性質,不只是路徑的性質:同一個負數會沿著整條關鍵路徑傳播,而這正是你讀一份時序報告、找出該往哪裡最佳化的方法。クリティカルパスはスラックが最も負である経路(すべて合格していれば最も正でない経路)である。スラックが経路だけでなくノードの属性でもある点に注意したい:同じ負の数がクリティカルパス全体に沿って伝播し、まさにそれがタイミングレポートを読んでどこを最適化すべきかを探す方法である。

10.3 A worked static timing analysis10.3 一個靜態時序分析實例10.3 静的タイミング解析の実例

The slide's example is a four-gate cone. Work it yourself before reading the arithmetic.投影片的例子是一個四個閘的邏輯錐。在看下面的算式之前,先自己算一遍。スライドの例は四つのゲートからなるコーンである。以下の計算を読む前に、まず自分で解いてみてほしい。

7 / 4 / -3 5 / 3 / -2 4 / 7 / 3 8 / 8 / 0 2 3 4 3 9 / 6 / -3 11 / 11 / 0 11 7 20 / 17 / -3 18 / 18 / 0 3 2 23 / 20 / -3 notation: arrival / required / slack   ·   numbers inside a gate are pin-to-output delays red = critical path (slack -3)
Static timing analysis of a four-gate cone. Arrival times propagate left to right taking the maximum; required times propagate right to left taking the minimum; slack is their difference.一個四閘邏輯錐的靜態時序分析。到達時間由左往右傳播並取最大值;需求時間由右往左傳播並取最小值;餘裕是兩者之差。四つのゲートからなるコーンの静的タイミング解析。到着時間は左から右へ伝播し最大値を取り、要求時間は右から左へ伝播し最小値を取り、スラックはその差である。
The arithmetic, node by node逐個節點的算式ノードごとの計算
Top gate (pin delays 2 and 3). Arrival at the output $= \max(7+2,\; 5+3) = \max(9, 8) = 9$. The budget at this output is 6, so the required times at the inputs are $6-2 = 4$ and $6-3 = 3$. Slacks: $4-7 = -3$, $3-5 = -2$, and at the output $6-9 = -3$.上方的閘(接腳延遲 2 與 3)。輸出的到達時間 $= \max(7+2,\; 5+3) = \max(9, 8) = 9$。這個輸出的預算是 6,所以兩個輸入的需求時間是 $6-2 = 4$ 與 $6-3 = 3$。餘裕:$4-7 = -3$、$3-5 = -2$,輸出則是 $6-9 = -3$。上側のゲート(ピン遅延 2 と 3)。出力の到着時間は $= \max(7+2,\; 5+3) = \max(9, 8) = 9$。この出力の予算は 6 なので、入力の要求時間は $6-2 = 4$ と $6-3 = 3$ である。スラックは $4-7 = -3$、$3-5 = -2$、出力は $6-9 = -3$。
Bottom gate (pin delays 4 and 3). Arrival $= \max(4+4,\; 8+3) = \max(8, 11) = 11$. Required at the output is 11, so required at the inputs are $11-4 = 7$ and $11-3 = 8$. Slacks: $7-4 = +3$, $8-8 = 0$, output $11-11 = 0$.下方的閘(接腳延遲 4 與 3)。到達時間 $= \max(4+4,\; 8+3) = \max(8, 11) = 11$。輸出的需求時間是 11,所以兩個輸入的需求時間是 $11-4 = 7$ 與 $11-3 = 8$。餘裕:$7-4 = +3$、$8-8 = 0$,輸出 $11-11 = 0$。下側のゲート(ピン遅延 4 と 3)。到着時間は $= \max(4+4,\; 8+3) = \max(8, 11) = 11$。出力の要求時間は 11 なので、入力の要求時間は $11-4 = 7$ と $11-3 = 8$ である。スラックは $7-4 = +3$、$8-8 = 0$、出力は $11-11 = 0$。
Interconnect. The top net costs 11, so arrival at the final gate's upper input is $9 + 11 = 20$. The bottom net costs 7, so arrival at the lower input is $11 + 7 = 18$.連線。上方那條 net 的成本是 11,所以最後那個閘上方輸入的到達時間是 $9 + 11 = 20$。下方那條 net 的成本是 7,所以下方輸入的到達時間是 $11 + 7 = 18$。配線。上側のネットのコストは 11 なので、最終ゲートの上側入力の到着時間は $9 + 11 = 20$ である。下側のネットのコストは 7 なので、下側入力の到着時間は $11 + 7 = 18$ である。
Final gate (pin delays 3 and 2). Arrival $= \max(20+3,\; 18+2) = \max(23, 20) = 23$. The end-point budget is 20, so required at the inputs are $20-3 = 17$ and $20-2 = 18$. Slacks: $17-20 = -3$ and $18-18 = 0$; the output slack is $20-23 = -3$.最後那個閘(接腳延遲 3 與 2)。到達時間 $= \max(20+3,\; 18+2) = \max(23, 20) = 23$。終點的預算是 20,所以兩個輸入的需求時間是 $20-3 = 17$ 與 $20-2 = 18$。餘裕:$17-20 = -3$ 與 $18-18 = 0$;輸出餘裕是 $20-23 = -3$。最終ゲート(ピン遅延 3 と 2)。到着時間は $= \max(20+3,\; 18+2) = \max(23, 20) = 23$。終点の予算は 20 なので、入力の要求時間は $20-3 = 17$ と $20-2 = 18$ である。スラックは $17-20 = -3$ と $18-18 = 0$、出力のスラックは $20-23 = -3$ である。
Reading the report. The $-3$ appears at the first input, the top gate's output and both of its inputs. That chain of identical negative slacks is the critical path, and it tells you where to intervene: either speed up the top gate, or attack the 11-unit interconnect, which is by far the largest single term in the path.怎麼讀這份報告。那個 $-3$ 出現在第一個輸入、上方那個閘的輸出,以及它的兩個輸入上。那一串相同的負餘裕就是關鍵路徑,而它告訴你該從哪裡下手:要嘛把上方那個閘加速,要嘛去對付那段 11 個單位的連線 —— 那是整條路徑裡遠遠最大的單一項。レポートの読み方。この $-3$ は最初の入力、上側ゲートの出力、そしてその両入力に現れる。同じ負のスラックが連なるこの鎖こそクリティカルパスであり、どこに手を入れるべきかを教えている:上側のゲートを速くするか、11 単位の配線に取り組むかである。後者はこの経路で群を抜いて大きな単一項である。

10.4 System tasks for timing checks10.4 時序檢查用的系統任務10.4 タイミングチェック用のシステムタスク

Dynamic timing analysis needs somewhere to express the constraint being checked. Verilog provides a family of system tasks that may be invoked from a behavior in a test bench, or — the usual place — inside a specify ... endspecify block in a cell model.動態時序分析需要一個地方來表達它要檢查的那個約束。Verilog 提供了一族系統任務,可以從測試平台裡的一個行為描述呼叫,或者 —— 更常見的位置 —— 放在單元模型的 specify ... endspecify 區塊裡。動的タイミング解析には、検査する制約を書く場所が必要である。Verilog はそのためのシステムタスク群を用意しており、テストベンチの振る舞い記述から呼び出すことも、より一般的にはセルモデルの specify ... endspecify ブロックの中に置くこともできる。

Task任務タスクSignature簽名シグネチャViolation when違規條件違反となる条件Example範例例
$setup(data_event, ref_event, limit)the interval from data to ref is less than limit從 data 到 ref 的間隔小於 limitdata から ref までの間隔が limit より短い$setup (data, posedge clk, 5);
$hold(ref_event, data_event, limit)the data is stable for less than limit after the reference資料在參考事件之後維持穩定的時間不足 limitデータが基準の後に安定している時間が limit に満たない$hold (posedge clk, data, 2);
$setuphold(data_event, ref_event, s_limit, h_limit)either of the above上述兩者之一上のいずれか$setuphold (data, posedge clk, 5, 2);
$period(ref_event, limit)two consecutive reference events are closer than limit兩個連續的參考事件之間的間隔小於 limit連続する二つの基準イベントの間隔が limit より近い$period (posedge clk, 16);
$width(ref_event, limit)the interval from the reference to the next opposite transition is less than limit從參考事件到下一次反向轉態的間隔小於 limit基準から次の逆向き遷移までの間隔が limit より短い$width (posedge clk, 8);
$skew(event_1, event_2, limit)the interval between the two events exceeds limit兩個事件之間的間隔超過 limit二つのイベントの間隔が limit を超える$skew (posedge clk1, posedge clk2, 3);
$nochange(ref_event, data_event, start_edge_offset, end_edge_offset)the data changes inside the stated window around the reference資料在參考事件周圍所指定的視窗之內發生改變基準の周りに指定された窓の内側でデータが変化する$nochange (posedge clk, data, -1, 2);

Four details about the argument order and semantics, each of which is an exam question waiting to happen:關於引數順序與語意有四個細節,每一個都是隨時可能冒出來的考題:引数の順序と意味について四つの細部がある。どれもいつ出てもおかしくない試験問題である。

10.5 Notifiers10.5 通知器10.5 ノーティファイア

By default a timing-check violation does two things: it prints a message, and it lets the output take its new value anyway. That is optimistic. A real flip-flop whose setup time is violated does not cleanly capture the new data — it may capture the old value, the new value, or go metastable. Pretending it captured correctly means the simulation keeps running with a value that silicon might not produce, and the violation message scrolls off the screen.預設情況下,一次時序檢查違規會做兩件事:印出一則訊息,然後照樣讓輸出取得它的新值。那是樂觀的。一個建立時間被違反的真實正反器,並不會乾淨地捕捉到新資料 —— 它可能捕捉到舊值、新值,或進入亞穩態。假裝它正確捕捉了,等於讓模擬帶著一個矽片未必產生得出來的值繼續跑下去,而那則違規訊息則捲出螢幕消失了。既定では、タイミングチェックの違反は二つのことをする:メッセージを印字し、それでも出力に新しい値を取らせる。これは楽観的である。セットアップ時間を破られた実際のフリップフロップは新しいデータをきれいに取り込まない——古い値を取るか、新しい値を取るか、メタステーブルになるかである。正しく取り込んだふりをするということは、シリコンが出すとは限らない値を抱えたままシミュレーションを続けるということであり、違反メッセージは画面の外へ流れて消えてしまう。

A notifier fixes this. You pass an extra register as the final argument of the timing check; the simulator toggles that register whenever the check fires. The cell's sequential UDP takes the notifier as an additional input and is written so that any toggle on it drives the output to x.通知器解決了這件事。你把一個額外的暫存器當成時序檢查的最後一個引數傳進去;每當檢查發動,模擬器就把那個暫存器反轉一次。單元的循序 UDP 把這個通知器當成一個額外的輸入,並且寫成:只要它上面發生反轉,就把輸出驅動成 x。ノーティファイアがこれを解決する。タイミングチェックの最後の引数として追加のレジスタを渡すと、チェックが発火するたびにシミュレータがそのレジスタをトグルする。セルの順序 UDP はそのノーティファイアを追加の入力として取り、そこにトグルが起きたら出力を x に駆動するように書かれている。

module dff (data, clock, q);
  input  data, clock;
  output q;
  ...
  udp_dff (q, data, clock, notifier);   // an additional port on the sequential UDP

  specify
    $setup (data, posedge clock, 12, notifier);
    $hold  (posedge clock, data,   5, notifier);
    $width (posedge clock,        25, notifier);
  endspecify
endmodule

The effect is that a setup violation pushes an x into q, which then propagates downstream and, with luck, reaches a point where the test bench notices. This is the pessimistic — and correct — modelling choice: an unknown is an honest statement that the hardware's behaviour is undefined. It is also why a gate-level simulation that was clean in RTL can suddenly fill with x values; the first thing to check is not the design but the timing-check report.效果是:一次建立時間違規會把一個 x 推進 q,它接著往下游傳播,運氣好的話會抵達某個測試平台注意得到的地方。這是悲觀的 —— 也是正確的 —— 建模選擇:一個未知值,是對「硬體行為未定義」這件事的誠實陳述。這也是為什麼一個在 RTL 下乾乾淨淨的閘級模擬,可能突然滿是 x;第一個該檢查的不是設計,而是時序檢查報告。その結果、セットアップ違反は x を q へ押し込み、それが下流へ伝播して、運がよければテストベンチが気づく地点まで届く。これは悲観的で、しかも正しいモデル化の選択である:未知値は「ハードウェアの振る舞いは未定義である」という誠実な表明だからである。RTL ではきれいだったのにゲートレベルのシミュレーションが突然 x だらけになる理由もこれであり、まず調べるべきは設計ではなくタイミングチェックのレポートである。

Practice 17 — Slack arithmetic練習 17 — 餘裕的計算練習 17 — スラックの計算

Problem: A path starts at a flop clocked at 500 MHz, passes through combinational logic with a total delay of 1.70 ns, and ends at a flop whose setup time is 0.15 ns. Clock-to-Q of the launching flop is 0.20 ns and the clock skew between launch and capture is +0.05 ns (capture edge arrives later). (a) What is the required arrival time at the capture flop's D pin? (b) The arrival time? (c) The slack? (d) If the logic must be sped up, by how much?題目:一條路徑從一個以 500 MHz 計時的正反器出發,通過總延遲 1.70 ns 的組合邏輯,終止於一個建立時間為 0.15 ns 的正反器。發射端正反器的 clock-to-Q 是 0.20 ns,而發射與捕捉之間的時脈偏斜是 +0.05 ns(捕捉邊緣較晚抵達)。(a) 捕捉正反器 D 腳上的需求到達時間是多少?(b) 到達時間呢?(c) 餘裕呢?(d) 如果邏輯必須加速,要快多少?問題:ある経路が 500 MHz で動くフリップフロップから出発し、総遅延 1.70 ns の組合せ論理を通り、セットアップ時間 0.15 ns のフリップフロップで終わる。発射側フリップフロップのクロック・ツー・Q は 0.20 ns、発射と取り込みの間のクロックスキューは +0.05 ns(取り込みエッジのほうが遅く届く)である。(a) 取り込み側フリップフロップの D ピンにおける要求到着時間はいくらか。(b) 到着時間は。(c) スラックは。(d) 論理を速くする必要があるなら、どれだけか。

Click to reveal solution點擊展開解答クリックで解答を表示
(a) The clock period is $1/500\,\text{MHz} = 2.00$ ns. A later capture edge adds to the budget, so the required time at D is $2.00 + 0.05 - 0.15 = \mathbf{1.90}$ ns.(a) 時脈週期是 $1/500\,\text{MHz} = 2.00$ ns。較晚的捕捉邊緣會增加預算,所以 D 腳上的需求時間是 $2.00 + 0.05 - 0.15 = \mathbf{1.90}$ ns。(a) クロック周期は $1/500\,\text{MHz} = 2.00$ ns である。取り込みエッジが遅いぶんだけ予算が増えるので、D ピンの要求時間は $2.00 + 0.05 - 0.15 = \mathbf{1.90}$ ns となる。
(b) Arrival at D $=$ clock-to-Q $+$ logic $= 0.20 + 1.70 = \mathbf{1.90}$ ns.(b) D 腳上的到達時間 $=$ clock-to-Q $+$ 邏輯 $= 0.20 + 1.70 = \mathbf{1.90}$ ns。(b) D ピンの到着時間は $=$ クロック・ツー・Q $+$ 論理 $= 0.20 + 1.70 = \mathbf{1.90}$ ns である。
(c) Slack $= 1.90 - 1.90 = \mathbf{0}$ ns. The path just meets, with no margin.(c) 餘裕 $= 1.90 - 1.90 = \mathbf{0}$ ns。這條路徑剛好過關,沒有任何邊際。(c) スラックは $= 1.90 - 1.90 = \mathbf{0}$ ns。この経路はぎりぎり満たしており、余裕はない。
(d) It does not need speeding up at a nominal corner, but zero slack is not a sign-off condition: process, voltage and temperature variation, plus on-chip variation derating, will push it negative. In practice you would want a few percent of the period in hand. Note also that positive skew helped here — had the capture edge arrived 0.05 ns early, the required time would be 1.80 ns and the slack $-0.10$ ns.(d) 在標稱條件下它不需要加速,但零餘裕不是一個可以簽核的狀態:製程、電壓與溫度變異,加上晶片內變異的折減,都會把它推成負的。實務上你會希望留下週期的百分之幾作為餘裕。另外要注意,正的偏斜在這裡幫了忙 —— 如果捕捉邊緣早到 0.05 ns,需求時間就會是 1.80 ns,餘裕則是 $-0.10$ ns。(d) 公称条件では速くする必要はないが、スラック零はサインオフできる状態ではない:プロセス・電圧・温度のばらつきに加え、オンチップばらつきのディレーティングがこれを負へ押しやる。実務では周期の数パーセントを余裕として残したい。正のスキューがここでは助けになっている点にも注意したい——取り込みエッジが 0.05 ns 早く届いていたら、要求時間は 1.80 ns、スラックは $-0.10$ ns になっていた。
Practice 18 — Which check fires?練習 18 — 哪個檢查會觸發?練習 18 — どのチェックが発火するか

Problem: A cell declares $setup (d, posedge clk, 5), $hold (posedge clk, d, 2), $period (posedge clk, 20) and $width (posedge clk, 8). Rising clock edges occur at t = 0, 20 and 36. The clock falls at t = 12 and t = 26. d changes at t = 16 and t = 21. Which checks report a violation, and why?題目:某個單元宣告了 $setup (d, posedge clk, 5)、$hold (posedge clk, d, 2)、$period (posedge clk, 20) 與 $width (posedge clk, 8)。時脈上升緣出現在 t = 0、20 與 36。時脈在 t = 12 與 t = 26 下降。d 在 t = 16 與 t = 21 改變。哪些檢查會回報違規?為什麼?問題:あるセルが $setup (d, posedge clk, 5)、$hold (posedge clk, d, 2)、$period (posedge clk, 20)、$width (posedge clk, 8) を宣言している。クロックの立ち上がりエッジは t = 0、20、36 に起こり、立ち下がりは t = 12 と t = 26 に起こる。d は t = 16 と t = 21 で変化する。どのチェックが違反を報告するか、またその理由は。

Click to reveal solution點擊展開解答クリックで解答を表示

$period fires once. The edges at 20 and 36 are 16 apart, which is less than the limit of 20. The pair at 0 and 20 is exactly 20 and passes (the check is "less than limit").$period 觸發一次。20 與 36 的那兩個邊緣相距 16,小於上限 20。0 與 20 那一對剛好是 20,過關(檢查條件是「小於 limit」)。$period が一度発火する。20 と 36 のエッジは 16 離れており、上限の 20 より短い。0 と 20 の対はちょうど 20 なので合格である(条件は「limit より短い」)。

$width fires once. The rising edge at t = 20 is followed by the opposite transition at t = 26, a high phase of 6, which is less than 8. The edge at t = 0 is followed by the fall at 12, a width of 12, which passes.$width 觸發一次。t = 20 的上升緣之後,反向轉態發生在 t = 26,高電位相只有 6,小於 8。t = 0 的邊緣之後是 t = 12 的下降,寬度 12,過關。$width が一度発火する。t = 20 の立ち上がりエッジの後、逆向きの遷移は t = 26 に起こり、High 期間は 6 で 8 より短い。t = 0 のエッジの後は t = 12 の立ち下がりで幅は 12 となり、合格である。

$setup fires. The interval from the data event at t = 16 to the reference edge at t = 20 is $20 - 16 = 4$, which is less than the 5-unit limit.$setup 觸發。從 t = 16 的資料事件到 t = 20 的參考邊緣,間隔是 $20 - 16 = 4$,小於 5 個單位的上限。$setup が発火する。t = 16 のデータイベントから t = 20 の基準エッジまでの間隔は $20 - 16 = 4$ であり、5 単位の制限より短い。

$hold fires. After the edge at t = 20, d must be stable for 2 units, i.e. until t = 22. It changes at t = 21, so the stable period is 1, less than 2.$hold 觸發。t = 20 的邊緣之後,d 必須穩定 2 個單位,也就是到 t = 22。它在 t = 21 改變,所以穩定期是 1,小於 2。$hold が発火する。t = 20 のエッジの後、d は 2 単位、すなわち t = 22 まで安定していなければならない。t = 21 で変化するので安定期間は 1 であり、2 より短い。

Summary: all four report violations, which is exactly what you would expect from a clock that is both too fast and too narrow driving data that moves too close to the edge. If a notifier were attached, the flop's output would go to x at t = 20.總結:四個檢查全都回報違規,而這正是你對「一個又太快又太窄的時脈,驅動著一份太靠近邊緣才動的資料」所該預期的結果。如果接上了通知器,這個正反器的輸出會在 t = 20 變成 x。まとめ:四つのチェックがすべて違反を報告する。速すぎて幅も狭すぎるクロックが、エッジに近づきすぎて動くデータを駆動しているのだから、当然の結果である。ノーティファイアが付いていれば、このフリップフロップの出力は t = 20 で x になる。

11. Finite State Machines11. 有限狀態機11. 有限状態機械

The last third of Part 2 applies everything above to the most common sequential structure in digital design. Nothing new is introduced — an FSM is just an always @(posedge clk) block, a case statement and some non-blocking assignments — but the arrangement of those pieces determines how readable, how synthesisable and how debuggable the result is.第 2 部的最後三分之一,把上面所有東西套用到數位設計裡最常見的循序結構上。沒有引入任何新東西 —— 一個 FSM 不過就是一個 always @(posedge clk) 區塊、一個 case 敘述和一些非阻斷式指派 —— 但這些零件的排列方式,決定了結果有多好讀、多好合成、多好除錯。パート 2 の最後の三分の一は、ここまでのすべてをデジタル設計で最もよく現れる順序構造に適用する。新しいものは何も導入されない——FSM は always @(posedge clk) ブロックと case 文といくつかのノンブロッキング代入にすぎない——が、それらの部品の並べ方が、結果の読みやすさ・合成しやすさ・デバッグしやすさを決める。

11.1 Mealy and Moore11.1 Mealy 與 Moore11.1 ミーリ型とムーア型

Both machines have the same three parts: next-state combinational logic, a state register, and output combinational logic. They differ in what feeds the output logic.兩種機器都有同樣的三個部分:次態組合邏輯、狀態暫存器,以及輸出組合邏輯。它們的差別在於什麼東西餵給輸出邏輯。どちらの機械も同じ三つの部分を持つ:次状態組合せ論理、状態レジスタ、出力組合せ論理である。違いは出力論理に何が入力されるかである。

The slide marks Moore as "Better!!" and in a cell-based ASIC flow that recommendation is sound. Clean, registered interfaces between blocks are worth an extra state almost every time. The compromise worth knowing is the registered Mealy (sometimes called a synchronous Mealy): compute the Mealy output function in the same clocked block that computes the next state, and register the result. You keep the one-cycle reaction encoded in the output function while presenting a clean registered output to the outside world. That is exactly what Explicit Style 3 below does.投影片把 Moore 標為「Better!!」,而在 cell-based ASIC 流程裡這個建議是穩健的。區塊之間乾淨、有暫存的介面,幾乎每次都值得多花一個狀態。值得知道的折衷做法是暫存式 Mealy(有時稱為同步 Mealy):在計算次態的那同一個時脈同步區塊裡計算 Mealy 輸出函數,並把結果暫存起來。這樣你既保有編碼在輸出函數裡的一週期反應,又對外界呈現一個乾淨的已暫存輸出。下面的顯式寫法 3 做的正是這件事。スライドはムーア型に「Better!!」と記しており、セルベース ASIC フローにおいてその推奨は妥当である。ブロック間のきれいなレジスタ付きインタフェースは、ほとんどの場合、状態を一つ増やす価値がある。知っておく価値のある折衷案はレジスタ付きミーリ型(同期ミーリ型とも呼ばれる)である:ミーリ型の出力関数を次状態と同じクロック同期ブロックで計算し、結果をレジスタに入れる。これにより出力関数に符号化された 1 サイクルの反応性を保ちつつ、外にはきれいなレジスタ出力を見せられる。下の明示的スタイル 3 がまさにこれを行っている。

The slide's wording投影片的用詞スライドの言い回し

Slide 17 describes a Mealy machine as one whose "output is related to next states and inputs". The conventional definition is that the output is a function of the present state and the inputs. The two statements describe the same circuit from different ends — the next-state logic and the output logic read the same present state and the same inputs, and the block diagram on the slide does show the output logic fed from the state register plus the input bus. Read it as "present state", which is what the diagram draws.第 17 張投影片把 Mealy 機器描述成輸出「與次態和輸入有關」。慣例的定義則是輸出是現態與輸入的函數。這兩種說法是從兩端描述同一個電路 —— 次態邏輯與輸出邏輯讀的是同一個現態、同一組輸入,而投影片上的方塊圖確實畫出輸出邏輯由狀態暫存器加上輸入匯流排餵進來。請把它讀成「現態」,那也是圖上畫的。スライド 17 はミーリ型機械の出力を「次状態と入力に関係する」と記している。慣例的な定義では、出力は現状態と入力の関数である。この二つの言い方は同じ回路を両端から述べたものである——次状態論理と出力論理は同じ現状態と同じ入力を読むのであり、スライドのブロック図も出力論理が状態レジスタと入力バスから駆動される様子を描いている。図が描いているとおり「現状態」と読むのがよい。

11.2 Explicit style: declare the state register11.2 顯式寫法:把狀態暫存器宣告出來11.2 明示的スタイル:状態レジスタを宣言する

In the explicit style you declare a variable that is the state, encode the states as parameters, and write the transitions as assignments to it. The lecture gives three variants.在顯式寫法裡,你宣告一個就是狀態的變數,把各個狀態編碼成參數,並把轉移寫成對它的指派。講義給了三個變體。明示的スタイルでは、状態そのものである変数を宣言し、各状態をパラメータとして符号化し、遷移をその変数への代入として書く。講義は三つの変種を示している。

module FSM_style1 (...);             // Explicit Style 1
  input  ...;
  output ...;
  parameter size = ...;
  reg  [size-1:0] state;
  wire [size-1:0] next_state;

  assign the_outputs = ...;          // a function of state and inputs (Mealy),
                                     // or of state alone (Moore)
  assign next_state  = ...;          // a function of state and inputs

  always @ (negedge reset or posedge clk)
    if (reset == 1'b0) state = start_state; else
      state <= next_state;
endmodule
module FSM_style2 (...);             // Explicit Style 2
  parameter size = ...;
  reg [size-1:0] state, next_state;

  assign the_outputs = ...;

  always @ (state or the_inputs)     // next-state decode in a procedural block
    begin
      // decode for next_state with case or if statement
    end

  always @ (negedge reset or posedge clk)
    if (reset == 1'b0) state = start_state; else
      state <= next_state;
endmodule
module FSM_style3 (...);             // Explicit Style 3 - registered outputs
  parameter size = ...;
  reg [size-1:0] state, next_state;

  always @ (state or the_inputs)
    begin
      // decode for next_state
    end

  always @ (negedge reset or posedge clk)
    if (reset == 1'b0) state = start_state; else begin
      state       <= next_state;
      the_outputs <= ...;            // synchronous outputs that depend on
    end                              // inputs and state: a registered Mealy machine
endmodule

The progression is: Style 1 puts the next-state function in a continuous assignment, which is compact but limits you to what you can write as a single expression; Style 2 moves it into a procedural block so you can use case, which is the standard two-always-block template every synthesis manual recommends; Style 3 additionally registers the outputs, which is the registered-Mealy compromise of §11.1.演進的順序是:寫法 1 把次態函數放進一個連續指派裡,簡潔,但你只能寫成單一個運算式;寫法 2 把它搬進程序區塊,於是你可以用 case,這就是每一本合成手冊都推薦的標準雙 always 區塊樣板;寫法 3 再額外把輸出暫存起來,也就是 §11.1 的暫存式 Mealy 折衷。段階はこうなっている:スタイル 1 は次状態関数を連続代入に置く。簡潔だが、単一の式として書けるものに限られる。スタイル 2 はそれを手続き型ブロックへ移して case を使えるようにする。これはどの合成マニュアルも推奨する標準的な二 always ブロックのテンプレートである。スタイル 3 はさらに出力をレジスタに入れる。これが §11.1 のレジスタ付きミーリ型の折衷である。

All three slides mix = and <= on the same variable三張投影片都在同一個變數上混用了 = 與 <=三つのスライドはどれも同じ変数に = と <= を混ぜている

Look at the reset line: if (reset == 1'b0) state = start_state; else state <= next_state;. The reset branch uses blocking assignment and the normal branch uses non-blocking, on the same register, in the same block. Section 2.6 explained why that is a bad idea; here it is also pointless, since both branches want the same semantics.看那一行重置:if (reset == 1'b0) state = start_state; else state <= next_state;。重置分支用阻斷式指派,正常分支用非阻斷式,對象是同一個暫存器、同一個區塊。2.6 節已經說明過為什麼那是個壞主意;而在這裡它還毫無意義,因為兩個分支想要的語意是一樣的。リセットの行を見てほしい:if (reset == 1'b0) state = start_state; else state <= next_state;。リセットの分岐はブロッキング代入、通常の分岐はノンブロッキング代入で、対象は同じレジスタ、同じブロックである。それが悪い考えである理由は 2.6 節で述べた。しかもここでは無意味でもある。どちらの分岐も同じ意味論を望んでいるからである。

Write both branches with <=. Note too that the sensitivity list @(negedge reset or posedge clk) and the test reset == 1'b0 describe an active-low asynchronous reset, which is the normal convention; name the signal reset_n or rst_n so that the reader does not have to work that out from the negedge.兩個分支都用 <= 寫。另外也要注意,敏感度列表 @(negedge reset or posedge clk) 與測試 reset == 1'b0 描述的是一個低電位有效的非同步重置,這是常見的慣例;請把這個訊號命名為 reset_n 或 rst_n,讀者才不必從 negedge 去反推。どちらの分岐も <= で書くこと。また、感度リスト @(negedge reset or posedge clk) と判定 reset == 1'b0 が Low アクティブの非同期リセットを表している点にも注意したい。これは通常の慣習である。読者が negedge から推理せずに済むよう、この信号は reset_n か rst_n と名付けるとよい。

11.3 Implicit style: let the program counter be the state11.3 隱式寫法:讓程式計數器當狀態11.3 暗黙的スタイル:プログラムカウンタを状態にする

In the implicit style there is no state variable. The "state" is simply where the thread currently is in the body of an always block, expressed as a sequence of event controls:在隱式寫法裡沒有狀態變數。所謂「狀態」就只是執行緒此刻在 always 區塊本體裡的哪個位置,用一連串事件控制表達出來:暗黙的スタイルには状態変数がない。「状態」とは単にスレッドがいま always ブロックの本体のどこにいるかであり、一連のイベント制御として表現される。

always begin
  @ (posedge clk) ...     // state A
  @ (posedge clk) ...     // state B
  @ (posedge clk) ...     // state C, then back to A
end

The slide's summary of the trade-off is accurate and worth keeping:投影片對這個取捨的總結很準確,值得記下來:このトレードオフについてのスライドのまとめは正確であり、覚えておく価値がある。

Implicit machines are therefore excellent for test-bench stimulus sequences and bus-functional models, and poor for control logic that has to respond to arbitrary input patterns. The speed controller below is instructive precisely because the lecture writes it both ways and the implicit versions turn out not to be implicit at all.因此隱式機器很適合測試平台的激勵序列與匯流排功能模型,卻很不適合必須回應任意輸入樣式的控制邏輯。下面那個速度控制器之所以有教育意義,正是因為講義把它兩種寫法都寫了,而那些「隱式」版本其實根本不是隱式的。したがって暗黙的な機械は、テストベンチの刺激シーケンスやバス機能モデルには向くが、任意の入力パターンに応答せねばならない制御論理には向かない。下の速度コントローラが教育的なのは、まさに講義が両方のスタイルで書いており、その「暗黙的」版が実は暗黙的ではないと判明するからである。

11.4 The speed controller11.4 速度控制器11.4 速度コントローラ

Four states — stopped, low, medium, high. The accelerator input a steps up one level per clock; the brake input b steps down one level per clock and takes priority over the accelerator.四個狀態 —— 停止、低速、中速、高速。油門輸入 a 每個時脈往上一級;煞車輸入 b 每個時脈往下一級,而且優先於油門。四つの状態——停止、低速、中速、高速。アクセル入力 a はクロックごとに一段上げ、ブレーキ入力 b はクロックごとに一段下げ、アクセルより優先する。

stopped 2'b00 low 2'b01 medium 2'b10 high 2'b11 a=1, b=0 b=1 a=1, b=0 b=1 a=1, b=0 b=1 b=1 a=1, b=0 a : accelerator b : brake (priority) a=0, b=0 : hold (self-loop on every state, omitted for clarity)
The speed controller. Brake beats accelerator; the self-loops on stopped (braking) and high (accelerating) saturate the machine at each end. The hold transition a=0, b=0 exists on every state and is not drawn.速度控制器。煞車勝過油門;stopped 上(煞車時)與 high 上(加速時)的自迴圈讓這台機器在兩端飽和。a=0, b=0 這個保持轉移在每個狀態上都存在,圖上沒有畫出來。速度コントローラ。ブレーキがアクセルに勝つ。stopped(ブレーキ時)と high(アクセル時)の自己ループが、この機械を両端で飽和させる。a=0, b=0 の保持遷移はすべての状態に存在するが、図には描かれていない。
// Explicit FSM style
module speed_machine_1 (clock, accelerator, brake, speed);
  input      clock, accelerator, brake;
  output [1:0] speed;
  reg    [1:0] state, next_state;

  parameter stopped  = 2'b00;
  parameter s_slow   = 2'b01;          // see the warning below
  parameter s_medium = 2'b10;
  parameter s_high   = 2'b11;

  assign speed = state;                // Moore output: state alone

  always @ (posedge clock)
    state <= next_state;               // the state register

  always @ (state or accelerator or brake)   // the next-state decode
    if (brake == 1'b1)
      case (state)
        stopped  : next_state = stopped;
        s_low    : next_state = stopped;
        s_medium : next_state = s_low;
        s_high   : next_state = s_medium;
        default  : next_state = stopped;
      endcase
    else if (accelerator == 1'b1)
      case (state)
        stopped  : next_state = s_low;
        s_low    : next_state = s_medium;
        s_medium : next_state = s_high;
        s_high   : next_state = s_high;
        default  : next_state = stopped;
      endcase
    else next_state = state;
endmodule
The slide has a typo that would not compile投影片有一個會編不過的筆誤スライドにはコンパイルの通らない誤記がある

The parameter is declared s_slow but every case item refers to s_low. One of the two names is wrong; s_low is used four times and s_slow once, so the declaration is the error. The code above keeps the slide's text and flags it rather than silently correcting it, because this is exactly the class of bug that an undeclared-identifier error catches in two seconds and a reader's eye misses for ten minutes.參數宣告成 s_slow,但每一個 case 項目引用的都是 s_low。這兩個名字有一個是錯的;s_low 用了四次,s_slow 用了一次,所以錯的是那個宣告。上面的程式碼保留投影片的原文並把它標示出來,而不是默默改掉,因為這正是那種「未宣告識別字」錯誤兩秒就抓得到、而人眼盯十分鐘也看不出來的臭蟲。パラメータは s_slow と宣言されているのに、case 項目はどれも s_low を参照している。二つの名前のどちらかが誤りであり、s_low は四回、s_slow は一回使われているので、誤っているのは宣言のほうである。上のコードはスライドの原文を残して印をつけ、黙って直してはいない。これはまさに、未宣言識別子のエラーなら二秒で捕まえるのに、人間の目では十分見ても見落とす種類のバグだからである。

Structurally this is the textbook two-block template, and it is worth noting what it gets right. The state register block contains nothing but state <= next_state; — no logic, no conditions, so the tool infers exactly two flip-flops. The decode block is purely combinational, has a complete sensitivity list, and assigns next_state on every path including a default in both case statements and a final else, so no latch is inferred. The speed output is a continuous assignment from the state, which makes it unambiguously Moore. Three separate concerns, three separate pieces of code.結構上這就是教科書式的雙區塊樣板,值得指出它做對了哪些事。狀態暫存器區塊裡除了 state <= next_state; 什麼都沒有 —— 沒有邏輯、沒有條件,所以工具剛好推斷出兩個正反器。解碼區塊是純組合邏輯,有完整的敏感度列表,而且在每一條路徑上都指派了 next_state,包括兩個 case 敘述裡的 default 與最後那個 else,所以不會推斷出閂鎖。speed 輸出是從狀態來的連續指派,這讓它毫無疑問是 Moore 型。三件分開的事情,三段分開的程式碼。構造としてはこれが教科書どおりの二ブロックテンプレートであり、何を正しくやっているかを挙げておく価値がある。状態レジスタのブロックには state <= next_state; しかない——論理も条件もないので、ツールはちょうど二つのフリップフロップを推論する。デコードブロックは純粋な組合せ論理で、感度リストが完全であり、二つの case 文の default と最後の else を含むすべての経路で next_state に代入しているので、ラッチは推論されない。speed 出力は状態からの連続代入であり、これによって疑いなくムーア型になる。三つの別々の関心事を、三つの別々のコードに書いている。

module speed_machine_2 (clock, accelerator, brake, speed);
  // Implicit FSM. Style: decode the input, decode the state.
  input        clock, accelerator, brake;
  output [1:0] speed;
  reg    [1:0] speed;
  `define stopped 2'b00
  `define low     2'b01
  `define medium  2'b10
  `define high    2'b11

  always @ (posedge clock)
    if (brake == 1'b1)
      case (speed)
        `stopped : speed <= `stopped;
        `low     : speed <= `stopped;
        `medium  : speed <= `low;
        `high    : speed <= `medium;
        default  : speed <= `stopped;
      endcase
    else if (accelerator == 1'b1)
      case (speed)
        `stopped : speed <= `low;
        `low     : speed <= `medium;
        `medium  : speed <= `high;
        `high    : speed <= `high;
        default  : speed <= `stopped;
      endcase
    else speed <= speed;
endmodule

The lecture calls this the implicit style because there is no separate state variable — the output register speed is the state. That is a merge of two roles rather than the event-control-sequence style of §11.3, and it is better described as a one-block or merged FSM. It produces the same two flip-flops and a combinational cone, costs nothing in area, and is perfectly good code. Its drawback is conceptual: if the output encoding ever needs to differ from the state encoding — a one-hot state register with a binary output, say — the merge has to be undone.講義把這個叫做隱式寫法,因為沒有一個獨立的 state 變數 —— 輸出暫存器 speed 就是狀態。那是兩個角色的合併,而不是 §11.3 那種事件控制序列的寫法,更恰當的說法是單區塊或合併式 FSM。它產生同樣的兩個正反器加上一個組合邏輯錐,面積上不花成本,而且是完全好的程式碼。它的缺點是概念上的:萬一哪天輸出編碼必須與狀態編碼不同 —— 例如一個 one-hot 的狀態暫存器配上二進位輸出 —— 這個合併就得拆開。講義はこれを暗黙的スタイルと呼ぶ。独立した state 変数がなく、出力レジスタ speed こそが状態だからである。しかしそれは二つの役割の統合であって、§11.3 のイベント制御シーケンスのスタイルではない。一ブロックあるいは統合型 FSM と呼ぶほうが適切である。生成されるのは同じ二つのフリップフロップと組合せ論理コーンであり、面積上のコストはなく、まったく良いコードである。欠点は概念的なものである:出力の符号化を状態の符号化と変えねばならなくなったとき——たとえばワンホットの状態レジスタに 2 進出力を組み合わせる場合——この統合は解かねばならない。

Two more details. The `define macros are textual substitutions with global scope, so defining `low in this file redefines it for every file compiled afterwards. parameter, as used in speed_machine_1, is scoped to the module and is the right tool. And the third variant on slide 25 simply inverts the nesting — case (speed) on the outside with if (brake) inside each item — which produces identical hardware and is a matter of taste, though the brake-outside version makes the priority of the brake visible at a glance.還有兩個細節。`define 巨集是具有全域範圍的文字替換,所以在這個檔案裡定義 `low,會把它對之後編譯的每一個檔案都重新定義一次。speed_machine_1 裡用的 parameter 的範圍限於模組,才是正確的工具。另外第 25 張投影片的第三個變體只是把巢狀反過來 —— 外層是 case (speed),每個項目裡面放 if (brake) —— 產生的硬體完全相同,屬於口味問題,不過把煞車放外層的版本能讓煞車的優先權一眼就看得見。細部があと二つある。`define マクロはグローバルスコープのテキスト置換なので、このファイルで `low を定義すると、以後コンパイルされるすべてのファイルでそれが再定義される。speed_machine_1 で使われている parameter はモジュールにスコープが限られており、こちらが正しい道具である。またスライド 25 の三つ目の変種は入れ子を逆にしただけである——外側が case (speed) で、各項目の中に if (brake) を置く——生成されるハードウェアはまったく同じで好みの問題だが、ブレーキを外に置いた版はブレーキの優先順位が一目で分かる。

11.5 The up-down counter11.5 上下數計數器11.5 アップダウンカウンタ

Slides 26 and 27 repeat the exercise for a 3-bit up/down counter with a 2-bit mode input: up_dwn = 01 counts up, 10 counts down, 00 and 11 hold. The explicit version writes out the entire state transition table as nested case statements; the implicit versions compute the next count arithmetically.第 26、27 張投影片把同樣的練習重做一次,對象是一個帶 2 位元模式輸入的 3 位元上/下數計數器:up_dwn = 01 往上數,10 往下數,00 與 11 保持。顯式版本把整張狀態轉移表寫成巢狀 case 敘述;隱式版本則用算術算出下一個計數值。スライド 26 と 27 は同じ演習を、2 ビットのモード入力を持つ 3 ビットのアップ/ダウンカウンタで繰り返す:up_dwn = 01 でアップ、10 でダウン、00 と 11 で保持である。明示的版は状態遷移表の全体を入れ子の case 文として書き出し、暗黙的版は次の計数値を算術で求める。

Explicit — tabulated顯式 — 列表式明示的 — 表で書く
always @ (count or up_dwn) begin
  case (count)
    0: case (up_dwn)
         0, 3: next_count = 0;
         1:    next_count = 1;
         2:    next_count = 3'b111;
         default next_count = 0;
       endcase
    // ... states 1, 2, 3 likewise ...
    4,5,6,7:
       if      (up_dwn == 0 || up_dwn == 3) next_count = count;
       else if (up_dwn == 1) next_count = count + 1;
       else if (up_dwn == 2) next_count = count - 1;
       else                  next_count = count;
  endcase
end
Implicit — arithmetic隱式 — 算術式暗黙的 — 算術で書く
always @ (count or up_dwn) begin
  if (up_dwn == 2'b00 || up_dwn == 2'b11)
    next_count = count;
  else if (up_dwn == 2'b01)
    next_count = count + 1;
  else if (up_dwn == 2'b10)
    next_count = count - 1;
  else
    next_count = count;
end

The right-hand version is eight lines and obviously correct; the left-hand version is forty and has to be checked state by state. For a counter — a machine whose transition function is an arithmetic expression — the arithmetic form wins outright. For a protocol controller whose transitions have no closed form, the tabulated form wins. That is the real criterion for choosing between the styles, and it is more useful than the labels "explicit" and "implicit".右邊那個版本八行,而且一看就知道是對的;左邊那個四十行,而且必須一個狀態一個狀態核對。對一個計數器 —— 一台轉移函數就是一個算術運算式的機器 —— 而言,算術寫法完勝。對一個轉移沒有封閉形式的協定控制器而言,列表寫法勝出。這才是在兩種風格之間取捨的真正準則,而且比「顯式」與「隱式」這兩個標籤有用得多。右の版は八行で、見ればすぐ正しいと分かる。左の版は四十行あり、状態ごとに確かめねばならない。カウンタのように遷移関数が算術式そのものである機械には、算術の書き方が圧勝する。遷移に閉じた形のないプロトコルコントローラには、表の書き方が勝る。これこそ二つのスタイルを選び分ける本当の基準であり、「明示的」「暗黙的」というラベルよりはるかに役に立つ。

Two bugs on the up-down counter slides上下數計數器那幾張投影片上的兩個臭蟲アップダウンカウンタのスライドにある二つのバグ

First, the slide's own annotation points at the line else next_count = 0; in the 4–7 branch and corrects it to next_count = count;. The = 0 version would make an unknown mode value reset the counter instead of holding it — and since the three preceding conditions already cover all four binary values, the branch can only be reached when up_dwn contains x or z, where silently clearing the count hides the problem.第一,投影片自己的註記就指著 4–7 那個分支裡的 else next_count = 0;,並把它更正為 next_count = count;。= 0 的版本會讓一個未知的模式值把計數器重置掉而不是保持住 —— 而既然前面三個條件已經涵蓋了全部四種二進位值,這個分支只有在 up_dwn 含有 x 或 z 時才到得了,而在那裡默默把計數清掉,只會把問題藏起來。第一に、スライド自身の註記が 4–7 の分岐にある else next_count = 0; を指し、next_count = count; に訂正している。= 0 版では未知のモード値がカウンタを保持せずリセットしてしまう——しかも先行する三つの条件がすでに 2 進の四値すべてを覆っているので、この分岐に到達しうるのは up_dwn が x や z を含むときだけであり、そこで黙って計数を消すことは問題を隠すだけである。

Second, and worse, the state register is written with blocking assignment: always @ (negedge clock or negedge reset_) if (reset_ == 0) count = 3'b0; else count = next_count;. The combinational block is sensitive to count, so the two blocks are in a race at every clock edge — if the combinational block happens to run first it recomputes next_count from the old count, and if it runs second it recomputes from the new one, feeding a value back that the register has already consumed. Change both assignments to <= and the race disappears; this is §8.3 mechanism 2 in a design you might actually write.第二,而且更糟的是,狀態暫存器是用阻斷式指派寫的:always @ (negedge clock or negedge reset_) if (reset_ == 0) count = 3'b0; else count = next_count;。組合邏輯區塊對 count 敏感,所以這兩個區塊在每一個時脈邊緣上都處於競爭 —— 如果組合邏輯區塊剛好先跑,它會用舊的 count 重算 next_count;如果它後跑,它就用新的 count 重算,把一個暫存器已經消化掉的值再餵回去。把兩個指派都改成 <=,競爭就消失了;這就是 §8.3 的機制 2,出現在一份你真的可能會寫出來的設計裡。第二に、こちらのほうが悪いが、状態レジスタがブロッキング代入で書かれている:always @ (negedge clock or negedge reset_) if (reset_ == 0) count = 3'b0; else count = next_count;。組合せ論理ブロックは count に感応するので、二つのブロックはクロックエッジごとに競合している——組合せブロックがたまたま先に走れば古い count から next_count を計算し直し、後に走れば新しい count から計算し直して、レジスタがすでに消費した値を送り返すことになる。両方の代入を <= にすれば競合は消える。これは §8.3 の機構 2 が、実際に書きかねない設計に現れた例である。

11.6 The polling circuit11.6 輪詢電路11.6 ポーリング回路

The final example combines a state machine with a task, and it is the only place in the lecture where disable does real work. A server must poll three clients. The machine's state is which client is currently being served — none, client1, client2 or client3 — and its output s_code is that state. The input s_request[3:1] carries one request bit per client.最後一個例子把狀態機與任務結合起來,而且是整個講次裡唯一讓 disable 真正發揮作用的地方。一個伺服端必須輪詢三個客戶端。這台機器的狀態就是目前正在服務哪個客戶端 —— none、client1、client2 或 client3 —— 而它的輸出 s_code 就是那個狀態。輸入 s_request[3:1] 每個客戶端各帶一個請求位元。最後の例はステートマシンとタスクを組み合わせており、講義全体で disable が本当に仕事をする唯一の箇所である。サーバは三つのクライアントをポーリングせねばならない。この機械の状態は現在どのクライアントを処理しているか——none、client1、client2、client3——であり、出力 s_code がその状態である。入力 s_request[3:1] はクライアントごとに 1 ビットの要求を運ぶ。

module polling (s_request, s_code, clk, rst);
  `define client1 2'b01
  `define client2 2'b10
  `define client3 2'b11
  `define none    2'b00
  input  [3:1] s_request;
  input        clk, rst;
  output [1:0] s_code;
  reg    [1:0] next_client, present_client;

  always @ (posedge clk or posedge rst)
    begin
      if (rst) present_client = `none;
      else     present_client = next_client;
    end

  assign s_code[1:0] = present_client;

  always @ (present_client or s_request)
    begin
      poll_for_clients (present_client,
                        s_request, next_client);
    end
  task poll_for_clients;
    input  [1:0] present_client;
    input  [3:1] s_request;
    output [1:0] next_client;
    reg    [1:0] contender;
    integer N;
    begin : polling
      contender = `none;
      for (N = 3; N >= 1; N = N - 1)
        begin
          if (s_request[N])
            begin
              if (present_client == N)
                contender = present_client;
              else
                begin
                  next_client = N;
                  disable polling;
                end
            end
        end
      if ((next_client == `none) && (contender))
        next_client = contender;
    end
  endtask
endmodule

The arbitration policy is a mild form of fairness. The loop scans from client 3 down to client 1, so client 3 has the highest priority. When it finds a requesting client that is not the one currently being served, it grants to that client and disables the loop immediately — an early exit, which is the whole point of disable. When the requesting client is the one being served, the task does not grant; it records it as contender and keeps scanning, so that a different client gets a turn if one is asking. Only if no one else wants the bus does the final if re-grant to the incumbent.這個仲裁策略是一種溫和的公平性。迴圈從客戶端 3 往下掃到客戶端 1,所以客戶端 3 的優先權最高。當它找到一個不是目前正在服務的那個、而且正在請求的客戶端時,它就把權給那個客戶端,並立刻 disable 掉迴圈 —— 提早離開,這正是 disable 的全部用意。當提出請求的客戶端就是正在被服務的那個時,這個任務不授權;它把它記成 contender 並繼續掃,好讓另一個客戶端——如果有人在要的話——輪得到。只有在沒有別人要用匯流排時,最後那個 if 才會把權重新發給現任者。この調停方針は穏やかな公平性である。ループはクライアント 3 から 1 へ下りながら走査するので、クライアント 3 の優先度が最も高い。現在処理中のクライアントではない要求元を見つけると、そのクライアントに許可を与え、ただちにループを disable する——早期脱出であり、これこそ disable の要点である。要求元が処理中のクライアントそのものであれば、タスクは許可を与えず、contender として記録して走査を続ける。他のクライアントが要求していればそちらに順番が回るようにするためである。他に誰もバスを欲しがっていない場合にだけ、最後の if が現任者に再度許可を与える。

Three problems with this code這段程式碼的三個問題このコードの三つの問題

1. The named block and the module share a name. Both are called polling. disable polling resolves to the innermost matching scope, which is the block, so the code works — but a reader has to know the scoping rule to be sure, and one careless rename turns a loop exit into a module-wide kill. Name the block scan.1. 具名區塊與模組同名。兩者都叫 polling。disable polling 會解析到最內層相符的範圍,也就是那個區塊,所以程式碼能動 —— 但讀者得先知道這條範圍規則才敢確定,而一次不小心的改名就會把「離開迴圈」變成「殺掉整個模組」。請把那個區塊命名為 scan。1. 名前付きブロックとモジュールが同名である。どちらも polling という名である。disable polling は最も内側の一致するスコープ、すなわちブロックに解決されるのでコードは動く——が、読者はそのスコープ規則を知らないと確信が持てず、うかつな改名ひとつでループ脱出がモジュール全体の停止に変わる。このブロックは scan と名付けること。

2. next_client is read before it is written on every path. The final test if ((next_client == `none) && (contender)) reads the task's own output argument, which — because arguments are passed by value, §9.2 — was initialised from the caller's next_client on entry. So the grant decision depends on the previous grant. If the previous value happened to be non-none and nobody else is requesting, the incumbent is not re-granted and the bus is left on a stale code. Initialising next_client = `none; at the top of the task removes the dependency.2. next_client 在某些路徑上是先被讀才被寫的。最後那個測試 if ((next_client == `none) && (contender)) 讀的是這個任務自己的 output 引數,而它 —— 因為引數是傳值的,見 §9.2 —— 在進入任務時是從呼叫端的 next_client 初始化來的。所以授權的決定取決於上一次的授權。如果上一次的值剛好不是 none,而且沒有別人在請求,現任者就不會被重新授權,匯流排上留下的是一個過時的碼。在任務開頭加上 next_client = `none; 就消除了這個相依。2. next_client がすべての経路で、書かれる前に読まれている。最後の判定 if ((next_client == `none) && (contender)) はタスク自身の output 引数を読んでおり、それは——引数が値渡しであるため(§9.2)——タスクに入る際に呼び出し側の next_client から初期化されている。つまり許可の決定が前回の許可に依存する。前回の値がたまたま none 以外で、他に誰も要求していなければ、現任者は再許可されずバスには古いコードが残る。タスクの先頭で next_client = `none; と初期化すればこの依存は消える。

3. The state register uses blocking assignment — present_client = next_client; — in a block that races with the combinational block sensitive to present_client. Same problem as the up-down counter, same fix.3. 狀態暫存器用了阻斷式指派 —— present_client = next_client; —— 而它所在的區塊,與對 present_client 敏感的組合邏輯區塊之間存在競爭。和上下數計數器同樣的問題,同樣的修法。3. 状態レジスタがブロッキング代入を使っている——present_client = next_client;——そのブロックは present_client に感応する組合せ論理ブロックと競合する。アップダウンカウンタと同じ問題であり、直し方も同じである。

There is also a design-level observation worth making: a task called from a combinational always block, as here, must be purely combinational for the model to make sense. This one is — no delays, no event controls, every output assigned — but it is static (§9.7), so contender and N are shared storage. With a single caller that is harmless; instantiate two polling circuits in the same module and they would share the task's locals. Declaring it task automatic costs nothing and removes the hazard.另外還有一個設計層面的觀察值得提出來:像這裡這樣,一個從組合邏輯 always 區塊呼叫的任務,必須是純組合邏輯的,這個模型才說得通。這一個是 —— 沒有延遲、沒有事件控制、每一個輸出都有被指派 —— 但它是靜態的(§9.7),所以 contender 與 N 是共用儲存。只有一個呼叫者時這無害;但如果在同一個模組裡實例化兩個輪詢電路,它們就會共用這個任務的區域變數。把它宣告成 task automatic 不花成本,而且消除了這個風險。もう一つ、設計上の観察を挙げておく価値がある:ここのように組合せ論理の always ブロックから呼ばれるタスクは、モデルが意味をなすためには純粋に組合せ的でなければならない。このタスクはそうなっている——遅延なし、イベント制御なし、すべての出力に代入あり——が、static である(§9.7)ので contender と N は共有記憶である。呼び出し元が一つなら無害だが、同じモジュールにポーリング回路を二つインスタンス化すれば、タスクのローカル変数を共有してしまう。task automatic と宣言すればコストなしにこの危険は消える。

Practice 19 — Choosing an FSM style練習 19 — 選擇 FSM 的寫法練習 19 — FSM のスタイルを選ぶ

Problem: For each of the following, say which style you would use and why. (a) A UART transmitter that, once started, sends a start bit, eight data bits and a stop bit with no possibility of interruption. (b) A memory controller that must respond to read, write, refresh and abort requests arriving in any order. (c) A 4-bit Gray-code counter with enable. (d) A test bench that must drive a fixed 20-cycle initialisation sequence into a DUT.題目:對下列每一項,說出你會用哪種寫法,以及為什麼。(a) 一個 UART 發送器,一旦啟動就送出一個起始位元、八個資料位元與一個停止位元,中途不可能被打斷。(b) 一個記憶體控制器,必須回應以任意順序抵達的讀取、寫入、更新與中止請求。(c) 一個帶致能的 4 位元格雷碼計數器。(d) 一個必須把固定的 20 週期初始化序列打進 DUT 的測試平台。問題:次の各項について、どのスタイルを使うかとその理由を述べよ。(a) いったん起動すればスタートビット、8 ビットのデータ、ストップビットを中断の可能性なく送る UART 送信器。(b) 読み出し・書き込み・リフレッシュ・中止の要求が任意の順で到来するのに応答せねばならないメモリコントローラ。(c) イネーブル付き 4 ビットグレイコードカウンタ。(d) 固定の 20 サイクルの初期化シーケンスを DUT に打ち込まねばならないテストベンチ。

Click to reveal solution點擊展開解答クリックで解答を表示

(a) The sequence is fixed and each step has exactly one predecessor, so a true implicit machine (a chain of @(posedge clk) statements) is compact and readable. But "no possibility of interruption" is rarely true in practice — the moment a reset or an abort has to be honoured mid-byte, the implicit form becomes awkward. An explicit machine with a bit counter is the safer production choice.(a) 序列是固定的,而且每一步剛好只有一個前驅,所以真正的隱式機器(一串 @(posedge clk) 敘述)既簡潔又好讀。但「中途不可能被打斷」在實務上很少成立 —— 只要有一個重置或一個中止必須在位元組中途被尊重,隱式寫法就變得很彆扭。在量產設計裡,配上一個位元計數器的顯式機器才是比較安全的選擇。(a) シーケンスは固定で、各段の先行状態はちょうど一つなので、本物の暗黙的な機械(@(posedge clk) 文の連なり)が簡潔で読みやすい。しかし「中断の可能性がない」は実務ではめったに成り立たない——リセットや中止をバイトの途中で受け付けねばならなくなった瞬間、暗黙的スタイルは扱いにくくなる。量産設計ではビットカウンタを添えた明示的な機械のほうが安全な選択である。

(b) Explicit, without question. Requests can arrive in any order, so most states are reachable from several predecessors and the thread's position cannot encode the state. A tabulated case over the state and the request vector is the right structure.(b) 毫無疑問是顯式。請求可以用任意順序抵達,所以多數狀態都能從好幾個前驅到達,執行緒的位置編碼不了狀態。以狀態與請求向量為對象的列表式 case 才是正確的結構。(b) 疑いなく明示的である。要求は任意の順で到来しうるので、ほとんどの状態が複数の先行状態から到達可能であり、スレッドの位置では状態を符号化できない。状態と要求ベクタに対する表形式の case が正しい構造である。

(c) This is a counter, not a controller: write the next-state function as an expression (Gray code is count ^ (count >> 1) applied to a binary counter, or a four-term Boolean function) inside a single clocked block. Tabulating sixteen states by hand buys nothing and invites transcription errors.(c) 這是個計數器,不是控制器:把次態函數寫成一個運算式(格雷碼就是對一個二進位計數器套用 count ^ (count >> 1),或寫成一個四項的布林函數),放在單一個時脈同步區塊裡。用手把十六個狀態列成表什麼好處都沒有,只會招來抄寫錯誤。(c) これはコントローラではなくカウンタである:次状態関数を式として(グレイコードは 2 進カウンタに count ^ (count >> 1) を適用したもの、あるいは四項のブール関数として)単一のクロック同期ブロックに書く。十六の状態を手で表にしても何の得もなく、転記ミスを招くだけである。

(d) Implicit, and it need not even be an FSM — an initial block with a sequence of @(posedge clk) statements, or better a task automatic containing them, is exactly the right tool. Nothing is being synthesised, so none of the explicit-style constraints apply.(d) 隱式,而且它根本不必是一個 FSM —— 一個含有一串 @(posedge clk) 敘述的 initial 區塊,或者更好,一個裝著它們的 task automatic,才是完全正確的工具。這裡沒有東西要被合成,所以顯式寫法的那些約束一條都不適用。(d) 暗黙的であり、そもそも FSM である必要すらない——@(posedge clk) 文を並べた initial ブロック、あるいはより望ましくはそれらを収めた task automatic がまさに正しい道具である。合成されるものは何もないので、明示的スタイルの制約はどれも当てはまらない。

Practice 20 — Polling priority練習 20 — 輪詢的優先權練習 20 — ポーリングの優先順位

Problem: Using the task of §11.6, give next_client for each case. Assume next_client enters the task holding `none. (a) present_client = `none, s_request = 3'b000. (b) present_client = `none, s_request = 3'b101 (clients 3 and 1 asking). (c) present_client = `client3, s_request = 3'b100 (only client 3 asking). (d) present_client = `client3, s_request = 3'b110 (clients 3 and 2 asking).題目:用 §11.6 的那個任務,給出每一種情況下的 next_client。假設 next_client 進入任務時持有 `none。(a) present_client = `none、s_request = 3'b000。(b) present_client = `none、s_request = 3'b101(客戶端 3 與 1 在請求)。(c) present_client = `client3、s_request = 3'b100(只有客戶端 3 在請求)。(d) present_client = `client3、s_request = 3'b110(客戶端 3 與 2 在請求)。問題:§11.6 のタスクを使って、各場合の next_client を示せ。next_client はタスクに入る時点で `none を保持しているとする。(a) present_client = `none、s_request = 3'b000。(b) present_client = `none、s_request = 3'b101(クライアント 3 と 1 が要求)。(c) present_client = `client3、s_request = 3'b100(クライアント 3 のみ要求)。(d) present_client = `client3、s_request = 3'b110(クライアント 3 と 2 が要求)。

Click to reveal solution點擊展開解答クリックで解答を表示

(a) The loop finds no requests, contender stays `none, and the final test fails because contender is zero. next_client = `none. The bus idles.(a) 迴圈找不到任何請求,contender 維持 `none,而最後那個測試因為 contender 是零而失敗。next_client = `none。匯流排閒置。(a) ループは要求を一つも見つけず、contender は `none のままで、最後の判定は contender が零なので成立しない。next_client = `none。バスはアイドルになる。

(b) N = 3: s_request[3] is 1 and present_client (00) is not 3, so next_client = 3 and the loop is disabled immediately. Client 1 never gets looked at. next_client = `client3 — highest index wins.(b) N = 3:s_request[3] 是 1,而 present_client(00)不是 3,所以 next_client = 3,迴圈立刻被 disable。客戶端 1 根本沒被看到。next_client = `client3 —— 索引最高者勝。(b) N = 3:s_request[3] が 1 で present_client(00)は 3 ではないので next_client = 3 となり、ループはただちに disable される。クライアント 1 は見られもしない。next_client = `client3——添字の大きいほうが勝つ。

(c) N = 3: requesting, and present_client is 3, so contender = 3 and the scan continues. N = 2 and N = 1 are not requesting. The final test sees next_client == `none and a non-zero contender, so next_client = `client3. The incumbent keeps the bus because nobody else wants it.(c) N = 3:有請求,而且 present_client 就是 3,所以 contender = 3,掃描繼續。N = 2 與 N = 1 都沒有請求。最後那個測試看到 next_client == `none 而且 contender 非零,所以 next_client = `client3。現任者保住匯流排,因為沒有別人要。(c) N = 3:要求があり、しかも present_client がまさに 3 なので contender = 3 として走査を続ける。N = 2 と N = 1 には要求がない。最後の判定は next_client == `none かつ contender が非零であることを見て、next_client = `client3 とする。他に欲しがる者がいないので現任者がバスを保つ。

(d) N = 3: incumbent, so contender = 3, continue. N = 2: requesting and not the incumbent, so next_client = 2 and the loop exits. next_client = `client2. The incumbent is displaced even though it has the higher index — this is the anti-starvation rule, and it is the only interesting thing the task does.(d) N = 3:是現任者,所以 contender = 3,繼續。N = 2:有請求而且不是現任者,所以 next_client = 2,迴圈離開。next_client = `client2。現任者即使索引比較高也被擠掉了 —— 這就是那條反飢餓規則,也是這個任務唯一有趣的地方。(d) N = 3:現任者なので contender = 3 として続行。N = 2:要求があり現任者ではないので next_client = 2 となり、ループを抜ける。next_client = `client2。添字が大きいにもかかわらず現任者が追い出される——これが飢餓防止の規則であり、このタスクで唯一面白い点である。

Now repeat (c) assuming next_client entered holding `client1 from a previous grant: the final test fails because next_client != `none, so the output stays at `client1 even though client 1 is not asking. That is bug 2 from the warning above.現在把 (c) 重做一次,但假設 next_client 進入任務時帶著上一次授權留下的 `client1:最後那個測試會因為 next_client != `none 而失敗,所以輸出就停在 `client1,即使客戶端 1 根本沒在請求。那就是上面警告裡的第 2 個臭蟲。ここで (c) を、next_client が前回の許可から `client1 を保持したままタスクに入ったと仮定してやり直してみる:最後の判定は next_client != `none なので成立せず、クライアント 1 が要求していないのに出力は `client1 にとどまる。これが上の警告にあるバグ 2 である。

12. Summary, Checklist and Errata12. 總結、檢查清單與勘誤12. まとめ・チェックリスト・正誤表

12.1 Exam checklist12.1 考試檢查清單12.1 試験チェックリスト

Behaviors行為描述振る舞い
  • initial runs once; always forever.initial 跑一次;always 跑永遠。initial は一度、always は永遠に走る。
  • Every always needs a time control on every path.每個 always 的每一條路徑上都需要一個時間控制。すべての always は、あらゆる経路にタイミング制御を必要とする。
  • Procedural statements write variables, not nets.程序性敘述寫的是變數,不是 net。手続き型文が書き込むのは変数であってネットではない。
  • Blocks are concurrent; statements inside are not.區塊之間是並行的;區塊內部的敘述不是。ブロックどうしは並行だが、ブロック内の文はそうではない。
= versus <== 與 <= 的比較= と <= の比較
  • = is one Active event; <= is Active read plus NBA write.= 是一個 Active 事件;<= 是 Active 的讀加上 NBA 的寫。= は一つの Active イベント、<= は Active での読みと NBA での書きである。
  • Clocked → <=; combinational → =.時脈同步 → <=;組合邏輯 → =。クロック同期 → <=、組合せ論理 → =。
  • With <=, statement order cannot change the result.用 <= 時,敘述順序改變不了結果。<= を使えば、文の順序は結果を変えられない。
  • Never mix on one variable.絕不要在同一個變數上混用。同一の変数で混用してはならない。
PCAPCAPCA
  • assign/deassign: whole registers only.assign/deassign:只能用在完整的暫存器上。assign/deassign:レジスタ全体にのみ使える。
  • force/release: nets or registers, top priority.force/release:net 或暫存器皆可,優先權最高。force/release:ネットでもレジスタでも可、優先順位は最上位。
  • Released net reverts; released register does not.被釋放的 net 會回復;被釋放的暫存器不會。解放されたネットは戻るが、解放されたレジスタは戻らない。
  • Both are verification tools, not RTL.兩者都是驗證工具,不是 RTL。どちらも検証の道具であって RTL ではない。
Timing controls時間控制タイミング制御
  • # time, @ edge, wait level.# 看時間,@ 看邊緣,wait 看電位。# は時間、@ はエッジ、wait はレベル。
  • posedge includes 0→x and x→1.posedge 包含 0→x 與 x→1。posedge は 0→x と x→1 を含む。
  • @ blocks even when already true; wait does not.@ 即使條件已成立也照樣擋住;wait 不會。@ は条件がすでに真でもブロックするが、wait はしない。
  • Named event: event + ->, stateless.具名事件:event 加 ->,無狀態。名前付きイベント:event と ->、状態を持たない。
Delay placement延遲擺放的位置遅延を置く位置
  • #5 a = b reads b late; a = #5 b reads it now.#5 a = b 晚讀 b;a = #5 b 現在就讀。#5 a = b は b を遅く読み、a = #5 b はいま読む。
  • Both block; a <= #5 b does not.兩者都會阻斷;a <= #5 b 不會。どちらもブロックするが、a <= #5 b はしない。
  • assign #d = inertial, filters narrow pulses.assign #d = 慣性,會濾掉窄脈衝。assign #d = 慣性。狭いパルスを濾し取る。
  • y <= #d = transport, keeps every pulse.y <= #d = 傳輸,每一個脈衝都保留。y <= #d = 伝送。あらゆるパルスを保つ。
Scheduling排程スケジューリング
  • Active → Inactive(#0) → NBA → Postponed.Active → Inactive(#0) → NBA → Postponed。Active → Inactive(#0) → NBA → Postponed。
  • $display Active; $strobe/$monitor Postponed.$display 在 Active;$strobe/$monitor 在 Postponed。$display は Active、$strobe/$monitor は Postponed。
  • Only one $monitor at a time.同一時間只能有一個 $monitor。$monitor は同時に一つだけ。
  • #0 hides races, it does not fix them.#0 是把競爭藏起來,不是把它修好。#0 は競合を隠すのであって直すのではない。
Flow control流程控制フロー制御
  • Incomplete case/if in a combinational block → latch.組合邏輯區塊裡不完整的 case/if → 閂鎖。組合せ論理ブロックでの不完全な case/if → ラッチ。
  • casez safer than casex: x selector matches everything.casez 比 casex 安全:x 的選擇式什麼都相符。casez は casex より安全:x の選択式は何にでも一致する。
  • Loop bound must be static to synthesise.迴圈界限必須是靜態的才能合成。ループの境界が静的でなければ合成できない。
  • Index width can make a loop infinite.索引的寬度可能讓迴圈變成無窮迴圈。添字の幅がループを無限ループにしうる。
fork / joinfork / joinfork / join
  • Delays are absolute from the fork.延遲是從 fork 起算的絕對時間。遅延は fork からの絶対時刻である。
  • join waits for every thread.join 會等每一條執行緒。join はすべてのスレッドを待つ。
  • Race = two events, same slot, same region.競爭 = 兩個事件、同一時槽、同一區域。競合 = 二つのイベント、同じタイムスロット、同じ領域。
  • Kill it with time, with region, or with ordering.用時間、用區域,或用順序把它消掉。時間で、領域で、あるいは順序で消すこと。
Tasks & functions任務與函式タスクと関数
  • Function: ≥1 input, no outputs, no time, returns by name.函式:至少 1 個輸入,沒有輸出,不花時間,以名字回傳。関数:入力 1 個以上、出力なし、時間を消費せず、名前で返す。
  • Task: any ports, may consume time, called as a statement.任務:任何埠都可以,可以消耗時間,以敘述形式呼叫。タスク:ポートは自由、時間を消費してよく、文として呼ぶ。
  • Arguments are by value, associated positionally.引數是傳值的,依位置對應。引数は値渡しで、位置により対応づけられる。
  • Default storage is static — use automatic.預設儲存是靜態的 —— 請用 automatic。既定の記憶は static——automatic を使うこと。
Timing analysis時序分析タイミング解析
  • slack = required − arrival.餘裕 = 需求時間 − 到達時間。スラック = 要求時間 − 到着時間。
  • Arrival takes max forward; required takes min backward.到達時間往前取最大值;需求時間往後取最小值。到着時間は前向きに最大、要求時間は後ろ向きに最小を取る。
  • STA is exhaustive but reports false paths.STA 是窮盡的,但會回報偽路徑。STA は網羅的だが、フォルスパスも報告する。
  • $setup lists data first, $hold lists the clock first.$setup 把資料寫在前面,$hold 把時脈寫在前面。$setup はデータを先に、$hold はクロックを先に並べる。
  • $skew is the only "exceeds" check.$skew 是唯一用「超過」判定的檢查。$skew だけが「超える」で判定するチェックである。
FSMs有限狀態機有限状態機械
  • Moore: outputs from state. Mealy: state plus inputs.Moore:輸出來自狀態。Mealy:狀態加上輸入。ムーア型:出力は状態から。ミーリ型:状態と入力から。
  • Prefer Moore, or register the Mealy outputs.優先選 Moore,或把 Mealy 的輸出暫存起來。ムーア型を選ぶか、ミーリ型の出力をレジスタに入れる。
  • Explicit: state register plus case. Two blocks.顯式:狀態暫存器加 case。兩個區塊。明示的:状態レジスタと case。ブロックは二つ。
  • Implicit only when each state has one predecessor.只有在每個狀態都只有一個前驅時才用隱式。各状態の先行状態が一つだけのときにのみ暗黙的にする。
Habits that prevent bugs能預防臭蟲的習慣バグを防ぐ習慣
  • always @* for every combinational block.每一個組合邏輯區塊都用 always @*。組合せ論理ブロックにはすべて always @* を使う。
  • Assign every output at the top of a combinational block.在組合邏輯區塊的開頭就把每一個輸出都指派一次。組合せ論理ブロックの先頭ですべての出力に代入しておく。
  • One writer per variable, always.每個變數永遠只有一個寫入者。変数ごとに書き手はつねに一つだけ。
  • parameter, not `define, for state encodings.狀態編碼用 parameter,不要用 `define。状態の符号化には `define ではなく parameter を使う。

12.2 The non-synthesisable list12.2 不可合成清單12.2 合成不可能なものの一覧

Everything in this lecture that will not become hardware本講中所有不會變成硬體的東西この講義でハードウェアにならないものすべて

initial blocks; all # delay controls, including intra-assignment ones; wait; named events and ->; assign ... deassign and force ... release (tool dependent, usually unsupported); repeat (n) @(edge) intra-assignment delays; fork ... join; forever with delays; disable of anything other than a loop exit; every system task ($display, $monitor, $strobe, $finish, $setup, $hold, …); loops whose trip count depends on a signal value; and recursion, even with automatic.initial 區塊;所有的 # 延遲控制,包括指派內的;wait;具名事件與 ->;assign ... deassign 與 force ... release(視工具而定,通常不支援);repeat (n) @(edge) 形式的指派內延遲;fork ... join;帶延遲的 forever;用在離開迴圈以外用途的 disable;每一個系統任務($display、$monitor、$strobe、$finish、$setup、$hold、…);迴圈次數相依於訊號值的迴圈;以及遞迴,即使加上 automatic 也一樣。initial ブロック、すべての # 遅延制御(代入内のものを含む)、wait、名前付きイベントと ->、assign ... deassign と force ... release(ツール依存で、たいていは非対応)、repeat (n) @(edge) 形式の代入内遅延、fork ... join、遅延つきの forever、ループ脱出以外の用途の disable、すべてのシステムタスク($display、$monitor、$strobe、$finish、$setup、$hold、…)、繰り返し回数が信号値に依存するループ、そして再帰(automatic を付けても同じ)。

The complementary statement is just as useful: the synthesisable subset of this lecture is always blocks with a legal sensitivity list, = and <=, if/case/casez, statically bounded loops, functions, tasks without timing controls, and the conditional operator. That is a small language, and essentially every RTL file you will write for the rest of the course is built from it.反過來講同樣有用:本講中可合成的子集是「帶合法敏感度列表的 always 區塊、= 與 <=、if/case/casez、靜態有界的迴圈、函式、不含時間控制的任務,以及條件運算子」。那是一套很小的語言,而你在這門課接下來會寫的每一個 RTL 檔案,基本上都是用它搭出來的。裏返した言い方も同じく有用である:この講義の合成可能な部分集合は、合法な感度リストを持つ always ブロック、= と <=、if/case/casez、静的に有界なループ、関数、タイミング制御を含まないタスク、そして条件演算子である。これは小さな言語であり、この講義の残りで書く RTL ファイルは本質的にすべてこれで組み立てられる。

12.3 Slide errata and deliberate corrections12.3 投影片勘誤與刻意的更正12.3 スライドの正誤表と意図的な訂正

Points where this page departs from the slides, with the reason:本頁與投影片不同之處,以及理由:このページがスライドと異なる箇所と、その理由。

Slide投影片スライドAs printed原文印刷されている内容Correction更正訂正
Part 1, p.6第 1 部,p.6パート 1、p.6d <= 0; d <= 1; is "ambiguous → illegal"d <= 0; d <= 1; 是「有歧義 → 不合法」d <= 0; d <= 1; は「曖昧 → 違法」Within one sequential block the NBA updates are ordered, so d deterministically becomes 1. Genuine ambiguity requires two different processes. See §2.3.在單一個循序區塊裡,NBA 更新是有序的,所以 d 確定會變成 1。真正的歧義需要兩個不同的行程。見 §2.3。一つの逐次ブロック内では NBA の更新に順序があるので、d は確定的に 1 になる。真の曖昧さには異なる二つのプロセスが必要である。§2.3 を参照。
Part 1, p.34第 1 部,p.34パート 1、p.34"If incomplete case list, the circuit after synthesis would be latch"「若 case 列表不完整,合成後的電路會是閂鎖」「case リストが不完全なら、合成後の回路はラッチになる」True only in a combinational block. In a clocked block an incomplete case simply means the flip-flop holds, and no extra hardware appears. See §7.2.只有在組合邏輯區塊裡才成立。在時脈同步區塊裡,不完整的 case 只不過意味著正反器保持原值,不會多出任何硬體。見 §7.2。組合せ論理ブロックでのみ正しい。クロック同期ブロックでは、不完全な case はフリップフロップが値を保持するという意味にすぎず、余分なハードウェアは現れない。§7.2 を参照。
Part 1, p.35第 1 部,p.35パート 1、p.35The casex/casez definitions carry hand-drawn strike-throughs whose intent is unclearcasex/casez 的定義上有手畫的刪除線,意圖不明casex/casez の定義に手書きの取り消し線があり、意図が不明The IEEE rule is: casez treats z and ? as don't care; casex treats x, z and ?. In both, the don't-care applies to the selector as well as the items. See §7.3.IEEE 的規則是:casez 把 z 與 ? 當成不在乎;casex 把 x、z 與 ? 都當成不在乎。兩者的不在乎都同時適用於選擇式與項目。見 §7.3。IEEE の規則はこうである:casez は z と ? をドントケアとして扱い、casex は x、z、? を扱う。どちらでもドントケアは項目だけでなく選択式にも適用される。§7.3 を参照。
Part 1, p.39第 1 部,p.39パート 1、p.39The carry chain needs #0 "for simulation"進位鏈「為了模擬」需要 #0キャリーチェーンには「シミュレーションのために」#0 が必要Correct as a statement about that code, but the #0 is covering a race. always @* removes the need for it. See §6.3 and §7.6.就那段程式碼而言這句話沒錯,但那個 #0 是在掩蓋一個競爭。改用 always @* 就不需要它了。見 §6.3 與 §7.6。そのコードについての記述としては正しいが、その #0 は競合を覆い隠している。always @* にすれば不要になる。§6.3 と §7.6 を参照。
Part 1, p.42第 1 部,p.42パート 1、p.42if (a[i] == 1) disable;Not legal Verilog. disable requires the name of a block or task; the loop must be wrapped in a named block. See §7.7.不是合法的 Verilog。disable 需要一個區塊或任務的名字;那個迴圈必須包在一個具名區塊裡。見 §7.7。合法な Verilog ではない。disable にはブロックかタスクの名前が要り、そのループは名前付きブロックで包まねばならない。§7.7 を参照。
Part 2, p.5第 2 部,p.5パート 2、p.5The task declares both output [3:0] c; and reg [3:0] c;這個任務同時宣告了 output [3:0] c; 與 reg [3:0] c;このタスクは output [3:0] c; と reg [3:0] c; の両方を宣言しているRedundant and rejected by some tools. Write output reg [3:0] c; or drop the second line. See §9.4.多餘,而且有些工具會拒絕。請寫 output reg [3:0] c;,或刪掉第二行。見 §9.4。冗長であり、拒否するツールもある。output reg [3:0] c; と書くか、二行目を削ること。§9.4 を参照。
Part 2, p.17第 2 部,p.17パート 2、p.17"Mealy machine: output is related to next states and inputs"「Mealy 機器:輸出與次態和輸入有關」「ミーリ型機械:出力は次状態と入力に関係する」The conventional definition uses the present state, which is also what the slide's own block diagram draws. See §11.1.慣例的定義用的是現態,而那也正是投影片自己的方塊圖所畫的。見 §11.1。慣例的な定義は現状態を使っており、スライド自身のブロック図が描いているのもそれである。§11.1 を参照。
Part 2, pp.19–21第 2 部,pp.19–21パート 2、pp.19–21if (reset == 1'b0) state = start_state; else state <= next_state;Mixes = and <= on one register in one block. Use <= in both branches. See §11.2.在一個區塊裡對同一個暫存器混用 = 與 <=。兩個分支都該用 <=。見 §11.2。一つのブロック内で同じレジスタに = と <= を混用している。どちらの分岐も <= にすべきである。§11.2 を参照。
Part 2, p.23第 2 部,p.23パート 2、p.23parameter s_slow = 2'b01; but every case item says s_low宣告是 parameter s_slow = 2'b01;,但每個 case 項目寫的都是 s_low宣言は parameter s_slow = 2'b01; だが、case 項目はどれも s_low と書かれているA typo; the declaration should read s_low. See §11.4.這是筆誤;宣告應該寫成 s_low。見 §11.4。誤記である。宣言は s_low とすべきである。§11.4 を参照。
Part 2, pp.26–27第 2 部,pp.26–27パート 2、pp.26–27The state registers use blocking assignment狀態暫存器用了阻斷式指派状態レジスタがブロッキング代入を使っているRaces against the combinational block that is sensitive to the same variable. Use <=. See §11.5.會與對同一個變數敏感的組合邏輯區塊發生競爭。請用 <=。見 §11.5。同じ変数に感応する組合せ論理ブロックと競合する。<= を使うこと。§11.5 を参照。
Part 2, p.29第 2 部,p.29パート 2、p.29The task's named block and the module share the name polling; next_client is read before being written任務裡的具名區塊與模組共用 polling 這個名字;next_client 在被寫之前就先被讀タスク内の名前付きブロックとモジュールが polling という名を共有している。next_client が書かれる前に読まれているRename the block, and initialise next_client at the top of the task. See §11.6.請把那個區塊改名,並在任務開頭初始化 next_client。見 §11.6。ブロックの名前を変え、タスクの先頭で next_client を初期化すること。§11.6 を参照。
Where this material goes next這些材料接下來往哪裡走この題材はこの先どこへ向かうか

Lecture 5 takes the combinational subset of §7 and asks what each construct becomes in a netlist — priority versus parallel decode, latch inference, and resource sharing. Lecture 6 does the same for the sequential subset of §2 and §11, formalising the flip-flop and latch templates that synthesis recognises. Lecture 7 goes construct by construct through the whole language marking what is and is not synthesisable, which makes §12.2 above its executive summary. Lecture 3 is the prerequisite for §3.5 (net types and resolution) and for the UDP column of the assignment-mode table.第 5 講會把第 7 節裡的組合邏輯子集拿來,問每一個構造在 netlist 裡會變成什麼 —— 優先權解碼與平行解碼的對比、閂鎖推斷,以及資源共用。第 6 講對第 2 節與第 11 節的循序子集做同樣的事,把合成所認得的正反器與閂鎖樣板形式化。第 7 講則逐一走過整個語言的每個構造,標示出哪些可合成、哪些不可,而上面的 §12.2 正是它的摘要。第 3 講是 §3.5(net 的型別與解析)以及指派模式表中 UDP 那一欄的先備知識。第 5 講は第 7 節の組合せ論理の部分集合を取り上げ、各構文がネットリストで何になるかを問う——優先順位デコードと並列デコードの対比、ラッチ推論、リソース共有である。第 6 講は第 2 節と第 11 節の順序論理の部分集合について同じことを行い、合成が認識するフリップフロップとラッチのテンプレートを形式化する。第 7 講は言語全体を構文ごとにたどって合成可能か否かを示し、上の §12.2 はその要約にあたる。第 3 講は §3.5(ネットの型と解決)と、代入モード表の UDP 列の前提知識である。