A long-form walk through event-driven simulation, the stratified event queue, inertial delay and pulse rejection, testbench construction and the system tasks, four-value logic, nets and registers, memories, parameters, every operator class, and hierarchical de-referencing.一篇長篇導覽,走過事件驅動模擬、分層事件佇列、慣性延遲與脈衝抑制、測試平台的建構與系統任務、四值邏輯、連線與暫存器、記憶體、參數、所有運算子類別,以及階層式解參考。イベント駆動シミュレーション、層化イベントキュー、慣性遅延とパルス除去、テストベンチの構築とシステムタスク、四値論理、ネットとレジスタ、メモリ、パラメータ、全演算子クラス、そして階層的デリファレンスまでを通して歩く長編解説。
In Lecture 1 you learned to write a module that describes hardware. Nothing in that module tells you whether the hardware is correct. A Verilog compiler will happily accept a four-bit adder that computes a - b, an FSM that never leaves its reset state, or a FIFO whose full flag asserts one cycle late. Those are not syntax errors; they are design errors, and the only tool that finds them before silicon is simulation driven by a testbench.在第 1 講中,你學會了寫一個描述硬體的模組。但那個模組本身完全不會告訴你硬體是否正確。Verilog 編譯器會毫不猶豫地接受一個其實在算 a - b 的四位元加法器、一個永遠離不開重置狀態的有限狀態機,或是一個 full 旗標晚一個週期才拉起的 FIFO。那些都不是語法錯誤,而是設計錯誤;在進矽之前唯一能找出它們的工具,就是由測試平台(testbench)所驅動的模擬。第 1 講では、ハードウェアを記述するモジュールの書き方を学んだ。しかしそのモジュール自体は、そのハードウェアが正しいかどうかを一切教えてくれない。Verilog コンパイラは、実際には a - b を計算する 4 ビット加算器も、リセット状態から永遠に出ない FSM も、full フラグが 1 サイクル遅れて立つ FIFO も、平然と受け入れる。これらは構文エラーではなく設計エラーであり、シリコンになる前にそれを見つけられる唯一の道具が、テストベンチによって駆動されるシミュレーションである。
It is worth being blunt about the economics, because this is the reason the course spends a whole lecture on something that produces no gates. A modern cell-based ASIC tape-out costs a mask set in the high six figures and three to four months of turnaround. A bug found in simulation costs an afternoon. A bug found at wafer probe costs a respin. The industry rule of thumb is that verification consumes between half and two thirds of the total engineering effort on a chip, and that ratio has been getting worse, not better, for thirty years. When a lecture slide says “a testbench verifies the functionality of a design”, the subtext is that the testbench is where most of your working life as an IC designer will actually be spent.在此不妨把經濟面講白,因為這正是本課程願意花整整一講去談一個「產生不出任何閘」的主題的理由。現代 cell-based ASIC 的一次 tape-out,光光罩組就要數十萬美元等級,外加三到四個月的週轉時間。在模擬中找到的臭蟲,代價是一個下午。在晶圓針測時才找到的臭蟲,代價是一次重跑(respin)。業界的經驗法則是:驗證耗掉一顆晶片總工程投入的一半到三分之二,而且這個比例三十年來只有變糟、沒有變好。當投影片上寫著「測試平台用來驗證設計的功能」時,它的潛台詞是:身為 IC 設計者,你職業生涯的大部分時間其實都會花在測試平台上。ここでは経済性についてはっきり言っておく価値がある。ゲートを一つも生まないテーマに本講義が丸ごと一回分を割く理由がそこにあるからだ。現代のセルベース ASIC のテープアウトは、マスクセットだけで数十万ドル規模、さらに 3〜4 か月のターンアラウンドを要する。シミュレーションで見つかったバグの代償は午後一回分。ウェハプローブで見つかったバグの代償は再試作(respin)である。業界の経験則では、検証はチップ全体のエンジニアリング工数の半分から三分の二を消費し、その比率はこの 30 年間、改善どころか悪化し続けてきた。講義スライドが「テストベンチは設計の機能を検証する」と書くとき、その裏の意味は、IC 設計者としての職業人生の大半は実際にはテストベンチの上で費やされる、ということだ。
A second, quieter benefit: a testbench is an executable specification. If you can write a checker that says “after n cycles of this input stream the output must equal this value”, you have written down, unambiguously and in a form a machine can police, what the block is supposed to do. Natural-language specifications rot; testbenches fail loudly when the design drifts away from them.還有第二個比較低調的好處:測試平台是一份可執行的規格書。如果你能寫出一個檢查器,說「在這串輸入流經過 n 個週期之後,輸出必須等於這個值」,那你就已經把這個區塊該做什麼,用毫不含糊、而且機器能夠稽核的形式寫下來了。自然語言寫的規格會腐壞;測試平台則會在設計偏離它時大聲失敗。もう一つ、地味だが重要な利点がある。テストベンチは実行可能な仕様書だという点だ。「この入力列を n サイクル与えた後、出力はこの値に等しくなければならない」と述べるチェッカを書けたなら、そのブロックが何をすべきかを、曖昧さなく、しかも機械が取り締まれる形で書き下したことになる。自然言語の仕様は腐る。テストベンチは、設計がそこから離れたときに声を上げて失敗する。
The canonical picture is a box that wraps the design. The design under test — universally abbreviated DUT, occasionally UUT for “unit under test” — sits inside a top-level module that has no ports at all. That portlessness is the structural signature of a testbench: there is nothing above it to connect to, because it is the root of the simulation hierarchy. Everything the DUT needs comes from inside the wrapper.標準的圖像是一個把設計包起來的盒子。受測設計 —— 業界一律縮寫成 DUT(design under test),偶爾寫成 UUT(unit under test)—— 坐在一個完全沒有任何埠的頂層模組裡面。沒有埠正是測試平台的結構性特徵:它上面沒有東西可以接,因為它就是模擬階層的根。DUT 需要的一切,都來自這層包裝的內部。典型的な図は、設計を包み込む一つの箱である。被試験設計 —— 業界では一律に DUT(design under test)と略し、ときに UUT(unit under test)とも書く —— は、ポートをまったく持たないトップレベルモジュールの中に置かれる。ポートがないことこそテストベンチの構造上の目印である。上位に接続すべきものが何も無いからであり、それ自体がシミュレーション階層のルートだからだ。DUT が必要とするものはすべて、このラッパーの内側から供給される。
Four jobs are visible in the figure, and it is worth naming them separately because real testbenches separate them in code too:圖中可以看到四項工作,值得分別命名,因為真實的測試平台在程式碼上也會把它們分開:図には四つの仕事が見える。実際のテストベンチはコード上でもこれらを分離するので、別々に名前を付けておく価値がある。
initial block with a list of delayed assignments. In a serious one it is a constrained random generator, a file reader, or a transaction-level driver.激勵產生。必須有東西去撥動 DUT 的輸入。在最簡單的測試平台裡,這就是一個 initial 區塊,裡面列著一串帶延遲的指定。在認真一點的測試平台裡,它會是一個受限隨機產生器、一個檔案讀取器,或一個交易層級的 driver。刺激の生成。DUT の入力を動かすものが必要である。最も単純なテストベンチでは、遅延付き代入を並べた initial ブロック一つがそれにあたる。本格的なものでは、制約付きランダム生成器、ファイルリーダ、あるいはトランザクションレベルのドライバになる。$readmemh or $fscanf.黃金參考。必須有東西知道正確答案是什麼。對組合邏輯區塊,你也許可以就地算出來;對通訊類區塊 —— FIR 濾波器、Viterbi 解碼器、turbo 解碼器 —— 參考答案幾乎一定是一個位元精確的 C 或 MATLAB 模型,其輸出被傾印成文字檔,再由測試平台用 $readmemh 或 $fscanf 讀回來。ゴールデンリファレンス。正解を知っているものが必要である。組合せ回路なら直接その場で計算できることもあるが、通信系のブロック —— FIR フィルタ、ビタビ復号器、ターボ復号器 —— では、リファレンスはほぼ必ずビット精度の C または MATLAB モデルであり、その出力をテキストファイルにダンプして、テストベンチが $readmemh や $fscanf で読み戻す。$monitor text output, and a value-change dump for the waveform viewer.觀察。必須有東西記錄下發生了什麼,好讓人類能除錯失敗的案例:$monitor 的文字輸出,以及給波形檢視器用的值變化傾印(value-change dump)。観測。何が起きたかを記録し、人間が失敗をデバッグできるようにするものが必要である。$monitor によるテキスト出力と、波形ビューア用の値変化ダンプ(value-change dump)である。A Verilog design is elaborated as a tree of module instances. Simulation needs exactly one root. If your top module had ports, nothing would drive them, so every input would sit at z for the whole run and every output would go nowhere. Making the testbench portless is not a style convention — it is what makes it the root.Verilog 設計會被展開(elaborate)成一棵模組實例組成的樹。模擬需要剛好一個根。如果你的頂層模組有埠,就沒有東西去驅動它們,於是每個輸入在整段模擬中都停在 z,每個輸出也哪裡都去不了。把測試平台做成無埠不是一種風格慣例 —— 那正是讓它成為根的原因。Verilog の設計は、モジュールインスタンスの木としてエラボレートされる。シミュレーションにはルートがちょうど一つ必要である。トップモジュールにポートがあれば、それを駆動するものが無いので、すべての入力は実行中ずっと z のままになり、すべての出力はどこにも行かない。テストベンチをポート無しにするのはスタイル上の約束事ではなく、それをルートたらしめているものである。
The slide states flatly: Can’t be synthesized!! (Only for behavioral modeling). That is correct, but it is worth understanding why, because the same reasoning tells you which constructs are forbidden inside the DUT as well — a distinction Lecture 7 will make formal.投影片講得很乾脆:Can’t be synthesized!! (Only for behavioral modeling)。這沒錯,但值得弄懂為什麼,因為同一套推理也告訴你哪些語法結構在 DUT 內同樣是禁忌 —— 這個分野會由 Lecture 7 正式確立。スライドはそっけなくこう述べている。Can’t be synthesized!! (Only for behavioral modeling)。それは正しいが、なぜかを理解しておく価値がある。同じ理屈が、DUT の中で禁じられる構文がどれかも教えてくれるからだ。この線引きは Lecture 7 で正式に扱われる。
A synthesis tool reads Verilog and emits a netlist of standard cells. To do that it must be able to answer one question about every statement: what combinational function, and what storage, does this imply? A testbench is deliberately full of statements for which that question has no answer. Consider:合成工具讀進 Verilog,吐出一份標準元件的網表(netlist)。要做到這件事,它必須能對每一條敘述回答同一個問題:這暗示了什麼組合函數、又暗示了什麼儲存元件?而測試平台刻意寫滿了對這個問題無解的敘述。看看以下這些:合成ツールは Verilog を読み、標準セルのネットリストを出力する。そのためには、すべての文について一つの問いに答えられなければならない。これはどんな組合せ関数と、どんな記憶素子を意味するのか? テストベンチは、その問いに答えが存在しない文で意図的に埋め尽くされている。たとえば次のようなものだ。
| Construct語法結構構文 | Why there is no hardware for it為什麼它沒有對應的硬體対応するハードウェアが存在しない理由 |
|---|---|
initial | Describes something that happens exactly once, at power-on, at a known time. Silicon has no “run this once at t = 0” primitive; real flip-flops come up in an arbitrary state and need a reset.描述某件事恰好發生一次,在通電瞬間、在一個已知時刻。矽裡沒有「在 t = 0 跑這個一次」這種基本元件;真實的正反器開機時處於任意狀態,需要重置。ある事柄が電源投入時という既知の時刻にちょうど一度だけ起きることを記述する。シリコンには「t = 0 でこれを一度実行する」というプリミティブは無い。実際のフリップフロップは任意の状態で立ち上がるので、リセットが要る。 |
#10 (delay control)#10(延遲控制)#10(遅延制御) | Asks the simulator to wait ten time units. There is no cell whose function is “wait”. Delays in real hardware are a consequence of physics, not a specification.要求模擬器等待十個時間單位。沒有任何一顆元件的功能是「等待」。真實硬體中的延遲是物理的後果,不是一項規格。シミュレータに 10 時間単位待てと指示する。機能が「待つ」であるようなセルは存在しない。実ハードウェアの遅延は物理の結果であって、仕様として与えるものではない。 |
$monitor, $display, $finish, $fopen | System tasks. They call into the simulator, print to a terminal, write files. A chip has no terminal.系統任務。它們呼叫進模擬器、印到終端機、寫檔案。晶片沒有終端機。システムタスク。シミュレータを呼び出し、端末に表示し、ファイルを書く。チップに端末は無い。 |
real, realtime | IEEE-754 double precision. Synthesis has no floating-point net type.IEEE-754 倍精度浮點。合成沒有浮點的連線型別。IEEE-754 倍精度浮動小数点。合成に浮動小数点のネット型は無い。 |
force / release, hierarchical namesforce / release、階層式名稱force / release、階層名 | Reach into another module and override a value. No wire can do that.伸手進另一個模組去覆寫一個值。沒有任何一條線辦得到。別のモジュールに手を伸ばして値を上書きする。どんな配線にもそれはできない。 |
Unbounded while / forever without a timing control沒有時序控制的無界 while / foreverタイミング制御を伴わない無限の while / forever | Either an infinite combinational loop or nothing at all. Synthesis must be able to unroll loops at compile time.不是一個無窮的組合迴路,就是什麼也不是。合成必須能在編譯期把迴圈展開。無限の組合せループになるか、まったく何にもならないかのどちらかである。合成はコンパイル時にループを展開できなければならない。 |
The practical consequence is a discipline, not a prohibition: you are encouraged to use all of this in the testbench and forbidden to use any of it in the DUT. Keep the two in separate files, give testbench files a recognisable prefix such as tb_ or test_, and never add a testbench file to the synthesis script. Many groups enforce this by putting the DUT sources in one directory and the verification environment in another.實務上的後果是一條紀律,而不是一道禁令:在測試平台裡,你被鼓勵使用上述全部;在 DUT 裡,你被禁止使用其中任何一項。把兩者分開放在不同檔案,給測試平台檔案一個一眼認得出的前綴,例如 tb_ 或 test_,而且永遠不要把測試平台檔案加進合成腳本。許多團隊的做法是把 DUT 原始碼放一個目錄、驗證環境放另一個目錄,以制度強制這條界線。実務上の帰結は禁止令ではなく規律である。テストベンチの中ではこれらをすべて使うことが推奨され、DUT の中ではそのいずれも使うことが禁止される。両者は別ファイルに分け、テストベンチのファイルには tb_ や test_ のような一目でわかる接頭辞を付け、合成スクリプトにテストベンチのファイルを決して加えない。多くのグループは、DUT のソースを一つのディレクトリに、検証環境を別のディレクトリに置くことでこれを強制している。
Throughout this guide a not synthesisable badge marks any construct that belongs on the testbench side of that wall.本指南全篇會用一個 not synthesisable 標記,標出任何屬於這道牆的測試平台那一側的語法結構。本ガイドでは全体を通して、この壁のテストベンチ側に属する構文に not synthesisable のバッジを付けて示す。
The slides show the simplest possible testbench: one initial block that lists stimulus with # delays, one that calls $monitor, one that calls $finish. That is a directed testbench with visual checking — a human reads the printout or the waveform and decides whether it is right. It is the correct starting point and completely inadequate as an end point, for two reasons.投影片展示了最簡單的測試平台:一個 initial 區塊用 # 延遲列出激勵,一個呼叫 $monitor,一個呼叫 $finish。那是一個採視覺檢查的指向式(directed)測試平台 —— 由人去讀印出結果或波形,再判斷對不對。它是正確的起點,但作為終點則完全不夠,理由有兩個。スライドが示すのは考えうる最も単純なテストベンチである。# 遅延で刺激を並べた initial ブロックが一つ、$monitor を呼ぶものが一つ、$finish を呼ぶものが一つ。これは目視チェックによる指向型(directed)テストベンチであり、人間が出力や波形を読んで正しいかを判断する。出発点としては正しいが、終着点としてはまったく不十分である。理由は二つある。
First, visual checking does not scale. A four-bit adder has $2^9 = 512$ input combinations including carry-in; you can eyeball those. A sixteen-bit multiplier has $2^{32}$, about four billion. Nobody reads four billion lines of $monitor output. The moment the input space is larger than a screenful you need a self-checking testbench, one that computes or looks up the expected value and prints only on mismatch. The end of the run then prints a single line: 0 errors in 4294967296 vectors — or it prints the first failing vector and stops.第一,目視檢查無法擴展。四位元加法器含進位輸入共有 $2^9 = 512$ 種輸入組合,這你還看得完。十六位元乘法器有 $2^{32}$ 種,約四十億。沒有人會去讀四十億行 $monitor 輸出。只要輸入空間大過一個螢幕,你就需要自我檢查(self-checking)的測試平台:自己算出或查出期待值,只在不符時才印東西。這樣跑完時只印一行:0 errors in 4294967296 vectors —— 或者印出第一個失敗的向量然後停下來。第一に、目視チェックはスケールしない。4 ビット加算器はキャリー入力を含めて $2^9 = 512$ 通りの入力組合せしかなく、これなら目で追える。16 ビット乗算器は $2^{32}$ 通り、約 40 億である。40 億行の $monitor 出力を読む者はいない。入力空間が画面一枚を超えた瞬間、自己チェック型(self-checking)のテストベンチ、すなわち期待値を計算するか参照して、不一致のときだけ表示するものが必要になる。そうすれば実行の最後に 0 errors in 4294967296 vectors の一行だけが出るか、最初に失敗したベクタを表示して止まるかのどちらかになる。
Second, directed stimulus only finds bugs you already thought of. The vectors you write are the cases you had in mind when you wrote the design, which are exactly the cases the design handles. Bugs live in the cases you did not think of: the FIFO that is read and written in the same cycle while full, the state machine that receives an abort during the last byte of a packet. This is why industrial flows add constrained-random stimulus and functional coverage. Those are SystemVerilog topics and outside this course, but you should know the shape of the ladder you are standing on the first rung of.第二,指向式激勵只能找到你已經想到的臭蟲。你寫下的向量,正是你寫設計時心裡想著的狀況,而那也正是設計處理得了的狀況。臭蟲住在你沒想到的狀況裡:那個在滿的時候同一週期同時被讀與被寫的 FIFO、那個在封包最後一個位元組時收到 abort 的狀態機。這正是業界流程要加上受限隨機激勵與功能覆蓋率的原因。那些屬於 SystemVerilog 的題目,不在本課程範圍內,但你應該知道自己正站在哪一座梯子的第一階上。第二に、指向型の刺激は自分がすでに思いついたバグしか見つけられない。書いたベクタは設計時に念頭にあったケースであり、それはまさに設計が扱えるケースである。バグは思いつかなかったケースに潜む。満杯の状態で同一サイクルに読みと書きが来る FIFO、パケットの最終バイト中に abort を受け取るステートマシンなどだ。産業的なフローが制約付きランダム刺激と機能カバレッジを加えるのはこのためである。それらは SystemVerilog の話題で本講義の範囲外だが、自分がどんな梯子の一段目に立っているのかは知っておくべきである。
Write the directed testbench first and watch the waveform, because that is how you learn what your design does. Then, before you submit, convert it to self-checking: add a task check(input expected); that compares and prints ERROR at %0t on mismatch, and an error counter printed at $finish. The conversion usually takes fifteen minutes and it catches the bug you were about to miss.先寫指向式測試平台並看波形,因為那是你了解自己設計行為的方式。然後在交出去之前,把它改成自我檢查:加一個 task check(input expected); 負責比對並在不符時印出 ERROR at %0t,再加一個在 $finish 時印出的錯誤計數器。這個轉換通常只要十五分鐘,而它會抓到你原本差點漏掉的那個臭蟲。まず指向型のテストベンチを書いて波形を眺めること。自分の設計が何をするのかはそうやって学ぶからだ。そして提出前に自己チェック型へ変換する。比較して不一致なら ERROR at %0t を表示する task check(input expected); を加え、$finish 時に表示するエラーカウンタを加える。この変換はたいてい 15 分で済み、見逃しかけていたバグを捕まえてくれる。
Slide 12 of the deck draws a distinction that is easy to read past and important to internalise. The two halves of a simulation are written in the same language but describe fundamentally different kinds of object.投影片第 12 頁畫出一個很容易讀過去、卻很重要該內化的區別。模擬的兩半是用同一種語言寫的,描述的卻是根本不同種類的東西。スライド 12 枚目は、読み飛ばしやすいが身につけるべき重要な区別を示している。シミュレーションの二つの半分は同じ言語で書かれるが、記述している対象の種類は根本的に異なる。
always block above another says nothing about order.一切同時發生。把一個 always 區塊寫在另一個上面,完全不代表任何先後順序。すべては同時に起きる。ある always ブロックを別のブロックより上に書いても、順序については何も意味しない。A concrete symptom of mixing the two mindsets: students write a testbench that describes the stimulus as if it were hardware — a counter, a shift register, a state machine to sequence the inputs. It works, but it takes four times as long to write and is far harder to change. Conversely, students write a DUT as if it were a program — a long initial block, blocking assignments in sequence, delays to “wait for the result” — and are baffled when synthesis rejects it or, worse, accepts it and produces a circuit that bears no resemblance to the simulation.把兩種心態混在一起的具體症狀:學生寫出一個把激勵當成硬體來描述的測試平台 —— 用計數器、移位暫存器、狀態機去排出輸入順序。它會動,但寫起來要花四倍時間,而且改起來困難得多。反過來,學生把 DUT 當成程式來寫 —— 一個長長的 initial 區塊、一連串阻塞式指定、用延遲去「等結果出來」—— 然後在合成拒絕它時一頭霧水,或者更糟,合成接受了它,卻產生出一個跟模擬毫無相似之處的電路。二つの思考法を混ぜたときの具体的な症状はこうだ。学生は刺激をあたかもハードウェアのように記述したテストベンチを書く。入力を順序づけるためのカウンタ、シフトレジスタ、ステートマシン。動きはするが、書くのに 4 倍の時間がかかり、変更ははるかに難しい。逆に、学生は DUT をプログラムのように書く。長い initial ブロック、順に並んだブロッキング代入、「結果を待つ」ための遅延。そして合成がそれを拒否したとき、あるいはもっと悪いことに受理してシミュレーションとは似ても似つかない回路を出したとき、途方に暮れる。
Problem: For each fragment, say whether it belongs in the DUT, the testbench, or either, and why. (a) initial clk = 0; (b) always @(posedge clk) q <= d; (c) #5 reset = 1'b0; (d) assign y = sel ? a : b; (e) $readmemh("golden.hex", expected); (f) real snr; (g) always #5 clk = ~clk; (h) wire [7:0] sum = a + b;問題:對以下每個片段,說明它屬於 DUT、測試平台,還是兩邊皆可,並說明理由。(a) initial clk = 0; (b) always @(posedge clk) q <= d; (c) #5 reset = 1'b0; (d) assign y = sel ? a : b; (e) $readmemh("golden.hex", expected); (f) real snr; (g) always #5 clk = ~clk; (h) wire [7:0] sum = a + b;問題:次の各断片について、DUT・テストベンチ・どちらでも可、のいずれに属するかとその理由を述べよ。(a) initial clk = 0; (b) always @(posedge clk) q <= d; (c) #5 reset = 1'b0; (d) assign y = sel ? a : b; (e) $readmemh("golden.hex", expected); (f) real snr; (g) always #5 clk = ~clk; (h) wire [7:0] sum = a + b;
(a) Testbench — initial has no hardware meaning. (Some FPGA flows do honour initial as a bitstream-loaded power-up value; for cell-based ASIC, which is what this course targets, it is testbench-only.) (b) Either; this is the canonical synthesisable flip-flop. (c) Testbench — delay control. (d) Either; a synthesisable 2:1 multiplexer. (e) Testbench — file I/O system task. (f) Testbench — real has no net type in synthesis. (g) Testbench — both the delay and the free-running toggle are unsynthesisable; a real chip’s clock comes from a pad or a PLL. (h) Either; a net declaration with an implicit continuous assignment, which synthesises to an 8-bit adder (note the carry is discarded — see §8.11).(a) 測試平台 —— initial 沒有硬體意義。(有些 FPGA 流程確實會把 initial 當成由位元流載入的開機初值;但對本課程所針對的 cell-based ASIC 而言,它只屬於測試平台。)(b) 兩邊皆可;這是標準的可合成正反器。(c) 測試平台 —— 延遲控制。(d) 兩邊皆可;可合成的 2:1 多工器。(e) 測試平台 —— 檔案 I/O 系統任務。(f) 測試平台 —— real 在合成中沒有對應的連線型別。(g) 測試平台 —— 延遲與自由跑的翻轉兩者都不可合成;真實晶片的時脈來自接腳或 PLL。(h) 兩邊皆可;這是一個帶隱含連續指定的連線宣告,會合成出一個 8 位元加法器(注意進位被丟掉了 —— 見 §8.11)。(a) テストベンチ —— initial にハードウェア上の意味は無い。(FPGA のフローによっては initial をビットストリームでロードされる初期値として尊重するものもあるが、本講義が対象とするセルベース ASIC ではテストベンチ専用である。)(b) どちらでも可。これは標準的な合成可能フリップフロップである。(c) テストベンチ —— 遅延制御。(d) どちらでも可。合成可能な 2:1 マルチプレクサ。(e) テストベンチ —— ファイル I/O のシステムタスク。(f) テストベンチ —— real に対応するネット型は合成に存在しない。(g) テストベンチ —— 遅延もフリーランのトグルも合成できない。実チップのクロックはパッドか PLL から来る。(h) どちらでも可。暗黙の連続代入を伴うネット宣言であり、8 ビット加算器に合成される(キャリーが捨てられている点に注意 —— §8.11 を見よ)。
The slide definition is one line: a change in the value of a signal during simulation is referred to as an event. That is true but it hides a distinction the rest of this section depends on. Inside the simulator there are actually two kinds of thing on the queue, and conflating them is the single most common source of confusion about Verilog scheduling.投影片的定義只有一行:模擬過程中某個訊號值的改變,稱為一個事件。這沒錯,但它藏起了本節其餘部分所仰賴的一個區別。模擬器內部的佇列上其實有兩種東西,而把它們混為一談,正是 Verilog 排程相關混淆中最常見的單一來源。スライドの定義は一行である。シミュレーション中の信号値の変化をイベントと呼ぶ。それは正しいが、本節の残りが依存している区別を隠している。シミュレータ内部では、キューに載っているものは実は二種類あり、それらを混同することが Verilog のスケジューリングに関する混乱の最大の原因である。
C to 0 at time 13.” This is the event the slide is describing.更新事件(update event)改變儲存在連線或變數中的值。「在時刻 13 把 C 設為 0。」這就是投影片在描述的那種事件。更新イベント(update event)は、ネットまたは変数に格納された値を変える。「時刻 13 で C を 0 にする」。スライドが述べているのはこちらのイベントである。The two alternate. An update event changes a value; the simulator then looks up every process that is sensitive to that value and schedules an evaluation event for each; those evaluations compute new values and schedule further update events. Simulation is this loop, running until the queue runs dry. Once you see it as update → evaluate → update → evaluate, the rest of Verilog’s timing semantics — non-blocking assignments, delta cycles, races — stops being arbitrary and becomes mechanical.兩者交替出現。更新事件改變一個值;模擬器接著查出所有對該值敏感的程序,為每一個排一個求值事件;那些求值算出新值,又排出更多更新事件。模擬就是這個迴圈,一直跑到佇列乾掉為止。一旦你把它看成更新 → 求值 → 更新 → 求值,Verilog 其餘的時序語意 —— 非阻塞指定、delta 週期、競爭 —— 就不再任意武斷,而變成機械性的了。両者は交互に現れる。更新イベントが値を変え、シミュレータはその値に感応するすべてのプロセスを調べて各々に評価イベントを予約する。それらの評価が新しい値を計算し、さらに更新イベントを予約する。シミュレーションとはこのループであり、キューが空になるまで回り続ける。いったん更新 → 評価 → 更新 → 評価として見えるようになれば、Verilog の残りのタイミング意味論 —— ノンブロッキング代入、デルタサイクル、レース —— は恣意的なものではなく機械的なものになる。
If a gate re-evaluates and computes the value its output already has, no update event is scheduled. This is not an optimisation detail you can ignore: it is why the slide’s timing diagram shows nothing happening at $t_{sim} = 40$, and it is the mechanism that makes a combinational loop settle instead of spinning forever. “No change” is how the simulation terminates.如果一個閘重新求值,算出的結果就是它輸出目前已有的值,那麼不會排任何更新事件。這不是一個可以忽略的最佳化細節:它正是投影片的時序圖在 $t_{sim} = 40$ 處什麼也沒發生的原因,也是讓組合迴路得以安定下來、而不是永遠空轉的機制。「沒有變化」正是模擬得以終止的方式。ゲートが再評価され、その結果が出力の現在値と同じであれば、更新イベントは一切予約されない。これは無視してよい最適化の細部ではない。スライドのタイミング図で $t_{sim} = 40$ に何も起きていないのはこのためであり、組合せループが永久に回らず収束するのもこの仕組みによる。「変化なし」こそがシミュレーションの終わり方である。
The obvious way to simulate a circuit is to pick a time step, advance the clock by that step, and re-evaluate every gate. This is how SPICE works, and it is how a naive digital simulator would work. Verilog does not do this, and the reason is arithmetic.模擬電路最直覺的作法是:挑一個時間步長,讓時鐘前進一步,然後重新求值每一個閘。SPICE 就是這樣運作的,一個天真的數位模擬器也會這樣做。Verilog 不這麼做,理由是算術。回路をシミュレートする素朴な方法は、時間刻みを決め、その刻みだけ時刻を進め、すべてのゲートを再評価することである。SPICE はこの方式であり、素朴なディジタルシミュレータもそうするだろう。Verilog はそうしない。理由は算術にある。
Take a modest block: $N = 10^6$ gates, simulated for $T = 10^{-3}$ seconds of circuit time with a resolution of 1 ps, so $10^9$ time steps. A time-stepped simulator performs $N \times 10^9 = 10^{15}$ gate evaluations. At a hopeful $10^8$ evaluations per second that is $10^7$ seconds, or four months, for one millisecond of circuit activity.取一個規模不大的區塊:$N = 10^6$ 個閘,模擬 $T = 10^{-3}$ 秒的電路時間,解析度 1 ps,也就是 $10^9$ 個時間步。時間步進式模擬器要執行 $N \times 10^9 = 10^{15}$ 次閘求值。以樂觀的每秒 $10^8$ 次求值計算,那是 $10^7$ 秒,也就是四個月,只為了一毫秒的電路活動。控えめな規模のブロックを考える。$N = 10^6$ 個のゲートを、分解能 1 ps で回路時間 $T = 10^{-3}$ 秒ぶんシミュレートすると、時間刻みは $10^9$ 回になる。時間刻み型シミュレータは $N \times 10^9 = 10^{15}$ 回のゲート評価を行う。楽観的に毎秒 $10^8$ 回評価できるとしても $10^7$ 秒、すなわち 4 か月かかる。たった 1 ミリ秒の回路動作のためにである。
Now count the evaluations an event-driven simulator performs. In a synchronous design clocked at $f = 500$ MHz, one millisecond is $5\times10^5$ cycles. The activity factor $\alpha$ — the fraction of nodes that toggle in a given cycle — is typically between 0.05 and 0.2 for real logic. Taking $\alpha = 0.1$, the number of update events is現在來數事件驅動模擬器要做幾次求值。在一個 $f = 500$ MHz 的同步設計中,一毫秒是 $5\times10^5$ 個週期。活動率(activity factor)$\alpha$ —— 在某個週期中會翻轉的節點比例 —— 對真實邏輯而言通常介於 0.05 與 0.2 之間。取 $\alpha = 0.1$,更新事件的數目為次に、イベント駆動シミュレータが行う評価回数を数える。$f = 500$ MHz で動く同期設計では、1 ミリ秒は $5\times10^5$ サイクルである。活性化率(activity factor)$\alpha$ —— あるサイクルでトグルするノードの割合 —— は実際の論理では通常 0.05 から 0.2 の間にある。$\alpha = 0.1$ を取れば、更新イベント数は
Each update event triggers a handful of downstream evaluations, so call it $2\times10^{11}$ evaluations — still four orders of magnitude less work than the time-stepped approach, and the gap widens as the time resolution gets finer, because event-driven cost does not depend on resolution at all. That is the whole argument, and it is why every digital simulator since the 1960s has been event-driven. It also tells you something practical: simulation speed is proportional to activity. A testbench that toggles a 32-bit bus every picosecond for no reason will be slow; clock gating that is modelled faithfully will make your simulations faster as well as your chip cooler.每個更新事件會觸發少數幾個下游求值,所以就算它 $2\times10^{11}$ 次求值 —— 仍然比時間步進法少了四個數量級的工作量,而且時間解析度越細,差距越大,因為事件驅動的成本根本不取決於解析度。這就是整個論證,也是為什麼自 1960 年代以來每一個數位模擬器都是事件驅動的。它還告訴你一件很實用的事:模擬速度取決於活動量。一個毫無理由每皮秒就翻一次 32 位元匯流排的測試平台會很慢;而忠實建模的時脈閘控(clock gating)不只會讓你的晶片更涼,也會讓你的模擬更快。更新イベント一つがいくつかの下流評価を引き起こすので、$2\times10^{11}$ 回の評価としておこう。それでも時間刻み方式より 4 桁少ない仕事量であり、時間分解能を細かくするほど差は開く。イベント駆動のコストは分解能にまったく依存しないからだ。これが議論のすべてであり、1960 年代以降のあらゆるディジタルシミュレータがイベント駆動である理由でもある。さらに実用的なことも分かる。シミュレーション速度は活性度に左右される。理由もなく 32 ビットバスを 1 ピコ秒ごとにトグルさせるテストベンチは遅い。忠実にモデル化されたクロックゲーティングは、チップを涼しくするだけでなくシミュレーションも速くする。
The slide states the rule in its two halves: new values are computed only for signals affected by events that have already occurred (spatial locality — do not touch gates whose inputs did not move) and only at those times when changes can occur (temporal locality — do not visit times at which nothing is scheduled).投影片把這條規則分成兩半陳述:新值只針對受到已發生事件影響的訊號計算(空間局部性 —— 不要去碰輸入沒動的閘),而且只在可能發生變化的那些時刻計算(時間局部性 —— 不要走訪沒有排任何事件的時刻)。スライドはこの規則を二つの半分として述べている。新しい値はすでに起きたイベントの影響を受ける信号についてのみ計算され(空間的局所性 —— 入力が動かなかったゲートには触れない)、変化が起こりうる時刻においてのみ計算される(時間的局所性 —— 何も予約されていない時刻は訪れない)。
The simulator’s central data structure is a list of events ordered by the time at which they are to occur. Classically this is drawn as a wheel — the slide’s figure shows a rotating “current simulation time” pointer with a bucket of events hanging off each time stamp. Each bucket holds every event scheduled for that instant; an event executing at time $t$ may append new events to buckets at $t$, $t+1$, $t+2$ and so on, never to a bucket in the past.模擬器的核心資料結構,是一份依事件預定發生時刻排序的事件串列。傳統上畫成一個輪子 —— 投影片的圖顯示一個旋轉的「目前模擬時間」指標,每個時間戳上掛著一桶事件。每一桶裝著排定在那一瞬間的所有事件;在時刻 $t$ 執行的事件可以把新事件附加到 $t$、$t+1$、$t+2$ 等等的桶上,但永遠不能加到過去的桶裡。シミュレータの中心的なデータ構造は、発生予定時刻で順序づけられたイベントのリストである。古典的には車輪として描かれ、スライドの図では回転する「現在シミュレーション時刻」のポインタと、各タイムスタンプにぶら下がるイベントのバケツが示されている。各バケツにはその瞬間に予約されたすべてのイベントが入る。時刻 $t$ で実行されるイベントは $t$、$t+1$、$t+2$ などのバケツに新たなイベントを追加できるが、過去のバケツには決して追加できない。
Three consequences follow immediately, and all three are examinable:由此立刻得到三個推論,三個都會考:ここから直ちに三つの帰結が出る。いずれも試験に出る。
$time is monotonically non-decreasing.模擬時間永不倒退。你無法把事件排在過去,這就是為什麼負延遲是錯誤,也是為什麼 $time 是單調不遞減的。シミュレーション時刻は決して逆戻りしない。過去にイベントを予約することはできない。負の遅延がエラーである理由も、$time が単調非減少である理由もここにある。initial blocks in the testbench, which are all made runnable at $t_{sim} = 0$. If your testbench has no initial and no always, the list is empty at time zero and the simulation ends before it starts — a real and surprisingly common bug when a stimulus file is left out of the compile list.串列的初始內容來自激勵。投影片說「串列上的初始事件由輸入變化組成」。在 Verilog 中,它們來自測試平台裡的 initial 區塊,這些區塊全都在 $t_{sim} = 0$ 變成可執行。如果你的測試平台既沒有 initial 也沒有 always,那麼時刻零時串列就是空的,模擬還沒開始就結束了 —— 當某個激勵檔案被漏掉在編譯清單之外時,這是一個真實而且出奇常見的臭蟲。リストの初期内容は刺激から来る。スライドは「リスト上の初期イベントは入力変化からなる」と述べている。Verilog ではそれらはテストベンチの initial ブロックから来て、いずれも $t_{sim} = 0$ で実行可能になる。テストベンチに initial も always も無ければ、時刻ゼロでリストは空であり、シミュレーションは始まる前に終わる。刺激ファイルをコンパイルリストに入れ忘れたときに実際によく起きるバグである。always #5 clk = ~clk;) schedules a new event every time it fires, so the list never empties and the run would continue forever. This is precisely why the slide’s testbench contains a third initial block whose only job is #60 $finish;. Forgetting it is the classic “my simulation hangs” bug.當串列變空時,模擬停止。請注意它沒有說的部分:它並沒有說模擬會在某個固定的結束時刻停止。一個自由跑的時脈產生器(always #5 clk = ~clk;)每次觸發都會排一個新事件,所以串列永遠不會空,模擬會一直跑下去。這正是投影片的測試平台為何包含第三個 initial 區塊、而它唯一的工作就是 #60 $finish; 的原因。忘了它,就是經典的「我的模擬卡住了」臭蟲。リストが空になるとシミュレーションは止まる。これが言っていないことに注意せよ。固定の終了時刻で止まるとは言っていない。フリーランのクロック生成器(always #5 clk = ~clk;)は発火するたびに新しいイベントを予約するので、リストは決して空にならず、実行は永遠に続く。スライドのテストベンチに、#60 $finish; だけを仕事とする三つ目の initial ブロックがあるのはまさにこのためである。これを忘れるのが、古典的な「シミュレーションが終わらない」バグである。So far, so simple. The complication — and the part the slides do not cover, but which you cannot avoid once you write anything with non-blocking assignments — is that the events scheduled for a single time stamp are not one undifferentiated bag. IEEE 1364 divides each time slot into ordered regions. This is called the stratified event queue, and it is the formal reason Verilog is usable at all for synchronous design.到這裡為止都很單純。複雜之處 —— 也是投影片沒講、但你一旦寫下任何含非阻塞指定的程式碼就無法迴避的部分 —— 在於:排在同一個時間戳上的事件,並不是一袋不分彼此的東西。IEEE 1364 把每個時間槽切成有先後次序的區域(region)。這稱為分層事件佇列(stratified event queue),也是 Verilog 之所以能用於同步設計的形式上理由。ここまでは単純である。複雑になるのは —— スライドでは触れられていないが、ノンブロッキング代入を含むものを一度でも書けば避けられない部分だが —— 同一のタイムスタンプに予約されたイベントが、区別のない一つの袋ではないという点である。IEEE 1364 は各タイムスロットを順序づけられた領域(region)に分割する。これを層化イベントキュー(stratified event queue)と呼び、Verilog が同期設計に使える形式的な根拠となっている。
The regions, in the order the simulator drains them:各區域,依模擬器排乾它們的順序:領域を、シミュレータが空にしていく順に並べると次のようになる。
| Region區域領域 | What lands here什麼會落到這裡ここに入るもの | Why it matters為什麼重要なぜ重要か |
|---|---|---|
| ActiveActive(作用中)Active(アクティブ) | Blocking assignments (=); evaluation of gates, UDPs and continuous assignments; evaluation of the right-hand side of a non-blocking assignment; $display and $write; changing a primitive’s output.阻塞式指定(=);閘、UDP 與連續指定的求值;非阻塞指定右手邊的求值;$display 與 $write;改變基本元件的輸出。ブロッキング代入(=)、ゲート・UDP・連続代入の評価、ノンブロッキング代入の右辺の評価、$display と $write、プリミティブ出力の変更。 | The bulk of the work. The standard explicitly permits the simulator to execute active events in any order.絕大部分的工作都在這裡。標準明文允許模擬器以任意順序執行 active 事件。仕事の大半がここに入る。標準は、アクティブイベントを任意の順序で実行してよいと明示的に許している。 |
| InactiveInactive(非作用中)Inactive(インアクティブ) | Procedural assignments written with an explicit #0 delay.寫著明確 #0 延遲的程序性指定。明示的な #0 遅延を伴う手続き代入。 | A deliberate “go last within this instant” escape hatch. Using it to fix a race is a code smell; it moves the race rather than removing it.一個刻意設計的「在這一瞬間裡最後再做」的逃生門。拿它來修競爭是一種壞味道;它只是把競爭挪個位置,而不是消除它。「この瞬間の中で最後に実行する」という意図的な抜け道である。これでレースを直すのはコードの臭いであり、レースを取り除くのではなく移動させているだけだ。 |
| Non-blocking assign updateNon-blocking assign update(非阻塞指定更新)Non-blocking assign update(ノンブロッキング代入の更新) | The write-back half of every <= whose right-hand side was evaluated in the active region of this time slot.每一個在本時間槽的 active 區域中求出右手邊的 <=,其寫回的那一半。本タイムスロットのアクティブ領域で右辺が評価された、すべての <= の書き戻し側の半分。 | This split — evaluate now, write later — is the whole reason non-blocking assignment makes flip-flop models race-free.這個「現在求值、稍後寫入」的切割,正是非阻塞指定能讓正反器模型免於競爭的全部原因。この「今評価し、後で書く」という分割こそが、ノンブロッキング代入がフリップフロップのモデルをレースフリーにする理由のすべてである。 |
| MonitorMonitor(監看)Monitor(モニタ) | $monitor and $strobe.$monitor 與 $strobe。$monitor と $strobe。 | They run after everything else has settled, so they always print the final value of the time step rather than an intermediate one.它們在其他一切都安定之後才執行,所以印出來的永遠是該時間步的最終值,而不是中間值。他のすべてが落ち着いた後に実行されるので、常にそのタイムステップの最終値を表示し、途中の値を表示することはない。 |
There is also a conceptual future region holding everything scheduled for later times; it is not part of the current slot.另外還有一個概念上的未來區域,裝著所有排在較晚時刻的東西;它不屬於目前這個時間槽。さらに、より後の時刻に予約されたすべてを保持する概念上の未来領域もあるが、これは現在のスロットの一部ではない。
A non-blocking assignment is torn in half across two regions. When q <= d; executes in the active region, the simulator reads d immediately and remembers the value, then places a write into the NBA region. Every other active event at this time stamp — including every other flip-flop in the design reading q — therefore still sees the old q.非阻塞指定被撕成兩半,分跨兩個區域。當 q <= d; 在 active 區域執行時,模擬器立刻讀取 d 並把那個值記下來,然後把一次寫入放進 NBA 區域。因此,這個時間戳上的其他每一個 active 事件 —— 包括設計中其他每一個正在讀 q 的正反器 —— 看到的仍然是舊的 q。ノンブロッキング代入は二つの領域にまたがって半分に裂かれている。q <= d; がアクティブ領域で実行されると、シミュレータは直ちに d を読んでその値を覚え、書き込みを NBA 領域に置く。したがって、このタイムスタンプの他のすべてのアクティブイベント —— 設計中で q を読む他のすべてのフリップフロップを含む —— が見るのは依然として古い q である。
That is exactly the behaviour of a real edge-triggered flip-flop, which samples its D input on the edge and does not present the new Q until after its clock-to-Q delay. Verilog’s non-blocking assignment is a zero-delay model of that same read-then-write discipline. This is developed properly in Lecture 6; here you just need to know where in the queue the two halves live.這正是真實邊緣觸發正反器的行為:它在邊緣上取樣 D 輸入,並且要等 clock-to-Q 延遲之後才把新的 Q 呈現出來。Verilog 的非阻塞指定就是這套「先讀後寫」紀律的零延遲模型。這在 Lecture 6 會被完整展開;在這裡你只需要知道這兩半位於佇列的哪個位置。これはまさに実際のエッジトリガ・フリップフロップの振る舞いである。エッジで D 入力をサンプルし、クロック to Q 遅延の後までは新しい Q を出さない。Verilog のノンブロッキング代入は、この「読んでから書く」規律のゼロ遅延モデルである。これは Lecture 6 できちんと展開される。ここでは、この二つの半分がキューのどこにあるのかだけ分かっていればよい。
IEEE 1364 gives the scheduler as pseudo-code. Stripped to its essentials it reads like this:IEEE 1364 以虛擬碼的形式給出排程器。剝到只剩骨幹的話,它讀起來像這樣:IEEE 1364 はスケジューラを擬似コードとして与えている。本質だけを残すと次のように読める。
T = 0; // make every initial / always block runnable while (event queue is not empty) begin if (ACTIVE is empty) begin if (INACTIVE non-empty) move all INACTIVE events into ACTIVE; else if (NBA non-empty) move all NBA events into ACTIVE; else if (MONITOR non-empty) move all MONITOR events into ACTIVE; else begin T = time of the earliest event still scheduled; // time finally advances move every event scheduled at T into ACTIVE; end end E = any event from ACTIVE; // <-- the standard says "any" remove E from ACTIVE; if (E is an update event) begin write the new value into the net or variable; for (every process sensitive to it) schedule an evaluation event in ACTIVE; end else begin // E is an evaluation event execute the process until it blocks; it may schedule update events now or in the future; end end
Read the highlighted line again. “Any” is normative. The standard deliberately refuses to specify the order in which simultaneous active events are processed, because pinning it down would forbid perfectly reasonable implementations — and because a design whose behaviour depends on that order is broken anyway. Everything in §2.7 follows from this single word.再讀一次標示出來的那一行。「任意」是規範性的用語。標準刻意拒絕規定同時發生的 active 事件的處理順序,因為把它釘死會禁掉完全合理的實作方式 —— 也因為行為取決於那個順序的設計,本來就是壞的。§2.7 的所有內容都源自這一個詞。強調された行をもう一度読んでほしい。「任意(any)」は規範的な語である。標準は、同時に存在するアクティブイベントの処理順序を定めることを意図的に拒んでいる。それを固定すればまったく妥当な実装を禁じることになるからであり、またその順序に振る舞いが依存する設計はそもそも壊れているからである。§2.7 の内容はすべてこの一語から導かれる。
Notice also where time advances: in exactly one place, at the bottom of the region cascade, and only when all four regions of the current slot are empty. Within one value of $time the simulator may loop around the region cascade an unbounded number of times.也請注意時間在哪裡前進:只在一個地方,在區域串級的最底部,而且只有在目前時間槽的四個區域全都空了的時候。在同一個 $time 值之內,模擬器可以在區域串級上繞無上限多圈。時刻がどこで進むかにも注意せよ。進むのはただ一か所、領域カスケードの最下部であり、しかも現在のスロットの四つの領域がすべて空になったときだけである。同じ $time の値の中で、シミュレータは領域カスケードを何周でも回りうる。
Each pass around that cascade at a fixed simulation time is a delta cycle. Delta cycles are how a zero-delay model propagates a value through a chain of logic without time advancing.在固定模擬時刻上繞那個串級一圈,就是一個 delta 週期。delta 週期正是零延遲模型在時間不前進的情況下,把一個值傳遞過一串邏輯的方式。固定されたシミュレーション時刻でそのカスケードを一周することが、一つのデルタサイクルである。デルタサイクルは、時刻を進めずに値を論理の連鎖に伝播させる、ゼロ遅延モデルのやり方である。
Take the slide’s own circuit with no delays: C = A & B feeding D = ~C. At $t_{sim} = 10$ the testbench writes A = 1. That is one update event. Draining the queue:取投影片自己的電路,不含延遲:C = A & B 餵給 D = ~C。在 $t_{sim} = 10$ 時測試平台寫入 A = 1。那是一個更新事件。把佇列排乾:スライド自身の回路を遅延なしで取る。C = A & B が D = ~C に入る。$t_{sim} = 10$ でテストベンチが A = 1 を書く。これが一つの更新イベントである。キューを空にしていくと:
| DeltaDeltaデルタ | Active region containsActive 區域內含アクティブ領域の中身 | Result結果結果 |
|---|---|---|
| δ0 | update A→1, update B→0更新 A→1、更新 B→0A→1 を更新、B→0 を更新 | Both written. The AND gate is sensitive to both, so an evaluation event is queued.兩者皆已寫入。AND 閘對兩者都敏感,所以排入一個求值事件。どちらも書き込まれた。AND ゲートは両方に感応するので、評価イベントが予約される。 |
| δ1 | evaluate AND求值 ANDAND を評価 | 1 & 0 = 0, and C is currently x, so queue update C→0.1 & 0 = 0,而 C 目前是 x,所以排入更新 C→0。1 & 0 = 0 であり、C は現在 x なので、C→0 の更新を予約する。 |
| δ2 | update C→0更新 C→0C→0 を更新 | Written. The inverter is sensitive to C; queue its evaluation.已寫入。反相器對 C 敏感;排入它的求值。書き込まれた。インバータは C に感応するので、その評価を予約する。 |
| δ3 | evaluate NOT求值 NOTNOT を評価 | ~0 = 1, D is x, so queue update D→1.~0 = 1,D 是 x,所以排入更新 D→1。~0 = 1 であり、D は x なので、D→1 の更新を予約する。 |
| δ4 | update D→1更新 D→1D→1 を更新 | Written. Nothing is sensitive to D. Queue empty → advance time.已寫入。沒有東西對 D 敏感。佇列已空 → 時間前進。書き込まれた。D に感応するものは無い。キューが空 → 時刻を進める。 |
Five delta cycles, all at $t_{sim} = 10$. $time returns 10 throughout; a waveform viewer shows A, C and D all changing at exactly 10. The deltas are invisible in the waveform but they are the reason the order of changes is still well defined: C genuinely does change before D, in delta order if not in time order.五個 delta 週期,全都在 $t_{sim} = 10$。$time 從頭到尾都回傳 10;波形檢視器會顯示 A、C、D 全都剛好在 10 變化。這些 delta 在波形中看不見,但它們正是變化順序依然有明確定義的原因:C 確實在 D 之前改變 —— 就算不是在時間順序上,也是在 delta 順序上。デルタサイクルは五つ、いずれも $t_{sim} = 10$ である。$time は終始 10 を返し、波形ビューアでは A、C、D がすべてちょうど 10 で変化しているように見える。デルタは波形には現れないが、変化の順序が依然として明確に定まっている理由がそこにある。C は確かに D より先に変わる。時刻の順序ではないにせよ、デルタの順序ではそうなのだ。
Because time does not advance during delta cycles, a zero-delay feedback loop can spin forever. The classic one-liner is由於在 delta 週期中時間不前進,一個零延遲的回授迴路可以永遠空轉下去。經典的一行式是デルタサイクルの間は時刻が進まないため、ゼロ遅延の帰還ループは永遠に回り続けうる。古典的な一行の例が次である。
assign a = ~a; // zero-delay oscillator: the simulator hangs at t = 0
Each evaluation schedules an update that triggers another evaluation, at the same time stamp, for ever. The simulation appears to freeze with $time stuck; most simulators eventually print something like “iteration limit reached”. The same thing happens, far less obviously, in a cross-coupled NAND latch with zero gate delay when both inputs are released from the forbidden state simultaneously. The slides’ latch uses nand #1 precisely so that each loop iteration costs one time unit, which makes the oscillation visible in the waveform instead of hanging the tool.每次求值都排出一次更新,那次更新又在同一個時間戳上觸發另一次求值,如此永無止境。模擬看起來像凍住了,$time 卡著不動;大多數模擬器最後會印出類似「iteration limit reached」的訊息。同樣的事情也會發生在零閘延遲的交叉耦合 NAND 閂鎖上 —— 當兩個輸入同時離開禁止狀態時 —— 只是不那麼明顯。投影片的閂鎖之所以用 nand #1,正是為了讓迴圈的每一輪都要花掉一個時間單位,使振盪在波形上看得見,而不是把工具掛住。各評価が更新を予約し、その更新が同じタイムスタンプでさらに別の評価を引き起こす。これが永遠に続く。シミュレーションは $time が止まったまま凍りついたように見え、多くのシミュレータは最終的に「iteration limit reached」のようなメッセージを出す。ゲート遅延がゼロの交差結合 NAND ラッチで、両入力が禁止状態から同時に解放されたときにも、はるかに分かりにくい形で同じことが起きる。スライドのラッチが nand #1 を使っているのは、まさにループ一周ごとに 1 時間単位を消費させ、発振をツールのハングではなく波形上で見える形にするためである。
Because the standard says “any event from ACTIVE”, two conforming simulators can produce different — and both correct — answers for the same source. A design with that property contains a race. The exam-relevant canonical case:因為標準說的是「ACTIVE 中的任意事件」,兩個符合標準的模擬器可以對同一份原始碼產生不同的 —— 而且兩者都正確的 —— 答案。具備這種性質的設計含有一個競爭(race)。與考試相關的經典案例:標準が「ACTIVE の中の任意のイベント」と述べている以上、標準に準拠した二つのシミュレータが、同じソースに対して異なる、しかもどちらも正しい答えを出しうる。その性質を持つ設計にはレースが含まれている。試験に出る典型例を示す。
always @(posedge clk) b = a; always @(posedge clk) c = b;
always @(posedge clk) b <= a; always @(posedge clk) c <= b;
Suppose before the edge a = 1, b = 0, c = 0. The clock rises. Both always blocks become active evaluation events at the same time stamp, and the simulator may take them in either order.假設邊緣之前 a = 1、b = 0、c = 0。時脈上升。兩個 always 區塊在同一個時間戳上都成為 active 的求值事件,而模擬器可以用任一順序處理它們。エッジの前に a = 1、b = 0、c = 0 だったとする。クロックが立ち上がる。二つの always ブロックは同じタイムスタンプでアクティブな評価イベントになり、シミュレータはどちらの順でも処理してよい。
| Order chosen所選順序選ばれた順序 | What happens發生什麼事何が起きるか | Final b, c最終的 b、c最終的な b, c | Hardware this matches對應的硬體対応するハードウェア |
|---|---|---|---|
| Block 1, then block 2先區塊 1,再區塊 2ブロック 1、次にブロック 2 | b becomes 1; block 2 then reads the new b and sets c = 1.b 變成 1;區塊 2 接著讀到新的 b,把 c 設為 1。b が 1 になる。続いてブロック 2 が新しい b を読み、c を 1 にする。 | b = 1, c = 1 | A single flip-flop with its output wired to c — the second stage has vanished.一個輸出直接接到 c 的單級正反器 —— 第二級消失了。出力がそのまま c に配線された単段フリップフロップ —— 二段目が消えている。 |
| Block 2, then block 1先區塊 2,再區塊 1ブロック 2、次にブロック 1 | c takes the old b = 0; then b becomes 1.c 取到舊的 b = 0;之後 b 才變成 1。c は古い b = 0 を取る。その後で b が 1 になる。 | b = 1, c = 0 | A proper two-stage shift register.一個正確的兩級移位暫存器。正しい二段シフトレジスタ。 |
Both are legal executions of that source. The synthesised netlist, meanwhile, is unambiguously a two-stage shift register — real flip-flops all sample simultaneously. So the blocking version can produce a simulation that disagrees with the silicon, which is the worst failure mode in the business: your testbench passes and your chip does not work.兩者都是那份原始碼的合法執行結果。然而合成出來的網表毫無疑義就是一個兩級移位暫存器 —— 真實的正反器全都同時取樣。所以阻塞式的版本可能產生出與矽不一致的模擬結果,而這是業界最糟的失效模式:你的測試平台通過了,你的晶片卻不會動。どちらもそのソースの合法な実行である。一方、合成されたネットリストは疑いなく二段シフトレジスタである。実際のフリップフロップはすべて同時にサンプルするからだ。つまりブロッキング版は、シリコンと食い違うシミュレーション結果を生みうる。これは業界で最悪の故障モードである。テストベンチは通るのにチップは動かない。
The non-blocking version has no such freedom. Both right-hand sides are evaluated in the active region, reading the old b and the old a; both writes happen later in the NBA region. Whichever order the simulator picks, the answer is b = 1, c = 0. The rule that follows — non-blocking assignments for sequential logic, blocking for combinational — is the most quoted rule in Verilog, and this is where it comes from.非阻塞的版本沒有這種自由度。兩條右手邊都在 active 區域求值,讀到的是舊的 b 與舊的 a;兩次寫入都稍後才在 NBA 區域發生。無論模擬器挑哪個順序,答案都是 b = 1、c = 0。由此得出的規則 —— 循序邏輯用非阻塞指定,組合邏輯用阻塞指定 —— 是 Verilog 中被引用最多的規則,而它的出處就在這裡。ノンブロッキング版にはその自由度が無い。両方の右辺はアクティブ領域で評価され、古い b と古い a を読む。書き込みはいずれも後の NBA 領域で起きる。シミュレータがどちらの順序を選んでも、答えは b = 1、c = 0 である。ここから出てくる規則 —— 順序回路にはノンブロッキング代入、組合せ回路にはブロッキング代入 —— は Verilog で最も多く引用される規則であり、その出所がこれである。
A race almost never looks like a race. It looks like a signal that is one cycle early (data skipped a pipeline stage) or one cycle late. The giveaway is that the symptom moves when you change something irrelevant — reorder two always blocks, split a file in two, switch from VCS to ModelSim, or turn optimisation on. If a bug moves when you recompile with different switches, stop debugging the symptom and go looking for a blocking assignment in sequential logic, or for a testbench that drives DUT inputs with = on the same edge the DUT samples them.競爭幾乎從來不像競爭。它看起來像某個訊號早了一個週期(資料跳過了一級管線)或晚了一個週期。破綻在於:當你改了某個不相干的東西時,症狀就會跑掉 —— 把兩個 always 區塊對調、把一個檔案拆成兩個、從 VCS 換成 ModelSim,或是打開最佳化。如果一個臭蟲在你用不同編譯選項重編時會移動,那就別再追症狀了,去找循序邏輯裡的阻塞式指定,或是找一個用 = 在 DUT 取樣的同一個邊緣上去驅動 DUT 輸入的測試平台。レースがレースらしく見えることはまずない。見えるのは、ある信号が1 サイクル早い(データがパイプライン段を一つ飛ばした)か、1 サイクル遅いという現象である。手がかりは、無関係なものを変えると症状が動くことだ。always ブロックを二つ入れ替える、ファイルを二つに分ける、VCS から ModelSim に替える、最適化をオンにする。コンパイルオプションを変えると動くバグなら、症状を追うのをやめて、順序回路の中のブロッキング代入を探すか、DUT がサンプルするのと同じエッジで = を使って DUT の入力を駆動しているテストベンチを探すべきである。
The testbench side of that last sentence deserves its own warning, because it bites every year. If your stimulus block is always @(posedge clk) data = data + 1; and the DUT samples data on the same posedge clk, you have built exactly the race above with the stimulus playing the part of block 1. Three standard cures, in order of preference: drive stimulus with non-blocking assignments; drive it off the opposite clock edge; or offset it by an explicit small delay (@(posedge clk) #1 data = ...;) so the DUT’s sample point and the stimulus change are in different time slots.最後那句話的測試平台面向值得單獨警告,因為它每年都咬人。如果你的激勵區塊是 always @(posedge clk) data = data + 1;,而 DUT 又在同一個 posedge clk 取樣 data,那你就造出了上面那個競爭,只是由激勵扮演區塊 1 的角色。三個標準解法,依偏好排序:用非阻塞指定驅動激勵;用相反的時脈邊緣驅動它;或是用一個明確的小延遲把它錯開(@(posedge clk) #1 data = ...;),讓 DUT 的取樣點與激勵的變化落在不同的時間槽。最後の一文のテストベンチ側は、毎年必ず誰かを噛むので単独で警告しておく価値がある。刺激ブロックが always @(posedge clk) data = data + 1; で、DUT が同じ posedge clk で data をサンプルしているなら、刺激がブロック 1 の役を演じる形で、まさに上のレースを作ったことになる。標準的な対処は三つ、好ましい順に挙げる。刺激をノンブロッキング代入で駆動する。反対のクロックエッジで駆動する。あるいは明示的な小さい遅延でずらし(@(posedge clk) #1 data = ...;)、DUT のサンプル点と刺激の変化を別のタイムスロットに置く。
Being able to produce a timing diagram with a pencil, from the event list, is an exam skill and a debugging skill. Here is the full procedure on the slide’s own example so that §3 can reuse it.能夠拿著鉛筆、從事件串列畫出時序圖,既是一項考試技能,也是一項除錯技能。以下用投影片自己的例子走完整個程序,好讓 §3 可以沿用。鉛筆を手に、イベントリストからタイミング図を描けることは、試験の技能であると同時にデバッグの技能でもある。§3 で再利用できるよう、スライド自身の例で手順を最後まで示す。
Circuit: C = A & B; D = ~C. Stimulus: at $t = 10$, A = 1 and B = 0; at 20, B = 1; at 30, A = 0; at 40, B = 0.電路:C = A & B;D = ~C。激勵:在 $t = 10$ 時 A = 1 且 B = 0;在 20 時 B = 1;在 30 時 A = 0;在 40 時 B = 0。回路:C = A & B、D = ~C。刺激:$t = 10$ で A = 1 かつ B = 0、20 で B = 1、30 で A = 0、40 で B = 0。
x, because its driver has not yet computed anything. So A = B = C = D = x. Check for consistency: x & x is x and ~x is x, so the circuit is already settled and nothing is scheduled.步驟 0 —— 初始狀態。每條由基本元件驅動的連線都從 x 開始,因為它的驅動源還沒算出任何東西。所以 A = B = C = D = x。檢查一致性:x & x 是 x、~x 是 x,所以電路已經安定,沒有排任何事件。ステップ 0 —— 初期状態。プリミティブに駆動されるネットはすべて x から始まる。駆動側がまだ何も計算していないからだ。したがって A = B = C = D = x。整合性を確認すると、x & x は x、~x は x なので、回路はすでに落ち着いており、何も予約されていない。C’s current x, so C → 0. The inverter sees C change, computes ~0 = 1, so D → 1. All at $t = 10$, in successive delta cycles.步驟 1 —— t = 10。兩個更新事件抵達。重新求值每一個輸入有動過的閘:AND 看到 1 & 0 = 0。這與 C 目前的 x 不同,所以 C → 0。反相器看到 C 變化,算出 ~0 = 1,所以 D → 1。全都在 $t = 10$,發生在連續的 delta 週期中。ステップ 1 —— t = 10。更新イベントが二つ届く。入力が動いたゲートをすべて再評価する。AND は 1 & 0 = 0 を見る。これは C の現在値 x と異なるので C → 0。インバータは C の変化を見て ~0 = 1 を計算し、D → 1 となる。すべて $t = 10$ で、連続するデルタサイクルの中で起きる。B → 1. AND: 1 & 1 = 1, differs from 0, so C → 1. NOT: ~1 = 0, so D → 0.步驟 2 —— t = 20。B → 1。AND:1 & 1 = 1,與 0 不同,所以 C → 1。NOT:~1 = 0,所以 D → 0。ステップ 2 —— t = 20。B → 1。AND:1 & 1 = 1 で 0 と異なるので C → 1。NOT:~1 = 0 なので D → 0。A → 0. AND: 0 & 1 = 0, so C → 0; D → 1.步驟 3 —— t = 30。A → 0。AND:0 & 1 = 0,所以 C → 0;D → 1。ステップ 3 —— t = 30。A → 0。AND:0 & 1 = 0 なので C → 0、D → 1。B → 0. AND: 0 & 0 = 0. C is already 0 → no update event is scheduled. The inverter is never re-evaluated. D stays 1. This is the step students get wrong; the slide’s figure shows the box for B = 0 with nothing below it for exactly this reason.步驟 4 —— t = 40。B → 0。AND:0 & 0 = 0。C 已經是 0 → 不會排任何更新事件。反相器根本不會被重新求值。D 維持 1。這是學生會做錯的一步;投影片的圖在 B = 0 的方塊下方什麼都沒有,正是這個原因。ステップ 4 —— t = 40。B → 0。AND:0 & 0 = 0。C はすでに 0 なので → 更新イベントは一切予約されない。インバータは再評価すらされない。D は 1 のままである。学生が間違えるのはこのステップで、スライドの図で B = 0 の箱の下に何も無いのはまさにこの理由による。The discipline to copy is: (i) write down the current value of every net; (ii) apply the input change; (iii) re-evaluate only the gates fed by what changed; (iv) schedule an event only where the computed value differs from the present value; (v) repeat for the newly changed nets. Four lines of bookkeeping, and it generalises directly to the delayed case in §3.值得照抄的紀律是:(i) 寫下每條連線目前的值;(ii) 套用輸入變化;(iii) 只重新求值被變化餵到的閘;(iv) 只在算出的值與現值不同時才排事件;(v) 對新變化的連線重複以上步驟。四行帳就記完了,而且可以直接推廣到 §3 的有延遲情形。まねるべき規律はこうだ。(i) すべてのネットの現在値を書き出す。(ii) 入力変化を適用する。(iii) 変化したものに駆動されるゲートだけを再評価する。(iv) 計算値が現在値と異なるときだけイベントを予約する。(v) 新たに変化したネットについて繰り返す。帳簿は四行で済み、そのまま §3 の遅延ありの場合へ一般化できる。
Problem: nand G1 (p, a, b); not G2 (q, p); or G3 (r, q, c); all with zero delay. Initially a = 1, b = 1, c = 0 and the circuit is settled. At $t = 5$, b → 0. At $t = 8$, c → 1. At $t = 12$, b → 1. List, in order, every update event with its time and say how many delta cycles each input change costs.問題:nand G1 (p, a, b); not G2 (q, p); or G3 (r, q, c);,全部零延遲。初始 a = 1、b = 1、c = 0,電路已安定。在 $t = 5$ 時 b → 0。在 $t = 8$ 時 c → 1。在 $t = 12$ 時 b → 1。依序列出每一個更新事件與其時刻,並說明每次輸入變化各花掉幾個 delta 週期。問題:nand G1 (p, a, b); not G2 (q, p); or G3 (r, q, c); がすべて遅延ゼロであるとする。初期状態は a = 1、b = 1、c = 0 で回路は落ち着いている。$t = 5$ で b → 0、$t = 8$ で c → 1、$t = 12$ で b → 1。すべての更新イベントを時刻とともに順に挙げ、各入力変化がデルタサイクルをいくつ消費するかを述べよ。
Settled state: p = ~(1&1) = 0, q = ~0 = 1, r = 1 | 0 = 1.安定狀態:p = ~(1&1) = 0、q = ~0 = 1、r = 1 | 0 = 1。落ち着いた状態:p = ~(1&1) = 0、q = ~0 = 1、r = 1 | 0 = 1。
t = 5: b→0. G1: ~(1&0) = 1 ≠ 0, so p→1. G2: ~1 = 0 ≠ 1, so q→0. G3: 0 | 0 = 0 ≠ 1, so r→0. Three update events, all at t = 5; the change rippled through three deltas (plus the delta for b itself).t = 5:b→0。G1:~(1&0) = 1 ≠ 0,所以 p→1。G2:~1 = 0 ≠ 1,所以 q→0。G3:0 | 0 = 0 ≠ 1,所以 r→0。三個更新事件,全在 t = 5;變化漣漪穿過三個 delta(再加上 b 自己的那個 delta)。t = 5:b→0。G1:~(1&0) = 1 ≠ 0 なので p→1。G2:~1 = 0 ≠ 1 なので q→0。G3:0 | 0 = 0 ≠ 1 なので r→0。更新イベントは三つ、いずれも t = 5。変化は三つのデルタ(加えて b 自身のデルタ)を通って波及した。
t = 8: c→1. Only G3 is fed by c: 0 | 1 = 1 ≠ 0, so r→1. One update event; G1 and G2 are never visited because nothing they depend on moved.t = 8:c→1。只有 G3 被 c 餵入:0 | 1 = 1 ≠ 0,所以 r→1。一個更新事件;G1 與 G2 完全不會被走訪,因為它們依賴的東西都沒動。t = 8:c→1。c に駆動されるのは G3 だけで、0 | 1 = 1 ≠ 0 なので r→1。更新イベントは一つ。G1 と G2 は依存しているものが動いていないので、そもそも訪れられない。
t = 12: b→1. G1: ~(1&1) = 0, so p→0. G2: ~0 = 1, so q→1. G3: 1 | 1 = 1 — equal to the current r, so no event. The ripple stops at q. Two update events, not three.t = 12:b→1。G1:~(1&1) = 0,所以 p→0。G2:~0 = 1,所以 q→1。G3:1 | 1 = 1 —— 與 r 的現值相等,所以沒有事件。漣漪停在 q。兩個更新事件,不是三個。t = 12:b→1。G1:~(1&1) = 0 なので p→0。G2:~0 = 1 なので q→1。G3:1 | 1 = 1 —— r の現在値と等しいのでイベントは無い。波及は q で止まる。更新イベントは三つではなく二つ。
Total: six update events for three input changes. A time-stepped simulator would have evaluated all three gates at every one of the ~12 time points.合計:三次輸入變化共產生六個更新事件。時間步進式模擬器則會在大約 12 個時間點的每一點上,把三個閘全部求值一次。合計:三回の入力変化に対して更新イベントは六つ。時間刻み型シミュレータなら、およそ 12 個の時刻すべてで三つのゲートを毎回評価していたはずである。
Problem: You want to swap two registers on every clock edge. Analyse both versions and say what each does, and what the synthesised hardware does.問題:你想在每個時脈邊緣交換兩個暫存器的內容。分析兩個版本,說明各自做了什麼,以及合成出來的硬體做了什麼。問題:クロックエッジごとに二つのレジスタの内容を入れ替えたい。両方の版を解析し、それぞれが何をするか、そして合成されたハードウェアが何をするかを述べよ。
always @(posedge clk) begin a = b; end // version A, two separate blocks always @(posedge clk) begin b = a; end always @(posedge clk) begin a <= b; end // version B always @(posedge clk) begin b <= a; end
Also: what happens with always @(posedge clk) begin a = b; b = a; end — one block, blocking?另外:always @(posedge clk) begin a = b; b = a; end —— 單一區塊、阻塞式 —— 又會怎樣?さらに:always @(posedge clk) begin a = b; b = a; end —— ブロック一つ、ブロッキング代入 —— ではどうなるか。
Version A is a race. If the first block runs first, a takes the old b, then b takes the new a, which is the old b — so both end up holding the old b and the old a is destroyed. If the second runs first, both end up holding the old a. Either way the swap fails, and which way it fails depends on the simulator. Worse, the synthesised netlist is two flip-flops cross-connected, which does swap correctly — so the gate-level simulation disagrees with the RTL simulation.版本 A 有競爭。若第一個區塊先跑,a 取到舊的 b,接著 b 取到新的 a,也就是舊的 b —— 於是兩者最後都存著舊的 b,而舊的 a 被摧毀了。若第二個先跑,兩者最後都存著舊的 a。無論哪一種,交換都失敗,而失敗的方式取決於模擬器。更糟的是,合成出來的網表是兩個交叉相接的正反器,它確實能正確交換 —— 於是閘級模擬與 RTL 模擬結果不一致。版 A はレースである。最初のブロックが先に動けば a は古い b を取り、続いて b が新しい a、すなわち古い b を取る。結果として両方が古い b を保持し、古い a は失われる。二番目が先に動けば、両方が古い a を保持する。いずれにせよスワップは失敗し、どう失敗するかはシミュレータ次第である。さらに悪いことに、合成されたネットリストは交差接続された二つのフリップフロップであり、こちらは正しく入れ替える。つまりゲートレベルシミュレーションが RTL シミュレーションと食い違う。
Version B works and matches the hardware. Both right-hand sides are read in the active region (old a, old b); both writes land in the NBA region; the values exchange. This is the standard idiom.版本 B 可以運作,而且與硬體一致。兩條右手邊都在 active 區域讀取(舊的 a、舊的 b);兩次寫入都落在 NBA 區域;於是值被交換。這就是標準寫法。版 B は正しく動き、ハードウェアとも一致する。両方の右辺はアクティブ領域で読まれ(古い a、古い b)、書き込みはいずれも NBA 領域に入るので、値が入れ替わる。これが標準的なイディオムである。
One block with blocking assignments is deterministic but wrong: statements inside a begin...end execute in order, so a = b runs first and b = a then copies the value a just received. Both registers end up holding the old b. No race, no ambiguity — just a copy instead of a swap. Synthesis agrees with simulation here and produces one flip-flop fanning out to two, which is at least honest.單一區塊配阻塞式指定是決定性的,但是錯的:begin...end 內的敘述依序執行,所以 a = b 先跑,接著 b = a 把 a 剛收到的值抄回去。兩個暫存器最後都存著舊的 b。沒有競爭、沒有歧義 —— 只是做了複製而不是交換。這裡合成與模擬一致,產生一個正反器扇出到兩處,至少是誠實的。ブロック一つにブロッキング代入は決定的だが誤りである。begin...end の中の文は順に実行されるので、まず a = b が動き、次の b = a は a が今受け取った値をコピーし返す。両レジスタとも古い b を保持して終わる。レースも曖昧さも無い —— ただスワップではなくコピーになっているだけだ。ここでは合成とシミュレーションが一致し、一つのフリップフロップが二か所へファンアウトする回路ができる。少なくとも正直ではある。
Moral: the problem with version A is not that it is wrong, it is that it is unpredictably wrong. A deterministic bug you can find; a scheduling-dependent one may only appear on the night before tape-out, on a different simulator.教訓:版本 A 的問題不在於它錯了,而在於它不可預測地錯。決定性的臭蟲你找得到;取決於排程的臭蟲,可能只會在 tape-out 前一晚、在另一台模擬器上才現身。教訓:版 A の問題は誤っていることではなく、予測不能な形で誤っていることである。決定的なバグは見つけられる。スケジューリングに依存するバグは、テープアウト前夜に、別のシミュレータで初めて現れるかもしれない。
A gate written without a delay specification — and (C, A, B); — has zero delay. Its output changes in the same time slot as its input, separated only by delta cycles. The slide calls this “generate the results immediately (no response time)”.一個寫出來沒有延遲規格的閘 —— and (C, A, B); —— 延遲為零。它的輸出與輸入在同一個時間槽內變化,兩者之間只隔著 delta 週期。投影片稱之為「立刻產生結果(沒有反應時間)」。遅延指定なしで書かれたゲート —— and (C, A, B); —— は遅延ゼロである。出力は入力と同じタイムスロットで変化し、両者を隔てるのはデルタサイクルだけだ。スライドはこれを「結果を即座に生成する(応答時間なし)」と呼んでいる。
Zero-delay modelling is not a simplification that will be fixed later; it is the correct and intended style for RTL, the level at which you will write everything from Lecture 4 onwards. The reason is a division of labour. RTL describes what the logic computes; a static timing analysis tool, run after synthesis against the standard-cell library, decides how long it takes. Putting delays in RTL would be stating a timing answer before the implementation that determines it exists, and the numbers would be wrong. Delays belong in two places only: the cell models of a vendor library (back-annotated from SDF after place-and-route), and teaching examples like this lecture’s.零延遲建模並不是一個日後要修正的簡化;它正是 RTL 這個層次上正確而且刻意採用的風格,而從 Lecture 4 起你寫的一切都在這個層次。理由在於分工。RTL 描述邏輯算什麼;至於要花多久,則由合成之後針對標準元件庫執行的靜態時序分析工具決定。把延遲放進 RTL,等於在決定延遲的實作存在之前就先宣布時序答案,而那些數字一定是錯的。延遲只屬於兩個地方:廠商元件庫的 cell 模型(在繞線之後由 SDF 反標註回來),以及像本講這樣的教學範例。ゼロ遅延のモデル化は、後で直すべき簡略化ではない。RTL、すなわち Lecture 4 以降に書くすべてのものが属するレベルにおける、正しく意図されたスタイルである。理由は分業にある。RTL は論理が何を計算するかを記述し、どれだけ時間がかかるかは、合成後に標準セルライブラリに対して走らせる静的タイミング解析ツールが決める。RTL に遅延を書くことは、それを決める実装が存在する前にタイミングの答えを宣言することであり、その数値は誤っている。遅延が属する場所は二つだけだ。ベンダライブラリのセルモデル(配置配線後に SDF から逆注釈される)と、本講のような教育用の例である。
The one thing zero delay costs you is the ability to see glitches. In a zero-delay model a combinational network reaches its final value in delta cycles, so hazards that a real circuit would exhibit as narrow spikes never appear. That is usually what you want in RTL simulation — you do not care about a hazard that will be swallowed by a flip-flop’s setup window — but it is why gate-level simulation with real delays is still run before tape-out.零延遲唯一讓你失去的,是看見毛刺(glitch)的能力。在零延遲模型中,組合網路在幾個 delta 週期內就到達最終值,所以真實電路會表現成窄尖波的冒險(hazard)根本不會出現。在 RTL 模擬中這通常正是你要的 —— 你不在乎一個會被正反器建立時間視窗吞掉的 hazard —— 但這也正是為什麼在 tape-out 之前仍要跑帶真實延遲的閘級模擬。ゼロ遅延で失われるものは一つだけ、グリッチを見る能力である。ゼロ遅延モデルでは組合せ回路網がデルタサイクルのうちに最終値へ到達するので、実回路なら細いスパイクとして現れるハザードがまったく出てこない。RTL シミュレーションではたいていそれが望ましい —— フリップフロップのセットアップ窓に飲み込まれるハザードなど気にしない —— が、テープアウト前に実遅延つきのゲートレベルシミュレーションを依然として走らせる理由もそこにある。
Attach a delay and the structure of the event list changes. and #3 (C, A, B); means: whenever the output of this gate would change, schedule that change three time units into the future rather than now.一旦加上延遲,事件串列的結構就變了。and #3 (C, A, B); 的意思是:每當這個閘的輸出將要改變時,就把那個改變排在未來三個時間單位,而不是現在。遅延を付けるとイベントリストの構造が変わる。and #3 (C, A, B); の意味は、このゲートの出力が変化しようとするたびに、その変化を今ではなく 3 時間単位先に予約する、ということである。
The slide’s example is the same AND/NOT chain with AND = 3 ns and inverter = 2 ns. Work it with the five-step discipline from §2.8, adding one line: schedule at $t+d$, not at $t$.投影片的例子是同一條 AND/NOT 鏈,AND = 3 ns、反相器 = 2 ns。用 §2.8 的五步紀律去做,只要加一行:排在 $t+d$,不是排在 $t$。スライドの例は同じ AND/NOT の連鎖で、AND = 3 ns、インバータ = 2 ns である。§2.8 の五段階の規律でそのまま解き、一行だけ加える。$t$ ではなく $t+d$ に予約する。
| Time時刻時刻 | Event processed處理的事件処理したイベント | Gate re-evaluated重新求值的閘再評価したゲート | New event scheduled新排入的事件新たに予約したイベント |
|---|---|---|---|
| 0 | — | AND(x,x) = x, NOT(x) = x | none; C and D sit at x無;C 與 D 停在 xなし。C と D は x のまま |
| 10 | A→1, B→0 | AND → 0, current C = xAND → 0,目前 C = xAND → 0、現在の C = x | C→0 at 13C→0 於 1313 で C→0 |
| 13 | C→0 | NOT → 1, current D = xNOT → 1,目前 D = xNOT → 1、現在の D = x | D→1 at 15D→1 於 1515 で D→1 |
| 15 | D→1 | nothing downstream下游沒有東西下流には何も無い | none無なし |
| 20 | B→1 | AND → 1, current C = 0AND → 1,目前 C = 0AND → 1、現在の C = 0 | C→1 at 23C→1 於 2323 で C→1 |
| 23 | C→1 | NOT → 0 | D→0 at 25D→0 於 2525 で D→0 |
| 30 | A→0 | AND → 0, current C = 1AND → 0,目前 C = 1AND → 0、現在の C = 1 | C→0 at 33C→0 於 3333 で C→0 |
| 33 | C→0 | NOT → 1 | D→1 at 35D→1 於 3535 で D→1 |
| 40 | B→0 | AND → 0, current C already 0AND → 0,目前 C 已經是 0AND → 0、現在の C はすでに 0 | none無なし |
Two features of the delayed waveform are worth dwelling on. First, the x region at the beginning is longer for the deeper node: C′ leaves x at 13, D′ only at 15. Unknown values propagate with the same delay as known ones. In a large design this produces the familiar phenomenon of an x “wavefront” sweeping through the logic for several hundred nanoseconds after power-up until reset has flushed it out.有延遲的波形有兩個特徵值得多停留一下。第一,開頭的 x 區域對越深的節點越長:C′ 在 13 離開 x,D′ 要到 15。未知值傳播的延遲與已知值相同。在大型設計中,這會造成大家熟悉的現象:通電後有一道 x「波前」橫掃整個邏輯好幾百奈秒,直到重置把它沖乾淨為止。遅延つき波形には、立ち止まって見る価値のある特徴が二つある。第一に、冒頭の x 領域は深いノードほど長い。C′ は 13 で x を抜け、D′ は 15 まで抜けない。不定値は既知値と同じ遅延で伝播する。大規模設計では、電源投入後に x の「波面」が数百ナノ秒にわたって論理を掃いていき、リセットがそれを洗い流すまで続く、というおなじみの現象になる。
Second, at $t = 40$ nothing happens at all. The AND gate is re-evaluated — B changed, so the simulator must look — but the computed value equals the present value, so no event is created and the inverter is never visited. An event-driven simulator can tell you it did almost no work here; a time-stepped one could not.第二,在 $t = 40$ 時什麼也沒發生。AND 閘確實被重新求值了 —— B 變了,模擬器必須去看 —— 但算出的值等於現值,所以沒有建立事件,反相器也完全不會被走訪。事件驅動模擬器能告訴你它在這裡幾乎沒做事;時間步進式的則做不到。第二に、$t = 40$ では何も起きない。AND ゲートは確かに再評価される —— B が変わったのでシミュレータは見に行かねばならない —— が、計算値が現在値と等しいのでイベントは作られず、インバータは一度も訪れられない。イベント駆動シミュレータはここでほとんど仕事をしなかったと言える。時間刻み型にはそれができない。
So far delay has looked like a simple shift to the right. It is not, and the difference is the subject of the slide titled Simulation with Inertial Delay.到目前為止,延遲看起來就像單純往右平移。其實不是,而這個差別正是那張標題為 Simulation with Inertial Delay 的投影片的主題。ここまで遅延は単に右へ平行移動するだけに見えてきた。実際はそうではなく、その違いこそ Simulation with Inertial Delay と題されたスライドの主題である。
The physical motivation comes from the gate itself. A CMOS gate’s output node has capacitance; switching it means moving charge through a finite-conductance transistor. If the input pulse is removed before enough charge has moved, the output never reaches the switching threshold of the next stage and the pulse simply does not propagate. The circuit has inertia: an input disturbance must persist for some minimum time to have any effect.物理上的動機來自閘本身。CMOS 閘的輸出節點有電容;要切換它,就必須透過一個導通度有限的電晶體搬移電荷。如果輸入脈衝在足夠的電荷搬完之前就被撤掉,輸出就永遠到不了下一級的切換門檻,脈衝也就根本不會傳下去。電路具有慣性:輸入的擾動必須持續某個最短時間,才會產生任何效果。物理的な動機はゲート自身にある。CMOS ゲートの出力ノードには容量があり、それを切り替えるには有限のコンダクタンスのトランジスタを通して電荷を動かさねばならない。十分な電荷が動く前に入力パルスが取り除かれれば、出力は次段のスイッチング閾値に届かず、パルスはそもそも伝播しない。回路には慣性がある。入力の擾乱は、何らかの効果を持つために最小限の時間だけ持続しなければならない。
The mechanism in the event queue is a cancellation. Each driver keeps at most one pending output event. When the driver re-evaluates at time $t$ and computes a new value $v$:事件佇列裡的機制是一次取消。每個驅動源最多保留一個待處理的輸出事件。當驅動源在時刻 $t$ 重新求值並算出新值 $v$ 時:イベントキュー上の仕組みはキャンセルである。各ドライバは未成熟の出力イベントを高々一つだけ保持する。ドライバが時刻 $t$ に再評価して新しい値 $v$ を計算したとき:
Step 1 followed by step 3 is pulse rejection. It is not a special case bolted on; it falls out of “one pending event per driver, replaced on every re-evaluation”.步驟 1 接著步驟 3,就是脈衝抑制。它不是硬加上去的特例;它是「每個驅動源一個待處理事件,每次重新求值都換掉」這條規則自然掉出來的結果。ステップ 1 に続いてステップ 3 が起きる、それがパルス除去である。後付けの特例ではなく、「ドライバごとに保留イベントは一つ、再評価のたびに置き換える」という規則から自然に落ちてくる。
Gate: and #2 G (O, A, B);. Initially A = 0, B = 1, so O = 0 and settled. Stimulus: A rises at $t = 10$; B falls at $t = 11$. The AND of the two inputs is therefore high for exactly 1 ns.閘:and #2 G (O, A, B);。初始 A = 0、B = 1,所以 O = 0 且已安定。激勵:A 在 $t = 10$ 上升;B 在 $t = 11$ 下降。因此兩個輸入的 AND 恰好高了 1 ns。ゲート:and #2 G (O, A, B);。初期状態は A = 0、B = 1 なので O = 0 で落ち着いている。刺激:A が $t = 10$ で立ち上がり、B が $t = 11$ で立ち下がる。したがって両入力の AND はちょうど 1 ns だけ高くなる。
A→1. Re-evaluate: 1 & 1 = 1. Current O = 0, so schedule O→1 at 12. One pending event.t = 10。A→1。重新求值:1 & 1 = 1。目前 O = 0,所以排入 O→1 於 12。一個待處理事件。t = 10。A→1。再評価:1 & 1 = 1。現在の O = 0 なので 12 で O→1 を予約する。保留イベントが一つ。B→0. Re-evaluate: 1 & 0 = 0. There is a pending event (O→1 at 12) whose value, 1, differs from the newly computed 0 → cancel it. Now compare 0 with O’s current value, which is also 0 → schedule nothing.t = 11。B→0。重新求值:1 & 0 = 0。存在一個待處理事件(12 的 O→1),其值 1 與新算出的 0 不同 → 取消它。接著把 0 與 O 的現值相比,現值也是 0 → 什麼也不排。t = 11。B→0。再評価:1 & 0 = 0。保留イベント(12 の O→1)があり、その値 1 は新たに計算した 0 と異なる → キャンセルする。次に 0 を O の現在値と比べると、そちらも 0 → 何も予約しない。O never leaves 0. The 1 ns pulse was shorter than the 2 ns inertial delay and has been filtered out entirely. Had Verilog used transport delay instead, O would have shown a 1 ns pulse from 12 to 13.結果。O 從未離開 0。那個 1 ns 的脈衝比 2 ns 的慣性延遲短,因此被完全濾掉。如果 Verilog 用的是傳輸延遲,O 就會在 12 到 13 之間出現一個 1 ns 的脈衝。結果。O は 0 から一度も離れない。1 ns のパルスは 2 ns の慣性遅延より短く、完全に濾し取られた。もし Verilog が伝送遅延を使っていたなら、O には 12 から 13 まで 1 ns のパルスが現れていたはずである。| Input pulse width $w$ vs delay $d$輸入脈衝寬度 $w$ 與延遲 $d$ 的關係入力パルス幅 $w$ と遅延 $d$ の関係 | Output輸出出力 | Reasoning理由理由 |
|---|---|---|
| $w < d$ | No pulse at all完全沒有脈衝パルスはまったく出ない | The second input change cancels the still-pending event before it matures.第二個輸入變化在待處理事件到期之前就把它取消了。二つ目の入力変化が、保留イベントが成熟する前にそれをキャンセルする。 |
| $w = d$ | Implementation dependent取決於實作実装依存 | The pending event matures in the same time slot as the cancelling re-evaluation. Which one the scheduler reaches first is exactly the “any active event” freedom of §2.5. Most simulators reject; do not build anything on this case.待處理事件與那次執行取消的重新求值落在同一個時間槽。排程器先碰到哪一個,正是 §2.5 所說的「任意一個 active 事件」的自由度。多數模擬器會抑制;不要在這個情況上建立任何東西。保留イベントが成熟する時刻と、それをキャンセルする再評価の時刻が同じタイムスロットになる。スケジューラがどちらに先に到達するかは、まさに §2.5 の「任意のアクティブイベント」という自由度である。多くのシミュレータは除去する側を選ぶが、この場合の挙動の上に何かを組み立ててはならない。 |
| $w > d$ | Full pulse of width $w$, delayed by $d$寬度為 $w$ 的完整脈衝,延後 $d$幅 $w$ のパルスがそのまま、$d$ 遅れて出る | The rising event matures before the falling re-evaluation occurs, so both edges appear.上升事件在下降的重新求值發生之前就已到期,所以兩個邊緣都會出現。立ち上がりイベントが、立ち下がりの再評価が起きる前に成熟するので、両方のエッジが現れる。 |
Exam questions love $w = d$. The defensible answer is: the standard does not define it; the pulse may or may not propagate; a simulator is free to choose, and a correct design must not depend on it. If a question insists on a single answer, say “rejected”, since that is what the common simulators do, but state the caveat. This is a genuine ambiguity in IEEE 1364 rather than a gap in your knowledge.考題很愛考 $w = d$。站得住腳的答案是:標準並未定義;脈衝可能傳過去,也可能不會;模擬器有選擇的自由,而正確的設計絕不能依賴它。如果題目硬要一個答案,就寫「被抑制」,因為常見的模擬器都是這樣做,但要把但書寫出來。這是 IEEE 1364 裡一個真正的模糊地帶,而不是你知識上的缺口。試験問題は $w = d$ を好んで出す。弁護できる答えはこうだ。標準はこれを定義していない。パルスは伝播するかもしれないし、しないかもしれない。シミュレータは自由に選んでよく、正しい設計はそれに依存してはならない。問題が一つの答えを要求するなら「除去される」と書けばよい。一般的なシミュレータはそうするからだ。ただし但し書きも添えること。これは知識の欠落ではなく、IEEE 1364 における本物の曖昧さである。
The opposite model is transport delay: every input change produces a corresponding output change $d$ later, no matter how close together they are. This is the right model for an ideal wire or a transmission line, where narrow pulses do propagate. VHDL offers both models with keywords (after versus transport ... after); Verilog gives you inertial delay by default on gates and continuous assignments, and makes you build transport delay out of non-blocking assignment.相反的模型是傳輸延遲(transport delay):每一次輸入變化都會在 $d$ 之後產生一次對應的輸出變化,不管它們靠得多近。對理想導線或傳輸線而言這才是正確的模型,因為窄脈衝確實會傳過去。VHDL 用關鍵字提供兩種模型(after 對上 transport ... after);Verilog 則在閘與連續指定上預設給你慣性延遲,而要傳輸延遲就得自己用非阻塞指定去搭。反対のモデルが伝送遅延(transport delay)である。入力変化はどれだけ近接していても、$d$ 後に対応する出力変化を必ず生む。狭いパルスも実際に伝播する理想的な配線や伝送線にはこちらが正しいモデルである。VHDL は両方のモデルをキーワードで提供する(after と transport ... after)。Verilog はゲートと連続代入では既定で慣性遅延を与え、伝送遅延はノンブロッキング代入で自分で組み立てさせる。
assign #5 out = in; // one pending event; every // re-evaluation replaces it
always @(in) out <= #5 in; // each trigger queues its own // independent NBA update
Why does the second form behave differently? Because of where the two halves of a non-blocking assignment live (§2.4). out <= #5 in; evaluates in immediately and then queues an independent update for five units later. A second trigger one unit afterwards queues a second independent update. Nothing cancels anything, so every input edge comes out the other end. The continuous assignment, by contrast, has exactly one driver with exactly one pending event, and replacing that event is what produces the inertial filtering.為什麼第二種寫法行為不同?因為非阻塞指定的兩半住在哪裡(§2.4)。out <= #5 in; 會立刻求出 in 的值,然後把一次獨立的更新排到五個單位之後。一個單位後的第二次觸發,會排出第二個獨立更新。沒有任何東西取消任何東西,所以每個輸入邊緣都會從另一端出來。相對地,連續指定只有一個驅動源、只有一個待處理事件,而替換那個事件正是產生慣性濾波的機制。なぜ二つ目の書き方は挙動が違うのか。ノンブロッキング代入の二つの半分がどこにあるかによる(§2.4)。out <= #5 in; は in を直ちに評価し、5 単位後に独立した更新を予約する。その 1 単位後に再び発火すれば、二つ目の独立した更新が予約される。何も何かをキャンセルしないので、入力のすべてのエッジが反対側から出てくる。対照的に連続代入はドライバが一つ、保留イベントも一つだけであり、そのイベントを置き換えることが慣性フィルタを生んでいる。
always @(in) out = #5 in; // blocks for 5 units: input changes during // that window are MISSED entirely
Here the always block is suspended for five time units, so it is not listening to in while it waits. This is neither inertial nor transport delay; it is a sampler with a 5-unit dead time. It appears in student code constantly, and the symptom in a waveform is dropped transitions with no obvious pattern.這裡 always 區塊被暫停五個時間單位,所以它在等待期間根本沒在聽 in。這既不是慣性延遲也不是傳輸延遲;它是一個帶 5 個單位死區時間的取樣器。它在學生的程式碼裡層出不窮,而波形上的症狀是毫無明顯規律地掉轉態。ここでは always ブロックが 5 時間単位のあいだ中断されるので、待っている間 in をまったく見ていない。これは慣性遅延でも伝送遅延でもなく、デッドタイム 5 単位のサンプラである。学生のコードには絶えず現れ、波形上の症状は、はっきりした規則性のない遷移の取りこぼしである。
If you need genuine control over pulse behaviour on a timing-annotated path — the situation in a real sign-off gate-level simulation — the mechanism is the specify block, where a module path can be given a separate rejection limit and error limit via PATHPULSE$ specparams. Below the rejection limit a pulse is filtered; above the error limit it passes; in between the output goes to x to flag that the real circuit’s behaviour is unpredictable. That three-way treatment is what vendor cell libraries actually ship with, and it is the honest answer to “what happens at $w = d$” in an industrial flow.如果你需要在一條有時序標註的路徑上真正控制脈衝行為 —— 也就是真實 sign-off 閘級模擬的情境 —— 機制是 specify 區塊:模組路徑可以透過 PATHPULSE$ specparam 被賦予獨立的抑制門檻(rejection limit)與錯誤門檻(error limit)。低於抑制門檻的脈衝會被濾掉;高於錯誤門檻的會通過;介於兩者之間時,輸出會變成 x,以標示真實電路的行為不可預測。這種三段式處理才是廠商元件庫真正隨附的作法,也是在業界流程中對「$w = d$ 會怎樣」最誠實的回答。タイミング注釈された経路でパルスの挙動を本当に制御する必要があるなら —— 実際のサインオフ用ゲートレベルシミュレーションがその状況だ —— 仕組みは specify ブロックである。モジュールパスには PATHPULSE$ specparam によって独立した除去限界(rejection limit)とエラー限界(error limit)を与えられる。除去限界より狭いパルスは濾され、エラー限界より広いものは通る。その間では出力が x になり、実回路の挙動が予測不能であることを示す。この三段構えこそベンダのセルライブラリが実際に備えているものであり、産業フローにおける「$w = d$ では何が起きるか」への正直な答えである。
Verilog’s delay specification is richer than the single number in the slides, and the full syntax turns up constantly in library models, so it is worth recognising.Verilog 的延遲規格比投影片上那個單一數字豐富得多,而完整語法在元件庫模型裡隨處可見,所以值得認得。Verilog の遅延指定はスライドの単一の数値よりはるかに豊かであり、完全な構文はライブラリモデルに絶えず現れるので、見分けられるようにしておく価値がある。
| Form形式形式 | Meaning意義意味 |
|---|---|
and #3 G (y, a, b); | One delay used for every transition.所有轉態共用一個延遲。すべての遷移に同じ遅延を使う。 |
and #(3, 4) G (y, a, b); | Rise delay 3 (any transition to 1), fall delay 4 (to 0). A transition to x uses the smaller of the two — pessimism, so that the unknown appears as early as possible.上升延遲 3(任何轉到 1 的變化)、下降延遲 4(轉到 0)。轉到 x 時用兩者中較小的那個 —— 這是悲觀作法,好讓未知值盡可能早出現。立ち上がり遅延 3(1 への遷移すべて)、立ち下がり遅延 4(0 への遷移)。x への遷移には二つのうち小さい方を使う。不定値をできるだけ早く現すための悲観的な扱いである。 |
bufif1 #(3, 4, 5) G (y, a, en); | Rise, fall and turn-off (transition to z). Only tri-state primitives accept the third value.上升、下降與關斷(轉到 z)。只有三態基本元件接受第三個值。立ち上がり・立ち下がり・ターンオフ(z への遷移)。三番目の値を受け付けるのはトライステートのプリミティブだけである。 |
and #(2:3:5) G (y, a, b); | min : typ : max. One of the three is selected at run time by a simulator switch (+mindelays, +typdelays, +maxdelays), which is how the same netlist is simulated at best-case and worst-case corners.min : typ : max。三者之一在執行時由模擬器開關選定(+mindelays、+typdelays、+maxdelays),這就是同一份網表能在最佳與最差 corner 下被模擬的方式。min : typ : max。三つのうちどれを使うかは実行時にシミュレータのスイッチ(+mindelays、+typdelays、+maxdelays)で選ばれる。同じネットリストをベストケースとワーストケースのコーナーでシミュレートできるのはこのためである。 |
and #(2:3:5, 3:4:6) G (...); | Both forms combined: min:typ:max for rise, and for fall.兩種形式合用:上升用 min:typ:max,下降也用 min:typ:max。両方の形式を組み合わせたもの。立ち上がりに min:typ:max、立ち下がりにも min:typ:max。 |
Delays may also be attached to a continuous assignment (assign #5 y = a & b;) and to a net declaration (wire #5 y;, or with a range, wire [7:0] #5 y = a ^ b; — the range comes first, the delay second). They may not be attached to a module instantiation: myblock #5 U1 (...) does not mean “delay”, it means “parameter override”, which is the subject of §7 and a notorious source of silent mis-elaboration.延遲也可以掛在連續指定上(assign #5 y = a & b;)與連線宣告上(wire #5 y;,或帶位元範圍時寫成 wire [7:0] #5 y = a ^ b; —— 範圍在前,延遲在後)。但它們不可以掛在模組實例化上:myblock #5 U1 (...) 的意思不是「延遲」,而是「參數覆寫」,那是 §7 的主題,也是惡名昭彰的無聲誤展開來源。遅延は連続代入(assign #5 y = a & b;)やネット宣言(wire #5 y;、ビット範囲を伴うなら wire [7:0] #5 y = a ^ b; —— 範囲が先、遅延が後)にも付けられる。しかしモジュールのインスタンス化に付けることはできない。myblock #5 U1 (...) は「遅延」ではなく「パラメータのオーバーライド」を意味する。これは §7 の主題であり、黙って誤ったエラボレーションを引き起こす悪名高い原因でもある。
Problem: A buffer is declared buf #4 B1 (y, x);. The input x is 0 up to $t = 0$ and then carries this sequence of changes: 1 at 10, 0 at 13, 1 at 20, 0 at 30, 1 at 32, 0 at 50. Give the complete waveform of y with times, showing which events are cancelled.問題:一個緩衝器宣告為 buf #4 B1 (y, x);。輸入 x 在 $t = 0$ 之前是 0,之後依序發生下列變化:10 時為 1、13 時為 0、20 時為 1、30 時為 0、32 時為 1、50 時為 0。寫出 y 的完整波形與時刻,並標示哪些事件被取消。問題:バッファが buf #4 B1 (y, x); と宣言されている。入力 x は $t = 0$ まで 0 で、その後次の順に変化する。10 で 1、13 で 0、20 で 1、30 で 0、32 で 1、50 で 0。y の完全な波形を時刻つきで示し、どのイベントがキャンセルされるかを述べよ。
t=10: x→1, y currently 0, schedule y→1 at 14.
t=13: x→0. Pending event (1 at 14) differs from the new value 0 → cancel. New value 0 equals current y = 0 → schedule nothing. The 3 ns pulse is rejected.
t=20: x→1, schedule y→1 at 24.
t=24: y→1 (matured; 10 ns of high input is plenty).
t=30: x→0, schedule y→0 at 34.
t=32: x→1. Pending (0 at 34) differs from 1 → cancel. New value 1 equals current y = 1 → nothing scheduled. The 2 ns low glitch is rejected and y stays high without a break.
t=50: x→0, schedule y→0 at 54.
t=54: y→0.t=10:x→1,y 目前為 0,排入 y→1 於 14。
t=13:x→0。待處理事件(14 的 1)與新值 0 不同 → 取消。新值 0 等於目前的 y = 0 → 什麼也不排。那個 3 ns 的脈衝被抑制。
t=20:x→1,排入 y→1 於 24。
t=24:y→1(到期;10 ns 的高電位輸入綽綽有餘)。
t=30:x→0,排入 y→0 於 34。
t=32:x→1。待處理(34 的 0)與 1 不同 → 取消。新值 1 等於目前的 y = 1 → 什麼也不排。那個 2 ns 的低電位毛刺被抑制,y 毫無中斷地維持高電位。
t=50:x→0,排入 y→0 於 54。
t=54:y→0。t=10:x→1、y は現在 0 なので 14 に y→1 を予約。
t=13:x→0。保留イベント(14 の 1)は新しい値 0 と異なる → キャンセル。新しい値 0 は現在の y = 0 と等しい → 何も予約しない。3 ns のパルスは除去される。
t=20:x→1、24 に y→1 を予約。
t=24:y→1(成熟。10 ns の高入力は十分である)。
t=30:x→0、34 に y→0 を予約。
t=32:x→1。保留(34 の 0)は 1 と異なる → キャンセル。新しい値 1 は現在の y = 1 と等しい → 何も予約しない。2 ns の低側グリッチは除去され、y は途切れずに高のままである。
t=50:x→0、54 に y→0 を予約。
t=54:y→0。
Answer: y = 0 until 24, 1 from 24 to 54, 0 thereafter. Two of the six input edges produced no output event at all. Note that rejection works in both directions — a narrow low glitch on a high signal is filtered just as a narrow high pulse is.答案:y 在 24 之前為 0,24 到 54 為 1,之後為 0。六個輸入邊緣中有兩個完全沒有產生輸出事件。注意抑制是雙向的 —— 高電位訊號上的窄低電位毛刺,和窄的高電位脈衝一樣會被濾掉。解答:y は 24 まで 0、24 から 54 まで 1、その後 0 である。六つの入力エッジのうち二つは出力イベントをまったく生まなかった。除去は双方向に働くことに注意せよ。高レベル信号上の狭い低側グリッチも、狭い高側パルスと同様に濾される。
Problem: A 2:1 multiplexer is built from gates: not #1 (sb, s); and #2 (p, a, sb); and #2 (q, b, s); or #2 (y, p, q);. Hold a = b = 1 and switch s from 0 to 1 at $t = 10$. (a) Does y glitch? (b) Repeat with all delays set to zero. (c) What does this tell you about using RTL simulation to find static hazards?問題:用閘搭出一個 2:1 多工器:not #1 (sb, s); and #2 (p, a, sb); and #2 (q, b, s); or #2 (y, p, q);。固定 a = b = 1,並在 $t = 10$ 時把 s 從 0 切到 1。(a) y 會不會出現毛刺?(b) 把所有延遲設為零再做一次。(c) 這告訴你用 RTL 模擬來找靜態冒險這件事如何?問題:ゲートで 2:1 マルチプレクサを組む。not #1 (sb, s); and #2 (p, a, sb); and #2 (q, b, s); or #2 (y, p, q);。a = b = 1 に固定し、$t = 10$ で s を 0 から 1 へ切り替える。(a) y にグリッチは出るか。(b) すべての遅延をゼロにして同じことをせよ。(c) これは RTL シミュレーションで静的ハザードを見つけることについて何を教えているか。
(a) Before the change: s=0, sb=1, p = 1&1 = 1, q = 1&0 = 0, y = 1. At t=10 s→1. The and for q sees 1&1 = 1, schedules q→1 at 12. The inverter schedules sb→0 at 11; at 11 the and for p sees 1&0 = 0 and schedules p→0 at 13. So q rises at 12 and p falls at 13. The OR gate is re-evaluated at 12 with (p,q) = (1,1) → 1, equal to the current y → no event. At 13 it sees (0,1) → 1, again no change. No glitch — because the path through the inverter is slower, the new path turns on before the old one turns off.(a) 變化之前:s=0、sb=1、p = 1&1 = 1、q = 1&0 = 0、y = 1。在 t=10 時 s→1。產生 q 的 and 看到 1&1 = 1,排入 q→1 於 12。反相器排入 sb→0 於 11;在 11 時產生 p 的 and 看到 1&0 = 0,排入 p→0 於 13。所以 q 在 12 上升、p 在 13 下降。OR 閘在 12 被重新求值,(p,q) = (1,1) → 1,等於目前的 y → 沒有事件。在 13 時它看到 (0,1) → 1,同樣沒有變化。沒有毛刺 —— 因為經過反相器的那條路徑較慢,新路徑先打開,舊路徑才關掉。(a) 変化前:s=0、sb=1、p = 1&1 = 1、q = 1&0 = 0、y = 1。t=10 で s→1。q を作る and は 1&1 = 1 を見て、12 に q→1 を予約する。インバータは 11 に sb→0 を予約し、11 で p を作る and が 1&0 = 0 を見て 13 に p→0 を予約する。したがって q は 12 で立ち上がり、p は 13 で立ち下がる。OR ゲートは 12 で再評価され、(p,q) = (1,1) → 1 は現在の y と等しい → イベントなし。13 では (0,1) → 1 で、やはり変化なし。グリッチは出ない。インバータを通る経路の方が遅いため、新しい経路が先にオンになってから古い経路がオフになるからである。
Now reverse the asymmetry: make the inverter fast (not #1) and the q AND slow (and #4). Then p falls at 1+2 = 13 while q does not rise until 14, so the OR sees (0,0) between 13 and 14 and emits a 1 ns low glitch — a classic static-1 hazard, visible only because the delays made it so.現在把不對稱性反過來:讓反相器變快(not #1)而產生 q 的 AND 變慢(and #4)。那麼 p 在 1+2 = 13 下降,而 q 要到 14 才上升,於是 OR 在 13 到 14 之間看到 (0,0),送出一個 1 ns 的低電位毛刺 —— 一個典型的 static-1 冒險,而它之所以看得見,純粹是因為延遲讓它看得見。次に非対称性を逆にする。インバータを速く(not #1)、q を作る AND を遅く(and #4)する。すると p は 1+2 = 13 で立ち下がるのに q は 14 まで立ち上がらないので、OR は 13 から 14 の間 (0,0) を見て 1 ns の低側グリッチを出す。典型的なスタティック 1 ハザードであり、遅延がそうさせたからこそ見えている。
(b) With zero delays the whole network settles in delta cycles at t = 10 and y never leaves 1. The glitch cannot exist, whatever the logic.(b) 在零延遲下,整個網路在 t = 10 的幾個 delta 週期內就安定,y 從未離開 1。無論邏輯如何,那個毛刺都不可能存在。(b) 遅延ゼロなら、回路網全体が t = 10 のデルタサイクルのうちに落ち着き、y は 1 から一度も離れない。論理がどうであれ、そのグリッチは存在しえない。
(c) RTL (zero-delay) simulation cannot find static hazards; they are invisible by construction. Hazards matter when the glitching signal feeds an asynchronous input — a latch enable, an asynchronous reset, a clock. The tools that find them are gate-level simulation with back-annotated SDF delays, and static timing analysis. This is also why Lecture 5 insists that combinational outputs feed only flip-flop data inputs, where a glitch is harmless as long as the signal is settled before the setup window.(c) RTL(零延遲)模擬無法找出靜態冒險;它們在構造上就是看不見的。冒險會造成問題,是在會抖動的訊號餵進非同步輸入時 —— 閂鎖的致能、非同步重置、時脈。能找出它們的工具是帶 SDF 反標註延遲的閘級模擬,以及靜態時序分析。這也正是為什麼 Lecture 5 堅持組合邏輯輸出只能餵給正反器的資料輸入,因為只要訊號在建立時間視窗之前就安定下來,毛刺在那裡是無害的。(c) RTL(ゼロ遅延)シミュレーションでは静的ハザードを見つけることはできない。構成上そもそも見えないからである。ハザードが問題になるのは、暴れる信号が非同期入力 —— ラッチのイネーブル、非同期リセット、クロック —— に入るときである。それらを見つける道具は、SDF 逆注釈つきのゲートレベルシミュレーションと静的タイミング解析である。Lecture 5 が、組合せ出力はフリップフロップのデータ入力にのみ入れよと主張するのもこのためだ。そこでは信号がセットアップ窓の前に落ち着いてさえいれば、グリッチは無害である。
The slide is candid: there is no firm rule for testbench editing. True — but there is a conventional layout that every testbench you will read follows, and deviating from it without reason costs the reader time. In order:投影片說得很坦白:測試平台的寫法沒有硬性規則。確實如此 —— 但有一套慣用的排版,你將讀到的每一個測試平台都依循它,而無故偏離它會浪費讀者的時間。依序是:スライドは率直にこう述べている。テストベンチの書き方に確たる規則は無い。その通りだが、これから読むどのテストベンチも従っている慣例的な構成はあり、理由なくそこから外れると読み手の時間を奪う。順に示す。
`timescale 1ns / 1ps // 1. time unit / precision module test_Nand_Latch_1; // 2. no port list reg preset, clear; // 3. reg for everything the TB drives wire q, qbar; // wire for everything the TB observes Nand_Latch_1 M1 (q, qbar, preset, clear); // 4. instantiate the DUT initial begin // 5. observation $monitor($time, " preset=%b clear=%b q=%b qbar=%b", preset, clear, q, qbar); end initial begin // 6. stimulus #10 preset = 0; clear = 1; #10 preset = 1; #10 clear = 0; #10 clear = 1; #10 preset = 0; end initial #60 $finish; // 7. the stop watch endmodule
Step 3 is the rule students most often get backwards, and it is not arbitrary. The testbench assigns to the DUT’s inputs from inside initial blocks, i.e. procedurally — and §6.1 will show that only a reg can be a procedural assignment target. The DUT’s outputs, conversely, are driven by the DUT itself, so from the testbench’s point of view they are connections to be watched, i.e. wires. Memorise it as: reg in, wire out, from the testbench’s perspective — which is precisely the mirror image of the DUT’s own declarations.步驟 3 是學生最常弄反的規則,而它並不武斷。測試平台是從 initial 區塊內部、也就是以程序性的方式去指定 DUT 的輸入 —— 而 §6.1 會說明只有 reg 才能當程序性指定的目標。反過來,DUT 的輸出是由 DUT 自己驅動的,所以從測試平台的角度看,它們是「要被觀察的連接」,也就是 wire。請這樣背:從測試平台的角度,輸入用 reg、輸出用 wire —— 正好是 DUT 自己宣告的鏡像。ステップ 3 は学生が最も逆に覚えやすい規則だが、恣意的なものではない。テストベンチは DUT の入力に initial ブロックの中から、すなわち手続き的に代入する —— そして §6.1 で示すように、手続き代入の代入先になれるのは reg だけである。逆に DUT の出力は DUT 自身が駆動するので、テストベンチから見ればそれは観測すべき接続、すなわち wire である。テストベンチの視点で入力は reg、出力は wireと覚えること。DUT 自身の宣言のちょうど鏡像になっている。
Step 7 is the one most often omitted. Without it a testbench containing a free-running clock never terminates (§2.3).步驟 7 是最常被省略的一步。少了它,含自由跑時脈的測試平台永遠不會終止(§2.3)。ステップ 7 は最も省かれやすい。これが無いと、フリーランのクロックを含むテストベンチは決して終了しない(§2.3)。
All initial blocks start concurrently at time zero and run independently. Splitting observation, stimulus and termination into three blocks means each reads as a simple linear script with no interleaving to reason about, and you can comment one out without disturbing the others. Cramming them into one block forces you to interleave $monitor setup with delays and makes the $finish time depend on the sum of every preceding delay — so adding one stimulus vector silently truncates the run.所有 initial 區塊都在時刻零同時啟動,並各自獨立執行。把觀察、激勵與終止拆成三個區塊,意味著每一個讀起來都是一支單純的線性腳本,不必去推理交錯執行的情形,而且你可以把其中一個註解掉而不干擾其他兩個。把它們塞進同一個區塊,會逼你把 $monitor 的設定和延遲交織在一起,也會讓 $finish 的時刻取決於前面所有延遲的總和 —— 於是加一條激勵向量就會無聲地把模擬截短。すべての initial ブロックは時刻ゼロで同時に始まり、それぞれ独立に実行される。観測・刺激・終了を三つのブロックに分ければ、どれも単純な直線的スクリプトとして読め、実行の交錯を考える必要がなく、一つをコメントアウトしても他は乱れない。一つのブロックに詰め込むと、$monitor の設定と遅延を織り交ぜる羽目になり、$finish の時刻が先行するすべての遅延の総和に依存するようになる。その結果、刺激ベクタを一本足しただけで実行が黙って短く切られる。
`timescale, time units and time precision4.2 `timescale、時間單位與時間精度4.2 `timescale・時間単位・時間精度Everything in §3 was expressed in “time units”. The compiler directive that gives them meaning is§3 裡的一切都用「時間單位」來表達。賦予它們意義的編譯指示是§3 の内容はすべて「時間単位」で表現されていた。それに意味を与えるコンパイラ指示子が次である。
`timescale <time_unit> / <time_precision>`timescale <時間單位> / <時間精度>`timescale <時間単位> / <時間精度>
The unit is what a bare number in a delay means: with `timescale 1ns/1ps, #5 is 5 ns. The precision is the granularity to which delays are rounded and the resolution at which the simulator keeps time: with 1 ps precision, #5.3721 becomes 5.372 ns. Precision must be no coarser than the unit. Both are written as 1, 10 or 100 followed by s, ms, us, ns, ps or fs.單位決定延遲中一個裸數字的意思:在 `timescale 1ns/1ps 下,#5 就是 5 ns。精度是延遲被四捨五入到的粒度,也是模擬器記錄時間的解析度:在 1 ps 精度下,#5.3721 會變成 5.372 ns。精度不可以比單位粗。兩者都寫成 1、10 或 100,後面接 s、ms、us、ns、ps 或 fs。単位は遅延に書かれた裸の数値の意味を決める。`timescale 1ns/1ps のもとでは #5 は 5 ns である。精度は遅延が丸められる粒度であり、シミュレータが時刻を保持する分解能でもある。精度 1 ps なら #5.3721 は 5.372 ns になる。精度は単位より粗くてはならない。どちらも 1、10、100 のいずれかに s、ms、us、ns、ps、fs を続けて書く。
Three practical points that cause real confusion:三個會造成真實困惑的實務要點:実際に混乱を招く実務上の要点が三つある。
`timescale applies to every module compiled after it until the next one. If one file has it and the next does not, the next inherits the previous file’s — so the behaviour of your design can depend on the order of files on the compiler command line. The fix is to put an identical `timescale at the top of every file, or none at all and set it globally with a tool switch.timescale 的作用範圍是編譯單元,不是模組。一個 `timescale 會套用到它之後編譯的每一個模組,直到下一個 `timescale 為止。如果某個檔案有而下一個沒有,下一個就繼承前一個檔案的 —— 於是你設計的行為可能取決於編譯器命令列上檔案的順序。解法是在每個檔案開頭放一個完全相同的 `timescale,或是乾脆都不放、改用工具開關全域設定。timescale の有効範囲はモジュールではなくコンパイル単位である。一つの `timescale は、その後にコンパイルされるすべてのモジュールに、次の `timescale まで適用される。あるファイルにあって次のファイルに無ければ、次のファイルは前のファイルのものを引き継ぐ。つまり設計の挙動がコンパイラのコマンドラインにおけるファイルの順序に左右されうる。対策は、全ファイルの先頭に同一の `timescale を置くか、まったく置かずにツールのスイッチでグローバルに設定することである。1ps/1fs, the whole simulation runs with femtosecond resolution and the event queue grows accordingly. This is a measurable performance problem in large gate-level runs.設計中任何一處最細的精度,決定模擬器的全域時間刻度。只要有一個元件庫模型宣告 1ps/1fs,整個模擬就會以飛秒解析度執行,事件佇列也隨之膨脹。在大型閘級模擬中,這是一個可以量到的效能問題。設計のどこかにある最も細かい精度が、シミュレータのグローバルな時間刻みを決める。セルライブラリのモデルが一つでも 1ps/1fs を宣言していれば、シミュレーション全体がフェムト秒分解能で走り、イベントキューもそれに応じて膨らむ。大規模なゲートレベル実行では、これは計測できる性能問題になる。1ns/1ns, #0.5 becomes #1 and #0.4 becomes #0 — a delay that silently vanishes into the inactive region. A clock written as always #0.5 clk = ~clk; under a 1 ns precision becomes a 2 ns clock, not a 1 ns one, and nobody notices until the frequency is wrong.四捨五入發生在展開期(elaboration),不是執行期,而且是四捨五入到最近值。在 1ns/1ns 下,#0.5 會變成 #1,而 #0.4 會變成 #0 —— 一個無聲消失進 inactive 區域的延遲。在 1 ns 精度下寫成 always #0.5 clk = ~clk; 的時脈會變成 2 ns 的時脈,而不是 1 ns 的,而且在頻率算錯之前沒有人會注意到。丸めは実行時ではなくエラボレーション時に起き、しかも最近接への丸めである。1ns/1ns のもとでは #0.5 は #1 になり、#0.4 は #0 になる —— インアクティブ領域へ黙って消える遅延である。精度 1 ns で always #0.5 clk = ~clk; と書いたクロックは 1 ns ではなく 2 ns のクロックになり、周波数が合わないと気づくまで誰も見落とす。$display, $write, $strobe, $monitor4.3 顯示家族:$display、$write、$strobe、$monitor4.3 表示系の四つ:$display、$write、$strobe、$monitorFour tasks print formatted text, and the differences between them are almost entirely about when in the event queue they run.有四個任務會印出格式化文字,而它們之間的差異幾乎完全在於它們在事件佇列的哪個時機執行。書式つきテキストを表示するタスクは四つあり、その違いはほぼすべて、イベントキューのどの時点で実行されるかにある。
| Task任務タスク | Fires觸發時機発火のタイミング | Queue region佇列區域キューの領域 | Use it for用途用途 |
|---|---|---|---|
$display | Once, when executed執行到時,印一次実行された時点で一度 | ActiveActiveActive | Progress messages, error reports, anything tied to a specific line of the stimulus. Appends a newline.進度訊息、錯誤回報,以及任何綁在激勵某一行上的輸出。會自動補上換行。進捗メッセージ、エラー報告、刺激の特定の行に結びついた出力全般。末尾に改行が付く。 |
$write | Once, when executed執行到時,印一次実行された時点で一度 | ActiveActiveActive | Identical to $display but with no trailing newline — build a line piecewise.與 $display 相同,但不補換行 —— 可以一段一段拼出一行。$display と同じだが末尾に改行が付かない —— 一行を小分けに組み立てられる。 |
$strobe | Once, at the end of the current time step在目前時間步的最後,印一次現在のタイムステップの最後に一度 | MonitorMonitorMonitor | Printing settled values after all non-blocking updates have landed.在所有非阻塞更新都落地之後,印出安定值。すべてのノンブロッキング更新が確定した後の、落ち着いた値を表示する。 |
$monitor | Continuously: once when called, then whenever any argument changes持續性:呼叫時印一次,之後只要任一引數變化就印継続的:呼び出し時に一度、その後は引数のいずれかが変化するたび | MonitorMonitorMonitor | A running trace of a small signal set for the whole run.對一小組訊號做整段模擬的連續追蹤。少数の信号について、実行全体を通した連続トレース。 |
The $display-versus-$strobe distinction is not academic. Consider a flip-flop model and a print placed just after it:$display 與 $strobe 的區別並非只是學術問題。考慮一個正反器模型,以及緊接在它後面的一次列印:$display と $strobe の違いは机上の話ではない。フリップフロップのモデルと、その直後に置かれた表示を考えてみる。
always @(posedge clk) begin
q <= d;
$display("display: t=%0t q=%b", $time, q); // prints the OLD q
$strobe ("strobe : t=%0t q=%b", $time, q); // prints the NEW q
end
$display executes in the active region, where the NBA write to q has been scheduled but not performed, so it sees the previous value. $strobe is deferred to the monitor region, after the NBA region has been drained, so it sees the updated value. Students chasing an “off by one cycle” print are usually looking at this and not at a design bug.$display 在 active 區域執行,那時對 q 的 NBA 寫入已排入但尚未執行,所以它看到的是前一個值。$strobe 則被推遲到 monitor 區域,在 NBA 區域排乾之後,所以它看到的是更新後的值。學生在追一個「差一個週期」的列印時,通常看到的是這件事,而不是設計上的臭蟲。$display はアクティブ領域で実行され、その時点で q への NBA 書き込みは予約済みだが実行されていないので、前の値を見る。$strobe は NBA 領域が空になった後のモニタ領域に遅延されるので、更新後の値を見る。「1 サイクルずれた」表示を追っている学生が見ているのはたいていこれであって、設計のバグではない。
Key facts about $monitor specifically, all of which are examinable:關於 $monitor 的幾個關鍵事實,每一條都會考:$monitor に固有の重要な事実を挙げる。いずれも試験に出る。
$monitor call replaces the first; it does not add a second trace. If you need several, build one call with all the signals, or use $strobe inside an always block.它只有一個。第二次呼叫 $monitor 會取代第一次,而不是加上第二條追蹤。如果你需要好幾條,就把所有訊號寫進同一次呼叫,或是在 always 區塊裡用 $strobe。一つしか存在しない。二度目の $monitor 呼び出しは一つ目を置き換えるのであって、二本目のトレースを追加するのではない。複数必要なら、すべての信号を一つの呼び出しにまとめるか、always ブロックの中で $strobe を使う。x.第一次被呼叫時會立刻印出,這就是為什麼投影片的輸出以 $t = 0$ 的一行開頭,顯示四個訊號全是 x。最初に呼ばれた時点で直ちに表示する。スライドの出力が $t = 0$ の行で始まり、四つの信号がすべて x になっているのはこのためである。$time. Simulation time changes constantly; if $time counted as an argument, the monitor would fire at every time step. The standard explicitly excludes $time, $stime and $realtime from the trigger set — they are printed but not watched.它不會被 $time 觸發。模擬時間時時刻刻在變;如果 $time 也算引數,監看就會在每個時間步觸發。標準明確把 $time、$stime 與 $realtime 排除在觸發集合之外 —— 它們會被印出,但不被監看。$time ではトリガされない。シミュレーション時刻は絶えず変化する。$time が引数として数えられるなら、モニタは毎タイムステップで発火してしまう。標準は $time、$stime、$realtime をトリガ集合から明示的に除外している。これらは表示されるが監視されない。$monitoroff / $monitoron suspend and resume it — useful to silence the trace during a long reset sequence.$monitoroff / $monitoron 可以暫停與恢復它 —— 在冗長的重置序列期間讓追蹤安靜下來時很有用。$monitoroff / $monitoron で一時停止と再開ができる。長いリセットシーケンスの間トレースを黙らせるのに便利である。Format specifiers are C-like: %b binary, %d decimal, %h hex, %o octal, %c character, %s string, %e %f %g real, %m the hierarchical name of the current scope, %t time. Prefixing with 0 — %0d, %0t, %0b — suppresses the default field width, which is otherwise sized for the widest possible value and produces columns of leading spaces.格式指定子沿用 C 的寫法:%b 二進位、%d 十進位、%h 十六進位、%o 八進位、%c 字元、%s 字串、%e %f %g 實數、%m 目前範圍的階層式名稱、%t 時間。在前面加 0 —— %0d、%0t、%0b —— 可以關掉預設欄寬,否則欄寬會依最寬可能值決定,印出一整排前導空白。書式指定子は C 風である。%b 二進、%d 十進、%h 十六進、%o 八進、%c 文字、%s 文字列、%e %f %g 実数、%m 現在スコープの階層名、%t 時刻。前に 0 を付けると —— %0d、%0t、%0b —— 既定のフィールド幅が抑制される。付けなければ幅は最大値に合わせて取られ、先頭に空白の列が並ぶ。
The deck writes $monitor($time, "preset = %b ...", preset, clear, q, qbar);. Passing $time as a bare first argument works, but it is printed with the default format for a 64-bit time value, which is right-justified in a wide field. The output is a column of numbers preceded by a dozen spaces, which looks broken the first time you see it.投影片寫的是 $monitor($time, "preset = %b ...", preset, clear, q, qbar);。把 $time 當成裸的第一個引數傳進去是可以的,但它會用 64 位元時間值的預設格式印出,也就是在一個很寬的欄位中靠右對齊。輸出會是一整排前面帶著十幾個空白的數字,第一次看到時會覺得壞掉了。スライドには $monitor($time, "preset = %b ...", preset, clear, q, qbar); と書かれている。$time を裸の第一引数として渡すこと自体は動くが、64 ビット時刻値の既定書式、すなわち広いフィールドでの右詰めで表示される。出力は十数個の空白を先頭に付けた数字の列になり、初めて見ると壊れているように見える。
The clean form is to put the time inside the format string: $monitor("%0t preset=%b clear=%b q=%b qbar=%b", $time, preset, clear, q, qbar);, optionally after calling $timeformat(-9, 2, " ns", 10); so that %t prints as 12.00 ns regardless of the local timescale.乾淨的寫法是把時間放進格式字串裡:$monitor("%0t preset=%b clear=%b q=%b qbar=%b", $time, preset, clear, q, qbar);,必要時先呼叫 $timeformat(-9, 2, " ns", 10);,讓 %t 不論當地 timescale 為何都印成 12.00 ns。きれいな書き方は、時刻を書式文字列の中に入れることである。$monitor("%0t preset=%b clear=%b q=%b qbar=%b", $time, preset, clear, q, qbar);。必要なら先に $timeformat(-9, 2, " ns", 10); を呼んでおけば、ローカルな timescale に関係なく %t は 12.00 ns と表示される。
$time, $stop and $finish4.4 $time、$stop 與 $finish4.4 $time・$stop・$finishThree time functions exist and they differ in ways that matter:有三個時間函數,而它們的差異很要緊:時刻に関する関数は三つあり、その違いは重要である。
| Function函數関数 | Returns回傳戻り値 | Note註記注記 |
|---|---|---|
$time | 64-bit integer64 位元整數64 ビット整数 | Scaled to the time unit of the module that calls it and rounded. Under 1ns/100ps, at a true time of 1.5 ns, $time returns 2.依呼叫它的那個模組的時間單位縮放,並且會四捨五入。在 1ns/100ps 下,真實時刻 1.5 ns 時 $time 回傳 2。それを呼んだモジュールの時間単位にスケーリングされ、さらに丸められる。1ns/100ps のもとでは、真の時刻 1.5 ns において $time は 2 を返す。 |
$realtime | real | Same scaling, no rounding: returns 1.5. Use this whenever fractional delays are in play.相同的縮放,但不四捨五入:回傳 1.5。只要有分數延遲在場就該用這個。スケーリングは同じだが丸めない。1.5 を返す。小数の遅延が絡むときは常にこちらを使う。 |
$stime | 32-bit integer32 位元整數32 ビット整数 | The low 32 bits of $time. Cheaper to print, wraps after about 4.3×109 units.$time 的低 32 位元。印起來比較省,約在 4.3×109 個單位之後會繞回。$time の下位 32 ビット。表示が軽く、およそ 4.3×109 単位でラップアラウンドする。 |
Because the scaling is per-module, the same instant prints as different numbers in two modules with different `timescale directives. That is not a bug, and it is a good reason to use one timescale everywhere.因為縮放是以模組為單位的,同一個瞬間在兩個 `timescale 不同的模組中會印出不同的數字。這不是臭蟲,而且它是「整個設計統一用一種 timescale」的好理由。スケーリングがモジュールごとであるため、同じ瞬間でも `timescale の異なる二つのモジュールでは別の数値として表示される。これはバグではないが、全体で単一の timescale を使うべき十分な理由である。
Ending a run:結束一次模擬:実行の終わらせ方:
$finish terminates the simulation and returns control to the operating system. It takes an optional diagnostic level: 0 prints nothing, 1 (the default) prints the simulation time and location, 2 adds CPU-time and memory statistics.$finish 終止模擬並把控制權交還作業系統。它接受一個選用的診斷層級:0 什麼都不印,1(預設)印出模擬時間與位置,2 再加上 CPU 時間與記憶體統計。$finish はシミュレーションを終了し、制御をオペレーティングシステムに返す。省略可能な診断レベルを取る。0 は何も表示せず、1(既定)はシミュレーション時刻と位置を表示し、2 はさらに CPU 時間とメモリの統計を加える。$stop suspends the simulation and drops into the simulator’s interactive prompt, leaving the full state alive for inspection. In the slide’s testbench the comment “Enter . to proceed” refers to the old Verilog-XL interactive command for continuing. In a batch regression $stop is a menace — it hangs the job waiting for a keystroke nobody will type — so most groups ban it from checked-in code and use it only while debugging at a terminal.$stop 暫停模擬並落到模擬器的互動提示符,讓完整狀態留著可供檢視。投影片測試平台裡的註解「Enter . to proceed」指的是舊 Verilog-XL 用來繼續執行的互動指令。在批次回歸測試中 $stop 是個禍害 —— 它會讓工作卡住,等一個沒有人會敲的按鍵 —— 所以多數團隊禁止它出現在簽入的程式碼裡,只在終端機前除錯時使用。$stop はシミュレーションを中断してシミュレータの対話プロンプトに落ち、状態をすべて生かしたまま調べられるようにする。スライドのテストベンチにある「Enter . to proceed」というコメントは、実行を継続するための古い Verilog-XL の対話コマンドを指している。バッチのリグレッションでは $stop は災いである —— 誰も打たないキー入力を待ってジョブを止めてしまう —— ので、多くのグループはチェックインするコードでの使用を禁じ、端末でデバッグするときだけ使う。$dumpvars and the VCD file4.5 $dumpvars 與 VCD 檔4.5 $dumpvars と VCD ファイルText output tells you what happened; a waveform shows you when, which is what you actually need for a timing bug. The standard, tool-independent mechanism is the Value Change Dump, a plain-text file that records the initial value of every selected signal and then every subsequent change. Two tasks create it:文字輸出告訴你發生了什麼;波形則顯示何時發生,而後者才是抓時序臭蟲真正需要的。標準且與工具無關的機制是 Value Change Dump(值變化傾印),一個純文字檔,記錄每個被選中訊號的初值,以及之後的每一次變化。有兩個任務負責產生它:テキスト出力は何が起きたかを教え、波形はいつ起きたかを見せる。タイミングのバグに本当に必要なのは後者である。標準的でツール非依存の仕組みが Value Change Dump であり、選択した各信号の初期値と、その後のすべての変化を記録するプレーンテキストファイルである。これを作るタスクは二つある。
initial begin
$dumpfile("wave.vcd"); // name the file; call this FIRST, once
$dumpvars(0, test_Nand_Latch_1); // what to record
end
The first argument of $dumpvars is a depth, and its encoding is the part everyone misremembers:$dumpvars 的第一個引數是深度,而它的編碼方式正是大家都會記錯的部分:$dumpvars の第一引数は深さであり、その符号化こそ誰もが覚え違いをする部分である。
| Call呼叫呼び出し | Records記錄範圍記録される範囲 |
|---|---|
$dumpvars; | Every variable in the entire design, at every level. Simple, and far too large for anything real.整個設計中每一層的每一個變數。簡單,但對任何真實設計都大得離譜。設計全体のあらゆる階層のすべての変数。単純だが、現実の設計にはあまりに大きすぎる。 |
$dumpvars(1, tb); | Variables declared in tb itself only — no sub-modules.只有宣告在 tb 本身的變數 —— 不含子模組。tb 自身で宣言された変数だけ —— 下位モジュールは含まない。 |
$dumpvars(2, tb); | tb and one level of instances below it.tb 與它底下一層的實例。tb と、その一階層下のインスタンス。 |
$dumpvars(0, tb); | Depth 0 means unlimited: tb and all levels below. This is the usual choice.深度 0 表示無限制:tb 與底下所有層。這是常用的選擇。深さ 0 は無制限を意味する。tb とその下の全階層。通常はこれを選ぶ。 |
$dumpvars(0, tb.M1.q, tb.clk); | Specific signals (a depth is still required but is ignored for individual variables).指定的個別訊號(仍然需要給深度,但對個別變數而言會被忽略)。特定の個別信号(深さは依然として必要だが、個別変数に対しては無視される)。 |
Companion tasks: $dumpoff and $dumpon suspend and resume recording, $dumpall forces a checkpoint of every value, $dumplimit(n) caps the file at n bytes, and $dumpflush forces buffered data to disk so the file is readable even if the simulation later crashes. All of them are not synthesisable, like everything else in this section.相關的配套任務:$dumpoff 與 $dumpon 暫停與恢復記錄,$dumpall 強制把每個值做一次檢查點,$dumplimit(n) 把檔案上限壓在 n 位元組,$dumpflush 強制把緩衝資料寫入磁碟,讓模擬之後就算崩潰檔案仍然讀得出來。它們全都not synthesisable,跟本節其他東西一樣。関連タスク:$dumpoff と $dumpon は記録の一時停止と再開、$dumpall はすべての値のチェックポイントを強制、$dumplimit(n) はファイルを n バイトで打ち切り、$dumpflush はバッファのデータをディスクへ書き出して、後でシミュレーションがクラッシュしてもファイルを読めるようにする。いずれも本節の他のものと同じくnot synthesisableである。
A VCD records one text line per value change. For a million-gate design at 10 % activity the file grows by gigabytes per millisecond of simulated time, and writing it can dominate run time. Standard mitigations: dump only the hierarchy you are debugging rather than $dumpvars(0, tb); turn dumping off during reset and on just before the failing cycle with $dumpoff/$dumpon; or use the simulator’s compressed native format (FSDB, VPD, WLF) instead of VCD, which is typically ten to fifty times smaller.VCD 每一次值變化就記一行文字。對一顆百萬閘、活動率 10 % 的設計,每模擬一毫秒檔案就長好幾 GB,而寫檔本身可能成為執行時間的主要開銷。標準的緩解手段:只傾印你正在除錯的那層階層,而不是 $dumpvars(0, tb);用 $dumpoff/$dumpon 在重置期間關掉、在失敗週期前才打開;或改用模擬器自家的壓縮格式(FSDB、VPD、WLF)而非 VCD,通常可以小上十到五十倍。VCD は値が変化するたびにテキスト 1 行を記録する。100 万ゲート、活性度 10 % の設計では、シミュレーション時間 1 ミリ秒あたり数ギガバイトずつ増え、その書き出しが実行時間の大半を占めることもある。標準的な緩和策は、$dumpvars(0, tb) ではなくデバッグ対象の階層だけをダンプすること、$dumpoff/$dumpon でリセット中はダンプを止め、失敗するサイクルの直前で入れること、あるいは VCD ではなくシミュレータ独自の圧縮形式(FSDB、VPD、WLF)を使うことである。後者は通常 10〜50 倍小さい。
The slide’s worked example is an SR latch built from two cross-coupled NAND gates, each with one unit of delay:投影片的實作演練是一個由兩個交叉耦合 NAND 閘搭成的 SR 閂鎖,每個閘都有一個單位的延遲:スライドの計算例は、遅延 1 単位の NAND ゲート二つを交差結合して作った SR ラッチである。
module Nand_Latch_1 (q, qbar, preset, clear);
output q, qbar;
input preset, clear;
nand #1 G1 (q, preset, qbar),
G2 (qbar, clear, q);
endmodule
Both inputs are active low: driving preset to 0 forces q high, driving clear to 0 forces qbar high, and holding both at 1 makes the pair hold its state. Driving both to 0 simultaneously is the forbidden input, since it drives both outputs to 1 and the subsequent behaviour depends on which input is released first.兩個輸入都是低電位有效:把 preset 驅動到 0 會強制 q 為高,把 clear 驅動到 0 會強制 qbar 為高,而把兩者都維持在 1 則讓這對閘保持狀態。同時把兩者都驅動到 0 是禁止輸入,因為那會把兩個輸出都驅到 1,而後續行為取決於哪一個輸入先被釋放。どちらの入力もローアクティブである。preset を 0 に駆動すると q がハイに強制され、clear を 0 に駆動すると qbar がハイに強制され、両方を 1 に保てばこの対は状態を保持する。両方を同時に 0 に駆動するのは禁止入力である。両出力が 1 になり、その後の振る舞いがどちらの入力を先に解放するかに依存するからだ。
Note also a subtlety in the stimulus. The line #10 preset = 0; clear = 1; contains two statements. The #10 delays only the first; the assignment to clear then executes with no further delay, in the same time slot. Both changes therefore land at $t = 10$. This is the single most common misreading of the slide’s testbench.激勵裡還有一個細節要注意。#10 preset = 0; clear = 1; 這一行包含兩條敘述。#10 只延遲第一條;對 clear 的指定接著在沒有額外延遲的情況下執行,落在同一個時間槽。因此兩個變化都發生在 $t = 10$。這是對投影片測試平台最常見的誤讀。刺激にはもう一つ細かい点がある。#10 preset = 0; clear = 1; という行には文が二つある。#10 が遅らせるのは最初の文だけで、clear への代入はさらなる遅延なしに同じタイムスロットで実行される。したがって両方の変化が $t = 10$ に起きる。スライドのテストベンチで最もよくある読み違いがこれである。
Now apply the inertial-delay bookkeeping of §3.3 one time stamp at a time. Remember the NAND identity that does all the work: any 0 input forces the output to 1, regardless of the other input, even if the other input is x. That is what lets the latch escape from the all-unknown start-up state.現在一次一個時間戳地套用 §3.3 的慣性延遲記帳法。記住那個撐起一切的 NAND 恆等式:任何一個輸入為 0,就把輸出強制為 1,與另一個輸入無關,就算另一個輸入是 x 也一樣。正是它讓閂鎖得以逃離全未知的開機狀態。ここで §3.3 の慣性遅延の帳簿付けを、タイムスタンプごとに一つずつ適用する。すべてを支える NAND の恒等式を覚えておくこと。入力のどれか一つが 0 なら、他方の入力が何であれ、たとえ x であっても、出力は 1 に強制される。ラッチが全不定の起動状態から脱出できるのはこれのおかげである。
| Time時刻時刻 | preset | clear | q | qbarqbarqbar | What the simulator did模擬器做了什麼シミュレータが行ったこと |
|---|---|---|---|---|---|
| 0 | x | x | x | x | Nothing assigned; both gates compute NAND(x,x) = x, which matches, so nothing is scheduled. $monitor prints its first line.什麼都還沒指定;兩個閘都算出 NAND(x,x) = x,與現值相符,所以沒有排任何事件。$monitor 印出它的第一行。まだ何も代入されていない。両ゲートとも NAND(x,x) = x を計算し、現在値と一致するので何も予約されない。$monitor が最初の行を表示する。 |
| 10 | 0 | 1 | x | x | G1 re-evaluates: NAND(0, x) = 1 ≠ x → schedule q→1 at 11. G2: NAND(1, x) = x — unchanged, nothing scheduled.G1 重新求值:NAND(0, x) = 1 ≠ x → 排入 q→1 於 11。G2:NAND(1, x) = x —— 沒變,什麼也不排。G1 が再評価される。NAND(0, x) = 1 ≠ x → 11 に q→1 を予約。G2:NAND(1, x) = x —— 変化なしなので何も予約しない。 |
| 11 | 0 | 1 | 1 | x | q updates. Only G2 has q as an input: NAND(1, 1) = 0 ≠ x → schedule qbar→0 at 12.q 更新。只有 G2 以 q 為輸入:NAND(1, 1) = 0 ≠ x → 排入 qbar→0 於 12。q が更新される。q を入力に持つのは G2 だけである。NAND(1, 1) = 0 ≠ x → 12 に qbar→0 を予約。 |
| 12 | 0 | 1 | 1 | 0 | qbar updates. G1 re-evaluates: NAND(0, 0) = 1 = current q → no event. The latch has settled; the start-up x is gone.qbar 更新。G1 重新求值:NAND(0, 0) = 1 = 目前的 q → 沒有事件。閂鎖已安定;開機時的 x 消失了。qbar が更新される。G1 が再評価され、NAND(0, 0) = 1 = 現在の q → イベントなし。ラッチは落ち着き、起動時の x は消えた。 |
| 20 | 1 | 1 | 1 | 0 | Preset released. G1: NAND(1, 0) = 1 = current q → no event. This is the hold state — the feedback through qbar = 0 keeps q high with no external help.preset 被釋放。G1:NAND(1, 0) = 1 = 目前的 q → 沒有事件。這就是保持狀態 —— 經由 qbar = 0 的回授讓 q 維持高電位,不需要任何外部幫助。preset が解放される。G1:NAND(1, 0) = 1 = 現在の q → イベントなし。これが保持状態である。qbar = 0 を通る帰還が、外部の助けなしに q をハイに保つ。 |
| 30 | 1 | 0 | 1 | 0 | Clear asserted. G2: NAND(0, 1) = 1 ≠ 0 → schedule qbar→1 at 31.clear 被致能。G2:NAND(0, 1) = 1 ≠ 0 → 排入 qbar→1 於 31。clear がアサートされる。G2:NAND(0, 1) = 1 ≠ 0 → 31 に qbar→1 を予約。 |
| 31 | 1 | 0 | 1 | 1 | G1: NAND(1, 1) = 0 ≠ 1 → schedule q→0 at 32.G1:NAND(1, 1) = 0 ≠ 1 → 排入 q→0 於 32。G1:NAND(1, 1) = 0 ≠ 1 → 32 に q→0 を予約。 |
| 32 | 1 | 0 | 0 | 1 | G2: NAND(0, 0) = 1 = current qbar → no event. Settled in the cleared state.G2:NAND(0, 0) = 1 = 目前的 qbar → 沒有事件。在 cleared 狀態下安定。G2:NAND(0, 0) = 1 = 現在の qbar → イベントなし。クリア状態で落ち着く。 |
| 40 | 1 | 1 | 0 | 1 | Clear released. G2: NAND(1, 0) = 1 = current qbar → hold.clear 被釋放。G2:NAND(1, 0) = 1 = 目前的 qbar → 保持。clear が解放される。G2:NAND(1, 0) = 1 = 現在の qbar → 保持。 |
| 50 | 0 | 1 | 0 | 1 | Preset asserted again. G1: NAND(0, 1) = 1 ≠ 0 → schedule q→1 at 51.preset 再次被致能。G1:NAND(0, 1) = 1 ≠ 0 → 排入 q→1 於 51。再び preset がアサートされる。G1:NAND(0, 1) = 1 ≠ 0 → 51 に q→1 を予約。 |
| 51 | 0 | 1 | 1 | 1 | Both outputs are momentarily 1 — the transient state while the loop propagates. G2: NAND(1, 1) = 0 → schedule qbar→0 at 52.兩個輸出暫時都是 1 —— 這是迴路正在傳遞時的暫態。G2:NAND(1, 1) = 0 → 排入 qbar→0 於 52。両出力が一時的に 1 になる —— ループが伝播している間の過渡状態である。G2:NAND(1, 1) = 0 → 52 に qbar→0 を予約。 |
| 52 | 0 | 1 | 1 | 0 | Settled. $finish at 60 ends the run.已安定。60 時的 $finish 結束模擬。落ち着いた。60 の $finish で実行が終わる。 |
Two lessons live in that one-unit gap. First, a cross-coupled latch is not instantaneous: the second output always follows the first by a gate delay, and during that window q and qbar are not complementary. Any logic that samples both and assumes they disagree will see an illegal state. Second, the x at start-up is cleared not by an initial block but by asserting an active-low input — which is exactly what reset does in a real chip, and exactly why every synchronous design in this course has one.那一個單位的落差裡藏著兩個教訓。第一,交叉耦合的閂鎖不是瞬時的:第二個輸出永遠比第一個晚一個閘延遲,而在那段窗口內 q 與 qbar 並不互補。任何同時取樣兩者、又假設它們必然相異的邏輯,都會看到非法狀態。第二,開機時的 x 不是靠 initial 區塊清掉的,而是靠致能一個低電位有效輸入 —— 那正是真實晶片裡重置所做的事,也正是本課程中每個同步設計都有重置的原因。その 1 単位のずれには教訓が二つある。第一に、交差結合ラッチは瞬時ではない。二つ目の出力は常に一つ目よりゲート遅延 1 個分遅れ、その窓の間 q と qbar は相補ではない。両方をサンプルして必ず食い違うと仮定している論理は、不正な状態を見ることになる。第二に、起動時の x は initial ブロックではなく、ローアクティブ入力をアサートすることで消える。それはまさに実チップでリセットが行うことであり、本講義のすべての同期設計にリセットがある理由でもある。
Almost every testbench you write after this lecture needs a clock. The idiom, and the trap:本講之後你寫的幾乎每一個測試平台都需要時脈。寫法與陷阱如下:本講以降に書くテストベンチはほぼすべてクロックを必要とする。定石と罠を示す。
reg clk; always #5 clk = ~clk; // clk starts at x. // ~x is x. It stays x // for the whole run, and // every posedge never // happens.
reg clk; initial clk = 1'b0; always #5 clk = ~clk; // or, in one block: // initial begin // clk = 0; // forever #5 clk = ~clk; // end
The failure mode of the left-hand version is characteristic and worth recognising instantly: the waveform viewer shows clk as a solid red or hatched band, every flip-flop output is x, and the whole design is dark. Whenever an entire design is x from time zero, suspect an uninitialised reg that is being inverted or incremented, because x is a fixed point of both operations.左邊版本的失效模式很有特徵,值得一眼認出:波形檢視器把 clk 顯示成一條實心紅色或斜線的帶子,每個正反器輸出都是 x,整個設計一片黑。每當整個設計從時刻零開始就是 x,就懷疑是某個沒初始化的 reg 正被取反或遞增,因為 x 是這兩種運算的不動點。左側の版の壊れ方は特徴的で、即座に見分けられるようにしておく価値がある。波形ビューアでは clk が赤一色か斜線の帯として表示され、すべてのフリップフロップ出力が x になり、設計全体が真っ暗になる。設計全体が時刻ゼロから x のときは、初期化されていない reg が反転または加算されているのを疑うこと。x はどちらの演算の不動点でもあるからだ。
A self-checking skeleton, which is what you should be writing by the end of this course:一個自我檢查的骨架,也就是你在本課程結束時應該寫得出來的樣子:自己チェック型の骨組み。本講義を終えるころには、これを書けるようになっているべきである。
integer errors = 0;
task check;
input [7:0] got, exp;
begin
if (got !== exp) begin // note !== , not != : see 8.5
$display("ERROR at %0t: got %h expected %h", $time, got, exp);
errors = errors + 1;
end
end
endtask
initial begin
// ... stimulus and checks ...
if (errors == 0) $display("PASS: no mismatches");
else $display("FAIL: %0d mismatches", errors);
$finish;
end
The !== rather than != is deliberate and is the single most important detail in that fragment. With !=, a DUT output of 8'hxx compared against an expected 8'h3c yields x, which an if treats as false — so the check silently passes on a completely broken output. !== compares x literally and returns a hard 1, so the mismatch is caught. Every checker in a real testbench uses the four-state comparison operators; §8.5 explains why.用 !== 而不是 != 是刻意的,而且是那段程式碼中最重要的一個細節。用 != 時,DUT 輸出 8'hxx 與期待值 8'h3c 相比會得到 x,而 if 會把它當成假 —— 於是在輸出完全壞掉的情況下,檢查無聲地通過了。!== 會照字面比較 x 並回傳確實的 1,所以不符會被抓到。真實測試平台裡的每一個檢查器都用四值比較運算子;§8.5 會說明原因。!= ではなく !== を使うのは意図的であり、この断片で最も重要な一点である。!= では、DUT 出力 8'hxx を期待値 8'h3c と比べると結果は x になり、if はそれを偽として扱う。つまり出力が完全に壊れているのにチェックが黙って通る。!== は x を文字どおり比較して確かな 1 を返すので、不一致が捕まる。実際のテストベンチのチェッカはどれも四値比較演算子を使う。理由は §8.5 で説明する。
Problem: (a) Why does the trace show a line at $t = 11$ but not at $t = 21$? (b) The stimulus sets preset = 0 at 50 but the slide’s waveform stops at 60. What would happen at 60 if the third initial block were deleted? (c) Suppose the stimulus were changed so that at $t = 30$ both preset and clear go to 0, and at $t = 40$ both return to 1 in the same time slot. Describe the simulation from 40 onwards.問題:(a) 為什麼追蹤在 $t = 11$ 有一行,在 $t = 21$ 卻沒有?(b) 激勵在 50 時把 preset 設為 0,但投影片的波形停在 60。如果把第三個 initial 區塊刪掉,60 時會發生什麼事?(c) 假設把激勵改成:在 $t = 30$ 時 preset 與 clear 同時變成 0,而在 $t = 40$ 時兩者在同一個時間槽內回到 1。描述 40 之後的模擬過程。問題:(a) なぜトレースには $t = 11$ の行があり、$t = 21$ の行は無いのか。(b) 刺激は 50 で preset を 0 にするが、スライドの波形は 60 で止まっている。三つ目の initial ブロックを削除したら 60 で何が起きるか。(c) 刺激を変えて、$t = 30$ で preset と clear が両方 0 になり、$t = 40$ で両方が同じタイムスロットで 1 に戻るとする。40 以降のシミュレーションを記述せよ。
(a) $monitor prints only when one of its arguments changes. At 11, q changed. At 21, nothing changed: the preset release at 20 produced no output event because G1 already computed 1 from the feedback path. “No line” is information — it means the latch held.(a) $monitor 只在它的某個引數變化時才印。在 11 時 q 變了。在 21 時什麼都沒變:20 時釋放 preset 並沒有產生輸出事件,因為 G1 早就從回授路徑算出 1 了。「沒有那一行」本身就是資訊 —— 它代表閂鎖保持住了。(a) $monitor は引数のいずれかが変化したときにだけ表示する。11 では q が変化した。21 では何も変化していない。20 の preset 解放は出力イベントを生まなかった。G1 はすでに帰還経路から 1 を計算していたからである。「行が無いこと」自体が情報であり、ラッチが保持したことを意味する。
(b) Nothing more is scheduled after $t = 52$: the stimulus block has run to completion, the latch is stable, and the event list is empty. The simulation would stop on its own at 52 even without $finish. It is the free-running clock case, not this one, that hangs without a stop watch — but you should still write the $finish, because the moment you add a clock the behaviour changes.(b) 在 $t = 52$ 之後不再排任何事件:激勵區塊已執行完畢,閂鎖穩定,事件串列是空的。就算沒有 $finish,模擬也會在 52 自行停止。會在沒有碼表時卡住的是自由跑時脈那種情況,不是這一種 —— 但你還是應該寫 $finish,因為只要你一加上時脈,行為就變了。(b) $t = 52$ 以降は何も予約されない。刺激ブロックは走り終わり、ラッチは安定し、イベントリストは空である。$finish が無くてもシミュレーションは 52 で自ら止まる。ストップウォッチ無しでハングするのはフリーランのクロックの場合であって、この場合ではない。とはいえ $finish は書いておくべきだ。クロックを足した瞬間に挙動が変わるからである。
(c) With both inputs 0, both NANDs output 1, so q = qbar = 1 — the forbidden state. When both are released to 1 at 40, each gate now computes NAND(1, 1) = 0 and schedules its output to 0 at 41. At 41 both go to 0; each gate then computes NAND(1, 0) = 1 and schedules 1 at 42. At 42 both go to 1 again, and the cycle repeats. The latch oscillates with a period of 2 time units for ever, and $monitor prints two lines every 2 units until $finish. This is Verilog’s deterministic stand-in for metastability: real silicon would resolve to one state or the other after an unpredictable time, but the model, being perfectly symmetric, never breaks the tie. If the two gates had different delays the asymmetry would resolve it, which is a neat illustration of why metastability is a physical phenomenon that no purely logical model can reproduce.(c) 兩個輸入都是 0 時,兩個 NAND 都輸出 1,所以 q = qbar = 1 —— 禁止狀態。當兩者在 40 時都被釋放回 1,每個閘現在都算出 NAND(1, 1) = 0,並把自己的輸出排在 41 變成 0。在 41 兩者都變 0;每個閘接著算出 NAND(1, 0) = 1,排在 42 變成 1。在 42 兩者又都變回 1,於是循環重複。閂鎖會以 2 個時間單位為週期永遠振盪下去,而 $monitor 每 2 個單位印兩行,直到 $finish 為止。這是 Verilog 對亞穩態(metastability)的決定性替身:真實的矽會在一段無法預測的時間後落到其中一個狀態,但這個模型因為完全對稱,永遠打不破平手。如果兩個閘的延遲不同,不對稱性就會解決它 —— 這漂亮地說明了為什麼亞穩態是一個物理現象,任何純邏輯模型都重現不了。(c) 両入力が 0 のとき両 NAND は 1 を出力するので q = qbar = 1、すなわち禁止状態である。40 で両方が 1 に解放されると、各ゲートは NAND(1, 1) = 0 を計算し、41 に自身の出力 0 を予約する。41 で両方が 0 になり、各ゲートは NAND(1, 0) = 1 を計算して 42 に 1 を予約する。42 で両方が再び 1 になり、以後このサイクルが繰り返す。ラッチは周期 2 時間単位で永久に発振し、$monitor は $finish まで 2 単位ごとに 2 行を表示する。これは Verilog によるメタステーブル状態の決定論的な代役である。実際のシリコンは予測できない時間の後にどちらかの状態へ落ち着くが、このモデルは完全に対称なので引き分けを決して破れない。二つのゲートの遅延が異なっていれば非対称性がそれを解消する。メタステーブル状態が純粋に論理的なモデルでは再現できない物理現象であることの、見事な例証である。
Problem: Each fragment has exactly one defect. Name it and give the symptom a user would see.問題:每個片段恰好有一個缺陷。指出它,並說明使用者會看到的症狀。問題:各断片にはちょうど一つ欠陥がある。それを指摘し、利用者が目にする症状を述べよ。
(a) module tb (input clk); reg d; dut u1(.clk(clk), .d(d)); ...
(b) initial begin $monitor("a=%b", a); $monitor("b=%b", b); end
(c) initial begin #10 a = 1; #10 a = 0; end
always #5 clk = ~clk;
// no $finish anywhere
(d) wire start; initial begin start = 1'b0; #20 start = 1'b1; end
(a) The testbench has a port. Nothing above it drives clk, so clk stays z for ever, no edge occurs, and the DUT never does anything. Symptom: a totally static waveform. Fix: make the top module portless and generate clk inside it.(a) 測試平台有埠。它上面沒有東西驅動 clk,所以 clk 永遠停在 z,不會有任何邊緣,DUT 也永遠不做事。症狀:完全靜止的波形。修法:把頂層模組改成無埠,並在裡面產生 clk。(a) テストベンチにポートがある。その上位で clk を駆動するものが無いので clk は永遠に z のままで、エッジは起きず、DUT は何もしない。症状:まったく動かない波形。修正:トップモジュールをポート無しにし、その内部で clk を生成する。
(b) Only one $monitor can be active; the second call replaces the first. Symptom: the trace of a is missing and nobody notices because no error is reported. Fix: one call with both signals, $monitor("a=%b b=%b", a, b);.(b) 只能有一個 $monitor 在作用;第二次呼叫取代了第一次。症狀:a 的追蹤不見了,而且因為沒有任何錯誤回報,沒有人會注意到。修法:用一次呼叫涵蓋兩個訊號,$monitor("a=%b b=%b", a, b);。(b) 有効な $monitor は一つだけであり、二度目の呼び出しが一度目を置き換える。症状:a のトレースが消えるが、エラーは報告されないので誰も気づかない。修正:両方の信号を一度の呼び出しにまとめる。$monitor("a=%b b=%b", a, b);。
(c) No $finish, and the always block re-arms itself every 5 units, so the event list never empties. Symptom: the simulation runs for ever, the log file grows without bound, and the batch job is eventually killed by the queue manager. Fix: add initial #1000 $finish;. (There is a second, latent defect: clk is never initialised, so it would be stuck at x anyway — see §4.7.)(c) 沒有 $finish,而 always 區塊每 5 個單位就重新裝填自己,所以事件串列永遠不會空。症狀:模擬永遠跑下去,log 檔無止境長大,批次工作最後被佇列管理器殺掉。修法:加上 initial #1000 $finish;。(還有第二個潛伏的缺陷:clk 從未初始化,所以它本來就會卡在 x —— 見 §4.7。)(c) $finish が無く、always ブロックが 5 単位ごとに自分を再武装するので、イベントリストは決して空にならない。症状:シミュレーションが永遠に続き、ログファイルが際限なく膨らみ、バッチジョブは最後にキュー管理者に強制終了される。修正:initial #1000 $finish; を加える。(もう一つ潜在的な欠陥がある。clk が初期化されていないので、いずれにせよ x のまま動かない —— §4.7 を参照。)
(d) start is a wire but is the target of procedural assignments inside an initial block. Symptom: a compile-time error, which is the good case. Fix: declare it reg. This is the rule from §6.1 and the reason testbenches declare DUT inputs as reg.(d) start 宣告成 wire,卻被當成 initial 區塊內程序性指定的目標。症狀:編譯期錯誤,這反而是好的情況。修法:宣告成 reg。這就是 §6.1 的規則,也是測試平台把 DUT 輸入宣告為 reg 的理由。(d) start が wire で宣言されているのに、initial ブロック内の手続き代入の代入先になっている。症状:コンパイルエラー。これはむしろ幸運な場合である。修正:reg で宣言する。これが §6.1 の規則であり、テストベンチが DUT の入力を reg と宣言する理由である。
Verilog nets and variables take one of four values: 0, 1, x and z. Boolean algebra needs only two; the other two exist because a simulator has to model situations that Boolean algebra has no vocabulary for.Verilog 的連線與變數可以取四個值之一:0、1、x 與 z。布林代數只需要兩個;另外兩個之所以存在,是因為模擬器必須模擬一些布林代數根本沒有詞彙可以描述的情況。Verilog のネットと変数は 0、1、x、z の四値のいずれかを取る。ブール代数に必要なのは二つだけだ。残り二つが存在するのは、シミュレータがブール代数に語彙を持たない状況までモデル化しなければならないからである。
| Value值値 | Means意義意味 | Where it comes from從何而來どこから来るか |
|---|---|---|
0 | Logic zero, FALSE邏輯零,FALSE論理ゼロ、FALSE | A driver pulling the node to ground.有驅動源把節點拉到接地。ドライバがノードをグラウンドへ引いている。 |
1 | Logic one, TRUE邏輯一,TRUE論理イチ、TRUE | A driver pulling the node to supply.有驅動源把節點拉到電源。ドライバがノードを電源へ引いている。 |
x | Unknown / ambiguous未知/不明確不定/曖昧 | The simulator cannot determine the value. Uninitialised storage; a net with two drivers that disagree; the output of a gate whose relevant input is x; a flip-flop whose setup time was violated in a timing-annotated run.模擬器無法判定這個值。未初始化的儲存元件;兩個互相矛盾的驅動源共用一條連線;相關輸入為 x 的閘的輸出;在有時序標註的模擬中違反建立時間的正反器。シミュレータが値を決定できない。初期化されていない記憶素子、互いに食い違う二つのドライバを持つネット、関係する入力が x であるゲートの出力、タイミング注釈つきの実行でセットアップ時間に違反したフリップフロップなど。 |
z | High impedance高阻抗ハイインピーダンス | Nothing is driving the node: a tri-state buffer that is disabled, an unconnected input, a bus with no active master.沒有東西在驅動這個節點:被關掉的三態緩衝器、沒接的輸入、沒有作用中主控者的匯流排。そのノードを駆動しているものが無い。無効化されたトライステートバッファ、未接続の入力、アクティブなマスタが無いバスなど。 |
The distinction between x and z trips people up because both are “not 0 or 1”. They are not alike. z is a known physical state — the node is floating, and that is a definite, deliberate and often correct condition. x is an admission of ignorance: the simulator is telling you it does not know, and it is almost always a symptom. A floating node that is then read by an ordinary gate becomes x, because a real CMOS input presented with a floating wire produces an output nobody can predict.x 與 z 的區別常讓人絆倒,因為兩者都是「不是 0 也不是 1」。它們其實不像。z 是一個已知的物理狀態 —— 節點處於浮接,而那是明確的、刻意的,而且常常是正確的狀況。x 則是承認無知:模擬器在告訴你它不知道,而這幾乎永遠是一個症狀。一個浮接節點若被普通的閘讀取,會變成 x,因為真實 CMOS 輸入接上浮接導線時,產生的輸出誰也預測不了。x と z の区別は、どちらも「0 でも 1 でもない」ために人をつまずかせる。両者は似ていない。z は既知の物理状態であり、ノードが浮いているという明確で意図的な、しばしば正しい状態である。x は無知の告白である。シミュレータが分からないと告げているのであり、ほぼ常に何らかの症状である。浮いたノードを通常のゲートが読めば x になる。実際の CMOS 入力に浮いた配線をつなげば、出力は誰にも予測できないからだ。
There is no wire in a chip carrying “x”. Every physical node sits at some voltage. x means “this model cannot tell you which”, and its purpose is to make your ignorance visible rather than letting the simulator invent a plausible-looking 0. If a design works in simulation only because an x happened to be interpreted as 0 by an if statement, it will not work in silicon.晶片裡沒有任何一條線承載著「x」。每個物理節點都處在某個電壓。x 的意思是「這個模型無法告訴你是哪一個」,它的目的是讓你的無知可見,而不是讓模擬器憑空捏造一個看起來合理的 0。如果一個設計在模擬中會動,只是因為某個 x 剛好被 if 敘述當成 0,那它在矽上就不會動。チップの中に「x」を運ぶ配線は存在しない。物理ノードはどれも何らかの電圧にある。x は「このモデルにはどちらか分からない」という意味であり、その目的は、シミュレータにもっともらしい 0 をでっち上げさせるのではなく、あなたの無知を可視にすることである。x がたまたま if 文で 0 と解釈されたおかげでシミュレーションが通っている設計は、シリコンでは動かない。
The deck shows the AND table; here are all the ones you need. The pattern to learn rather than memorise is controlling values dominate: a 0 on an AND forces 0 whatever the other input is, a 1 on an OR forces 1, and only when no input is controlling does the unknown leak through.投影片給了 AND 的表;這裡把你需要的全都列出來。要學的是模式而不是死背:控制值會壓過一切 —— AND 上的 0 不管另一個輸入是什麼都強制為 0,OR 上的 1 強制為 1,只有在沒有任何輸入是控制值時,未知才會滲透出去。スライドには AND の表がある。ここでは必要なものをすべて挙げる。丸暗記ではなく覚えるべきパターンは支配値が優先することだ。AND の 0 は他方の入力が何であれ出力を 0 に強制し、OR の 1 は 1 に強制する。どの入力も支配値でないときにだけ、不定が漏れ出る。
| and | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | x | x |
| x | 0 | x | x | x |
| z | 0 | x | x | x |
| or | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | x |
| 1 | 1 | 1 | 1 | 1 |
| x | x | 1 | x | x |
| z | x | 1 | x | x |
| xor | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | x |
| 1 | 1 | 0 | x | x |
| x | x | x | x | x |
| z | x | x | x | x |
| ininin | buf | not |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
| x | x | x |
| z | x | x |
Note the last row: a z input produces x, not z. A gate driven by a floating wire has an unpredictable output. Only tri-state primitives (bufif, notif) can produce z.注意最後一列:z 輸入產生的是 x,不是 z。被浮接導線驅動的閘,輸出是不可預測的。只有三態基本元件(bufif、notif)才能產生 z。最後の行に注目せよ。z の入力が生むのは z ではなく x である。浮いた配線に駆動されたゲートの出力は予測できない。z を生成できるのはトライステートのプリミティブ(bufif、notif)だけである。
XOR has no controlling value at all — the result depends on both inputs — so any x or z on either input makes the output x. Parity trees therefore turn a single unknown bit into a completely unknown result, which is why a lone uninitialised bit in a CRC generator blackens the whole checksum.XOR 根本沒有控制值 —— 結果取決於兩個輸入 —— 所以任一輸入上的 x 或 z 都會讓輸出變成 x。因此同位元樹會把單一個未知位元變成完全未知的結果,這也是為什麼 CRC 產生器裡孤零零一個未初始化的位元,就能把整個檢查碼都塗黑。XOR には支配値がまったく無く、結果は両方の入力に依存するので、どちらかの入力の x や z は出力を x にする。したがってパリティツリーは単一の不定ビットを完全に不定な結果へ変える。CRC 生成器の中のたった一つの未初期化ビットが、チェックサム全体を黒く塗りつぶす理由である。
The NAND case in §4.6 is worth restating because it is the reason the latch starts up at all: nand(0, x) is 1, not x. The 0 is controlling for the AND part, forcing 0, and the inversion makes it 1 — the unknown input never gets a vote.§4.6 的 NAND 情形值得重述一次,因為它正是閂鎖之所以能開機的原因:nand(0, x) 是 1,不是 x。那個 0 對 AND 的部分是控制值,強制為 0,再經反相變成 1 —— 未知的那個輸入根本沒有投票權。§4.6 の NAND の場合は繰り返す価値がある。ラッチがそもそも起動できる理由だからだ。nand(0, x) は x ではなく 1 である。0 は AND 部分にとって支配値で出力を 0 に強制し、反転して 1 になる。不定の入力には投票権が無い。
The tables above describe a gate. A different set of tables describes what happens when two or more drivers fight over the same net. The net’s declared type chooses the resolution function, which is the real reason Verilog has more than one net type.上面那些表描述的是一個閘。另外一組表則描述兩個以上的驅動源爭奪同一條連線時會發生什麼。連線宣告的型別決定了採用哪個解析函數,而這才是 Verilog 擁有不只一種連線型別的真正理由。上の表が記述しているのはゲートである。二つ以上のドライバが同じネットを取り合うときに何が起きるかは、別の一組の表が記述する。ネットの宣言された型が解決関数を選び、これこそ Verilog にネット型が複数ある本当の理由である。
| wire | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | x | x | 0 |
| 1 | x | 1 | x | 1 |
| x | x | x | x | x |
| z | 0 | 1 | x | z |
| wand | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 1 | x | 1 |
| x | 0 | x | x | x |
| z | 0 | 1 | x | z |
| wor | 0 | 1 | x | z |
|---|---|---|---|---|
| 0 | 0 | 1 | x | 0 |
| 1 | 1 | 1 | 1 | 1 |
| x | x | 1 | x | x |
| z | 0 | 1 | x | z |
Read the common structure. In all three tables the last row and column are identical: a driver contributing z contributes nothing, so the other driver wins outright. That is what high impedance means — a disconnected driver cannot influence the node.請讀出共同的結構。三張表的最後一列與最後一行都相同:貢獻 z 的驅動源什麼也沒貢獻,所以另一個驅動源完全獲勝。這就是高阻抗的意思 —— 一個斷開的驅動源無法影響節點。共通の構造を読み取ってほしい。三つの表とも最後の行と列は同一である。z を出すドライバは何も寄与しないので、もう一方のドライバが完全に勝つ。これがハイインピーダンスの意味である。切り離されたドライバはノードに影響できない。
The tables differ only in the 0 / 1 conflict cell. A plain wire says “two drivers disagree, I cannot tell you the voltage” and yields x — which models a real short between supply and ground, drawing crowbar current and producing an indeterminate level. wand models open-drain outputs with a pull-up: any driver pulling low wins. wor models the dual, wired-OR with a pull-down.三張表只在 0 / 1 衝突的那一格不同。單純的 wire 說「兩個驅動源意見相左,我沒辦法告訴你電壓」,於是給出 x —— 這模擬的是電源與地之間真實的短路,流過貫穿電流,產生不確定的電位。wand 模擬的是帶上拉的開汲極輸出:任何把電位拉低的驅動源獲勝。wor 則模擬其對偶,帶下拉的 wired-OR。三つの表が異なるのは 0 / 1 の衝突セルだけである。素の wire は「二つのドライバが食い違っており、電圧は答えられない」と言って x を返す。これは電源とグラウンドの実際の短絡をモデル化しており、貫通電流が流れて電位は不確定になる。wand はプルアップ付きのオープンドレイン出力をモデル化し、ローに引くドライバが勝つ。wor はその双対で、プルダウン付きのワイアード OR である。
This is why an unexpected x on a bus is nearly always a multiple-driver bug. Two always blocks assigning the same net, a tri-state bus with two enables accidentally asserted together, or a module output accidentally connected to another module output: all produce x on a plain wire, and the x then spreads downstream through §5.2’s tables until half the design is dark.這就是為什麼匯流排上出現意料之外的 x,幾乎一定是多驅動源的臭蟲。兩個 always 區塊指定同一條連線、三態匯流排上兩個致能不小心同時拉起、或是一個模組輸出不小心接到另一個模組輸出:在單純的 wire 上全都會產生 x,而那個 x 接著循著 §5.2 的表往下游擴散,直到半個設計一片黑。バス上に予期しない x が出たら、ほぼ必ず多重ドライバのバグである。二つの always ブロックが同じネットに代入している、トライステートバスで二つのイネーブルがうっかり同時にアサートされている、あるいはモジュール出力が別のモジュール出力につながっている。素の wire ではいずれも x を生み、その x が §5.2 の表に従って下流へ広がり、設計の半分が真っ暗になる。
Verilog actually tracks more than four values internally: each of 0 and 1 carries one of eight drive strengths (supply, strong, pull, large, weak, medium, small, highz), giving 120 distinct states. The resolution tables above are the special case in which every driver is strong, which is what gate primitives and continuous assignments produce by default.Verilog 內部實際追蹤的值不只四個:0 與 1 各自帶有八種驅動強度之一(supply、strong、pull、large、weak、medium、small、highz),總共給出 120 個相異狀態。上面那些解析表是每個驅動源都是 strong 的特例,而那正是閘基本元件與連續指定預設產生的強度。Verilog が内部で追跡している値は実は四つより多い。0 と 1 はそれぞれ八段階の駆動強度(supply、strong、pull、large、weak、medium、small、highz)のいずれかを帯びており、合わせて 120 の異なる状態になる。上の解決表は、すべてのドライバが strong である特別な場合であり、それがゲートプリミティブと連続代入が既定で生成する強度である。
Strengths matter for switch-level and mixed-signal modelling — a pullup on an open-drain bus is pull1, so a strong0 from an active driver overrides it and the node reads 0 without becoming x. For RTL and cell-based design you will almost never write a strength specification, and synthesis ignores them. Know that they exist, recognise supply0 and supply1 (nets permanently tied to ground and supply, used for tie cells and for the power pins of library models), and move on.強度在開關層級與混合訊號建模時才重要 —— 開汲極匯流排上的 pullup 是 pull1,所以作用中驅動源送出的 strong0 會蓋過它,節點讀起來是 0 而不會變成 x。對 RTL 與 cell-based 設計而言,你幾乎永遠不會去寫強度規格,合成也會忽略它們。知道它們存在、認得 supply0 與 supply1(永久接到地與電源的連線,用於 tie cell 與元件庫模型的電源腳),然後就往下走。強度が意味を持つのはスイッチレベルやミックスドシグナルのモデル化のときである。オープンドレインバス上の pullup は pull1 なので、アクティブなドライバの strong0 がそれを上書きし、ノードは x にならずに 0 と読める。RTL やセルベース設計で強度指定を書くことはまず無く、合成もそれを無視する。存在することを知り、supply0 と supply1(グラウンドと電源に恒久的に接続されたネットで、タイセルやライブラリモデルの電源ピンに使う)を見分けられれば十分である。
Four-value simulation is an approximation of reality, and it is wrong in both directions. Knowing which way it is wrong in a given construct is what separates a debugger from someone who stares at a waveform.四值模擬是對現實的一個近似,而且它在兩個方向上都會出錯。知道某個語法結構是往哪個方向錯,正是除錯者與只會盯著波形發呆的人之間的差別。四値シミュレーションは現実の近似であり、しかも両方向に誤る。ある構文でどちら向きに誤るかを知っていることが、デバッグできる人と波形を眺めるだけの人を分ける。
x-pessimism is the four-value system reporting x when the real circuit has a definite value. Example: assign y = sel ? a : a; with sel = x. A real multiplexer outputs a whichever way it is steered, but the bitwise merge rule of §8.8 compares a with a bit by bit and yields a — in this case the model is right. Change it to y = sel ? 1'b1 : 1'b1 and it still gives 1. But (a & ~x) | (a & x) written out as gates gives x, because the simulator evaluates each gate independently and cannot see that the two unknowns are the same unknown. This is the structural reason gate-level simulation is more pessimistic than RTL: reconvergent unknowns are treated as independent.x 悲觀(x-pessimism)是指真實電路有確定值時,四值系統卻回報 x。例子:assign y = sel ? a : a; 且 sel = x。真實的多工器無論被導向哪一邊都輸出 a,而 §8.8 的逐位元合併規則會把 a 與 a 一位一位比較,得到 a —— 在這個情況下模型是對的。把它改成 y = sel ? 1'b1 : 1'b1 仍然得到 1。但把 (a & ~x) | (a & x) 攤成閘來寫就會得到 x,因為模擬器是獨立求值每個閘的,看不出那兩個未知其實是同一個未知。這就是閘級模擬比 RTL 更悲觀的結構性理由:重新匯合的未知被當成互相獨立。x 悲観性(x-pessimism)とは、実回路には確定値があるのに四値システムが x を報告することである。例:sel = x のもとでの assign y = sel ? a : a;。実際のマルチプレクサはどちらに切り替わっても a を出力し、§8.8 のビットごとのマージ規則は a と a をビット単位で比較して a を返す。この場合モデルは正しい。y = sel ? 1'b1 : 1'b1 に変えてもやはり 1 を返す。しかし (a & ~x) | (a & x) をゲートに展開して書くと x になる。シミュレータは各ゲートを独立に評価するので、二つの不定が同じ不定であることを見抜けないからだ。これがゲートレベルシミュレーションが RTL より悲観的になる構造的な理由である。再収束する不定が互いに独立として扱われる。
x-optimism is the opposite and is far more dangerous: the model produces a clean 0 or 1 where the real circuit is genuinely unpredictable. The classic source is a behavioural if or case:x 樂觀(x-optimism)則相反,而且危險得多:真實電路確實不可預測,模型卻給出乾淨的 0 或 1。最典型的來源是行為式的 if 或 case:x 楽観性(x-optimism)はその逆で、はるかに危険である。実回路は本当に予測不能なのに、モデルはきれいな 0 か 1 を出す。典型的な発生源は振る舞い記述の if や case である。
always @(*) if (sel) y = a; else y = b; // sel = x ==> takes the ELSE branch, // y = b, a confident and wrong answer
An if treats x as false, so the unknown is laundered into a definite result and the x stops propagating. The design appears to work in RTL simulation; the equivalent gate-level netlist, which propagates the x faithfully, then fails, and the two simulations disagree. This mismatch is a routine cause of “RTL passes, gates fail” tickets. The remedy is discipline: initialise properly, reset everything, and never let a selector be x in the first place. Lecture 5 adds full_case/parallel_case and the synthesis-side consequences.if 把 x 當成假,於是未知被漂白成一個確定結果,x 的傳播就此中止。設計在 RTL 模擬中看起來能動;而忠實傳播 x 的等價閘級網表卻會失敗,兩個模擬結果不一致。這種不匹配是「RTL 過、閘級掛」這類工單的常見成因。解方是紀律:好好初始化、把每個東西都重置,而且一開始就別讓選擇訊號變成 x。Lecture 5 會補上 full_case/parallel_case 以及合成端的後果。if は x を偽として扱うので、不定は確定した結果へと漂白され、x の伝播はそこで止まる。設計は RTL シミュレーションでは動くように見えるが、x を忠実に伝播する等価なゲートレベルネットリストは失敗し、二つのシミュレーションが食い違う。この不一致は「RTL は通るのにゲートで落ちる」という報告の定番の原因である。対処は規律である。きちんと初期化し、すべてをリセットし、そもそもセレクタを x にしないこと。Lecture 5 では full_case/parallel_case と合成側への影響が加わる。
Problem: Evaluate each, giving reasons. (a) nand(1, x) (b) nor(1, x) (c) xor(z, 0) (d) buf(z) (e) a wire driven by 1 and by z (f) a wire driven by 0 and by 1 (g) a wand driven by 0 and by 1 (h) and(0, z).問題:求出以下各值並說明理由。(a) nand(1, x) (b) nor(1, x) (c) xor(z, 0) (d) buf(z) (e) 一條同時被 1 與 z 驅動的 wire (f) 一條同時被 0 與 1 驅動的 wire (g) 一條同時被 0 與 1 驅動的 wand (h) and(0, z)。問題:次の各式を評価し、理由を述べよ。(a) nand(1, x) (b) nor(1, x) (c) xor(z, 0) (d) buf(z) (e) 1 と z に駆動される wire (f) 0 と 1 に駆動される wire (g) 0 と 1 に駆動される wand (h) and(0, z)。
(a) x. There is no controlling value: 1 is non-controlling for AND, so the result depends on the unknown. (b) 0. The 1 is controlling for OR, forcing the OR to 1 and the NOR to 0 regardless of the other input. (c) x. XOR has no controlling value and z on a gate input is as good as unknown. (d) x. A buffer driven by a floating node has an unpredictable output; only tri-state primitives emit z. (e) 1. The z driver contributes nothing. (f) x — the conflict cell of the wire table; this models a short. (g) 0 — wired-AND, the low driver wins; no conflict, no x. (h) 0. The 0 is controlling for AND, so the floating input is irrelevant. Note how (a) versus (b) and (c) versus (h) make the same point: look for a controlling value first, and only reach for the table if there is none.(a) x。沒有控制值:1 對 AND 而言是非控制值,所以結果取決於那個未知。(b) 0。那個 1 對 OR 是控制值,不管另一個輸入是什麼都把 OR 強制為 1、NOR 為 0。(c) x。XOR 沒有控制值,而閘輸入上的 z 等同於未知。(d) x。被浮接節點驅動的緩衝器輸出不可預測;只有三態基本元件才會送出 z。(e) 1。z 驅動源什麼也沒貢獻。(f) x —— wire 表中的衝突格;這模擬的是短路。(g) 0 —— wired-AND,低電位驅動源獲勝;沒有衝突,沒有 x。(h) 0。那個 0 對 AND 是控制值,所以浮接的輸入無關緊要。注意 (a) 對 (b)、(c) 對 (h) 說的是同一件事:先找控制值,只有在沒有控制值時才去查表。(a) x。支配値が無い。1 は AND にとって非支配値なので、結果は不定に依存する。(b) 0。1 は OR にとって支配値で、他方の入力に関わらず OR を 1、NOR を 0 に強制する。(c) x。XOR に支配値は無く、ゲート入力の z は不定と同じである。(d) x。浮いたノードに駆動されたバッファの出力は予測できない。z を出せるのはトライステートのプリミティブだけである。(e) 1。z のドライバは何も寄与しない。(f) x —— wire の表の衝突セルであり、短絡をモデル化している。(g) 0 —— ワイアード AND でローのドライバが勝つ。衝突は無く x も出ない。(h) 0。0 は AND の支配値なので、浮いた入力は無関係である。(a) と (b)、(c) と (h) が同じことを示している点に注意せよ。まず支配値を探し、無いときにだけ表を引く。
Verilog has two families of declarable object and the distinction is the most consequential piece of syntax in the language. The slide puts it in one line each — nets: structural connectivity; registers: abstraction of storage — and then adds the warning that matters: may or may not be physical storage.Verilog 有兩大類可宣告的物件,而這個區別是整個語言中後果最重大的一項語法。投影片各用一行帶過 —— net:結構性連接;register:儲存的抽象 —— 然後加上那句真正要緊的警告:可能是、也可能不是實體儲存元件。Verilog には宣言できるオブジェクトが二系統あり、この区別は言語中で最も影響の大きい構文である。スライドはそれぞれ一行で済ませている —— ネット:構造的な接続、レジスタ:記憶の抽象 —— そして肝心の注意書きを添える。物理的な記憶素子であることも、そうでないこともある。
z — it cannot remember.若所有驅動源都消失,它就變成 z —— 它不會記憶。ドライバがすべて無くなれば z になる —— 記憶はできない。initial, always), a task, a function, or a sequential UDP.只能在程序性區塊(initial、always)、task、function 或循序 UDP 內被指定。代入できるのは手続きブロック(initial、always)、タスク、関数、順序 UDP の内部だけである。x.初值為 x。x から始まる。This naming is the worst wart in Verilog and it costs every cohort a week of confusion. A reg is a storage location in the simulator, not a flip-flop. Whether it becomes a flip-flop depends entirely on how you assign it: always @(posedge clk) q <= d; infers a flip-flop, while always @(*) y = a & b; uses a reg named y and infers nothing but an AND gate. The reason y must be declared reg is purely that it is assigned procedurally. SystemVerilog fixed the naming by introducing logic, which means exactly “reg” without the misleading word. Lectures 5 and 6 build the rules for when storage is actually inferred.這個命名是 Verilog 最糟的疣,每一屆學生都要為它耗掉一星期的困惑。reg 是模擬器中的一個儲存位置,不是正反器。它會不會變成正反器,完全取決於你怎麼指定它:always @(posedge clk) q <= d; 推論出一個正反器,而 always @(*) y = a & b; 用了一個叫 y 的 reg,卻只推論出一個 AND 閘。y 必須宣告成 reg 的理由,純粹是因為它是被程序性地指定的。SystemVerilog 引入 logic 修正了這個命名,它的意思就是「reg」,只是少了那個誤導人的詞。Lecture 5 與 6 會建立「何時真的會推論出儲存元件」的規則。この命名は Verilog 最大のいぼであり、どの学年も一週間ぶんの混乱を支払わされる。reg はシミュレータ内の記憶場所であって、フリップフロップではない。フリップフロップになるかどうかは、どう代入するかだけで決まる。always @(posedge clk) q <= d; はフリップフロップを推論するが、always @(*) y = a & b; は y という reg を使いながら AND ゲートしか推論しない。y を reg と宣言しなければならない理由は、ひとえに手続き的に代入されるからである。SystemVerilog は logic を導入してこの命名を正した。意味はまさに「reg」であり、誤解を招く語が無いだけである。記憶素子が実際に推論される条件は Lecture 5 と 6 で構築される。
Both families are informally called signals, and both may be scalar (one bit) or vector (a declared range).這兩類都被非正式地稱為訊號(signal),而且都可以是純量(一個位元)或向量(有宣告範圍)。この二系統はどちらも非公式に信号(signal)と呼ばれ、どちらもスカラ(1 ビット)にもベクタ(範囲を宣言したもの)にもなれる。
The table on slide 15 is short and entirely mechanical, but it explains the shape of every testbench you will write.第 15 頁投影片上的表很短,而且完全是機械性的,但它解釋了你將寫出的每一個測試平台的形狀。スライド 15 枚目の表は短く、まったく機械的だが、これから書くすべてのテストベンチの形を説明している。
| Port direction埠方向ポートの方向 | Inside the module (formal)模組內部(形式參數)モジュール内部(仮引数側) | Outside, in the parent (actual)模組外部、在上層(實際參數)外側、親モジュール内(実引数側) |
|---|---|---|
input | must be a net必須是 netネットでなければならない | net or registernet 或 registerネット または レジスタ |
output | net or registernet 或 registerネット または レジスタ | must be a net必須是 netネットでなければならない |
inout | must be a net必須是 netネットでなければならない | must be a net必須是 netネットでなければならない |
The underlying principle is a single sentence: a port is a continuous connection, and a continuous connection can only drive a net. An input port is driven from outside, so inside it must be a net. An output port drives the outside, so outside it must be a net. Whatever is doing the driving on the far side may be a register, because a register can perfectly well be the source of a continuous connection — it just cannot be its destination.背後的原則只有一句話:埠是一種持續的連接,而持續的連接只能驅動 net。input 埠是從外面被驅動的,所以在裡面它必須是 net。output 埠驅動外面,所以在外面它必須是 net。至於另一側負責驅動的東西,可以是 register,因為 register 完全可以是持續連接的來源 —— 它只是不能當它的目的地。背後の原則は一文で言える。ポートは継続的な接続であり、継続的な接続はネットしか駆動できない。input ポートは外から駆動されるので、内側ではネットでなければならない。output ポートは外を駆動するので、外側ではネットでなければならない。その反対側で駆動している当のものはレジスタでよい。レジスタは継続的接続の源には何の問題も無くなれる。なれないのは行き先の方だけである。
Two direct consequences you will use constantly:兩個你會不斷用到的直接推論:絶えず使うことになる直接の帰結が二つある。
reg (it drives them procedurally) and DUT outputs as wire (it only watches them). The “reg in, wire out” rule from §4.1 is this table read from the parent’s side.測試平台把 DUT 輸入宣告成 reg(它以程序性方式驅動它們),把 DUT 輸出宣告成 wire(它只是看著它們)。§4.1 的「輸入 reg、輸出 wire」規則,就是從上層那一側來讀這張表。テストベンチは DUT の入力を reg と宣言し(手続き的に駆動するから)、DUT の出力を wire と宣言する(見ているだけだから)。§4.1 の「入力は reg、出力は wire」という規則は、この表を親側から読んだものである。always block must be declared output ... ; reg ...; (or, in Verilog-2001 ANSI style, output reg ...). An output assigned by assign stays a net.在 DUT 內部,一個在 always 區塊中被指定的輸出必須宣告成 output ... ; reg ...;(或在 Verilog-2001 的 ANSI 風格中寫成 output reg ...)。由 assign 指定的輸出則維持為 net。DUT の内部では、always ブロックで代入される出力は output ... ; reg ...;(Verilog-2001 の ANSI 形式なら output reg ...)と宣言しなければならない。assign で代入される出力はネットのままである。inout, net on both sides, driven by a tri-state buffer from inside — this is the only time you will write tri deliberately.雙向接腳是 inout,兩側都是 net,由內部的三態緩衝器驅動 —— 這是你唯一會刻意寫 tri 的場合。双方向パッドは inout で、両側ともネットであり、内側のトライステートバッファから駆動される。意図して tri と書くのはこのときだけである。The scope of a variable is the module, task, function or named begin...end block in which it is declared. Within that scope you refer to it by its bare identifier; from outside, only by a hierarchical path name (§9).變數的範圍(scope)是它被宣告所在的模組、task、function 或具名的 begin...end 區塊。在那個範圍之內,你用裸識別字指涉它;從外面則只能用階層式路徑名稱(§9)。変数のスコープは、それが宣言されたモジュール、タスク、関数、または名前付き begin...end ブロックである。そのスコープの内側では裸の識別子で参照し、外側からは階層パス名でしか参照できない(§9)。
The named-block case is the one students forget. begin : loop_body integer i; ... end gives i a scope limited to that block, which is how you get a loop counter that does not collide with one in a neighbouring block. An unnamed begin...end cannot contain declarations at all, which is why the compiler rejects a declaration dropped in the middle of a procedural block.具名區塊那一項是學生最常忘記的。begin : loop_body integer i; ... end 讓 i 的範圍侷限在該區塊內,這就是你如何得到一個不會跟鄰近區塊撞名的迴圈計數器。未具名的 begin...end 根本不能包含宣告,這也正是為什麼編譯器會拒絕被塞在程序性區塊中間的宣告。名前付きブロックの場合は学生が忘れやすい。begin : loop_body integer i; ... end と書けば i のスコープはそのブロック内に限られ、隣のブロックのものと衝突しないループカウンタが得られる。名前の無い begin...end は宣言をまったく含められない。手続きブロックの途中に置かれた宣言をコンパイラが拒否するのはこのためである。
Verilog has no block-nested scoping in the C sense and no shadowing by default: a module-level reg count and a task-level reg count are two different objects, and inside the task the inner one wins — but there is no “inner block” scope inside an if or a for unless you name it. Tasks and functions declared in a module can see the module’s variables directly, which is both convenient and a classic source of accidental shared state when a task is called from two places at once.Verilog 沒有 C 意義下的區塊巢狀範圍,預設也沒有遮蔽(shadowing):模組層級的 reg count 與 task 層級的 reg count 是兩個不同的物件,而在 task 內部是內層那個勝出 —— 但 if 或 for 裡面並沒有「內層區塊」範圍,除非你替它命名。宣告在模組中的 task 與 function 可以直接看見該模組的變數,這既方便,也是當一個 task 同時被兩處呼叫時、意外共享狀態的經典來源。Verilog には C の意味でのブロック入れ子スコープも、既定のシャドーイングも無い。モジュールレベルの reg count とタスクレベルの reg count は別のオブジェクトで、タスク内では内側が勝つ —— しかし名前を付けない限り if や for の中に「内側のブロック」スコープは存在しない。モジュール内で宣言されたタスクと関数はそのモジュールの変数を直接見られる。これは便利であると同時に、タスクが二か所から同時に呼ばれたときの偶発的な状態共有という古典的な原因でもある。
| Type型別型 | Resolution解析方式解決方法 | Typical use典型用途典型的な用途 |
|---|---|---|
wire | Conflict → x衝突 → x衝突 → x | The default and 99 % of all nets. Ordinary single-driver connectivity.預設值,涵蓋全部連線的 99 %。一般的單驅動源連接。既定であり、全ネットの 99 % を占める。通常の単一ドライバの接続。 |
tri | Identical to wire與 wire 完全相同wire と完全に同一 | Pure documentation: identical semantics, but tells the reader the net is expected to float sometimes. A bidirectional pad net.純粹是文件用途:語意完全相同,但告訴讀者這條連線預期有時會浮接。例如雙向接腳的連線。純粋に文書化のためのもの。意味は同一だが、このネットが時として浮くことを読み手に伝える。双方向パッドのネットなど。 |
wand, triand | Any 0 wins任一個 0 獲勝どれか一つでも 0 なら 0 が勝つ | Open-drain / wired-AND buses, e.g. an I²C-style shared line or a distributed “all done” signal.開汲極/wired-AND 匯流排,例如 I²C 式的共用線路,或分散式的「全部完成」訊號。オープンドレイン/ワイアード AND のバス。たとえば I²C 風の共有線や、分散した「全部完了」信号。 |
wor, trior | Any 1 wins任一個 1 獲勝どれか一つでも 1 なら 1 が勝つ | Wired-OR interrupt or request lines.wired-OR 的中斷或請求線路。ワイアード OR の割り込み線や要求線。 |
tri0, tri1 | Floats to 0 / to 1浮接時落到 0/落到 1浮いたとき 0 / 1 に落ちる | A net with a resistive pull-down or pull-up; reads 0 (or 1) when no driver is active.帶電阻式下拉或上拉的連線;沒有作用中驅動源時讀到 0(或 1)。抵抗によるプルダウンまたはプルアップを備えたネット。アクティブなドライバが無いとき 0(または 1)と読める。 |
supply0, supply1 | Constant常數定数 | Permanently tied to ground / supply, at supply strength. Tie cells and library power pins.以 supply 強度永久接到地/電源。用於 tie cell 與元件庫的電源腳。supply 強度でグラウンド/電源に恒久接続される。タイセルやライブラリの電源ピンに使う。 |
trireg | Holds the last driven value保留最後被驅動的值最後に駆動された値を保持する | Charge storage on a node — dynamic logic, switch-level models. Essentially never used in RTL.節點上的電荷儲存 —— 動態邏輯、開關層級模型。在 RTL 中基本上永遠不會用到。ノード上の電荷蓄積 —— ダイナミック論理やスイッチレベルのモデル。RTL ではまず使わない。 |
Declaration examples from the slides, with what each one means:取自投影片的宣告範例,以及各自的意思:スライドから取った宣言の例と、それぞれの意味。
wire x; // scalar net wand y; // wired-AND scalar net wire [15:0] data; // 16-bit vector, bit 15 is the MSB // data[5] : bit-select -> 1 bit // data[5:3] : part-select -> 3 bits, MSB = bit 5 wire [0:7] array_a; // ASCENDING range: bit 0 is the MSB // array_a[0:3] : 4 bits, MSB = bit 0 wire [7:0] array_2d_p [0:31]; // 32 nets, each 8 bits wide // array_2d_p[2] : an 8-bit vector wire a = b + c; // net declaration + implicit continuous assign
The last form is a convenience: wire a = b + c; is exactly wire a; assign a = b + c;. It is compact and perfectly synthesisable, and it is where a net delay would go if you wanted one: wire [7:0] #5 sum = b + c; — range first, delay second.最後一種形式是便利寫法:wire a = b + c; 完全等於 wire a; assign a = b + c;。它簡潔而且完全可合成,而且如果你要給連線延遲,位置就在那裡:wire [7:0] #5 sum = b + c; —— 範圍在前,延遲在後。最後の形式は便宜的な書き方である。wire a = b + c; は wire a; assign a = b + c; とまったく同じだ。簡潔で完全に合成可能であり、ネット遅延を付けたいならその位置がここである。wire [7:0] #5 sum = b + c; —— 範囲が先、遅延が後。
Both [15:0] and [0:15] are legal, and the rule is mechanical — the left index is always the MSB. But mixing conventions in one design guarantees an off-by-one bug the first time two buses are connected. Pick [msb:lsb] descending for everything numeric, so that bit n has weight $2^n$ and [7:0] reads like a byte. Reserve ascending ranges for things that are genuinely ordered left-to-right, such as a serial bit stream or a character array.[15:0] 與 [0:15] 都合法,規則是機械性的 —— 左邊的索引永遠是 MSB。但在同一個設計裡混用兩種慣例,保證會在第一次把兩條匯流排接起來時出現差一位的臭蟲。對所有數值性的東西一律挑 [msb:lsb] 遞減寫法,這樣第 n 位的權重就是 $2^n$,而 [7:0] 讀起來就像一個位元組。遞增範圍留給真正由左到右有序的東西,例如序列位元流或字元陣列。[15:0] も [0:15] も合法で、規則は機械的である —— 左の添字が常に MSB。しかし一つの設計で両方の流儀を混ぜれば、二本のバスをつないだ最初の瞬間に 1 ずれのバグが出ることは保証されている。数値的なものはすべて [msb:lsb] の降順にすること。そうすればビット n の重みは $2^n$ になり、[7:0] はバイトらしく読める。昇順の範囲は、シリアルビット列や文字配列のように本当に左から右へ順序づいたものに取っておく。
At $t_{sim} = 0$, before anything has been assigned:在 $t_{sim} = 0$、任何東西被指定之前:$t_{sim} = 0$ で、まだ何も代入されていない時点では:
x.有驅動源的連線 —— 基本元件輸出、連續指定、模組輸出埠 —— 取到該驅動源產生的值。由於驅動源自己的輸入也還是未知,這幾乎永遠是 x。ドライバを持つネット —— プリミティブの出力、連続代入、モジュールの出力ポート —— は、そのドライバが出す値を取る。ドライバ自身の入力もまだ不定なので、これはほぼ常に x である。z. Not x — nothing is driving it, which is a known condition.完全沒有驅動源的連線是 z。不是 x —— 沒有東西在驅動它,而那是一個已知的狀況。ドライバがまったく無いネットは z である。x ではない。何も駆動していないというのは既知の状態だからだ。x.暫存器是 x。レジスタは x である。That z-versus-x split is a genuinely useful diagnostic. If a DUT input shows as z in the waveform, the testbench forgot to connect or declare it. If it shows as x, it is connected to something that has not produced a value yet. Two different bugs, distinguished at a glance.z 與 x 的這個分界是一項真正好用的診斷。如果某個 DUT 輸入在波形上顯示為 z,就是測試平台忘了接它或忘了宣告它。如果顯示為 x,那它是接到了某個還沒產生出值的東西。兩個不同的臭蟲,一眼就能分辨。この z と x の違いは、実によく効く診断材料である。DUT の入力が波形で z と表示されるなら、テストベンチが接続か宣言を忘れている。x と表示されるなら、まだ値を出していない何かにつながっている。二つの異なるバグが一目で区別できる。
The second half of the slide is a hazard: an undeclared identifier used in a port connection or on the right-hand side of an expression is silently created as a 1-bit wire. This is a Verilog-1995 compatibility rule and it is a bug factory:投影片的後半是一個危害:在埠連接或運算式右手邊用到一個未宣告的識別字,會無聲地被建立成一條 1 位元的 wire。這是 Verilog-1995 的相容性規則,而它是個臭蟲工廠:スライドの後半は危険の話である。ポート接続や式の右辺で未宣言の識別子を使うと、黙って 1 ビットの wire が作られる。これは Verilog-1995 との互換性規則であり、バグ製造機である。
wire [7:0] data_bus; dut u1 (.d(data_buss)); // typo: creates a 1-bit implicit wire, no error, // the real bus is never connected, d reads z
Symptom: the DUT input sits at z or shows only one meaningful bit, with no warning anywhere. The cure is one line at the top of every file:症狀:DUT 輸入停在 z,或只顯示出一個有意義的位元,而且到處都沒有任何警告。解方是每個檔案開頭的一行:症状:DUT の入力が z のままになるか、意味のあるビットが 1 本しか出てこない。しかもどこにも警告は出ない。対策はどのファイルの先頭にも置く一行である。
`default_nettype none // now every net must be declared explicitly // ... your modules ... `default_nettype wire // restore before including third-party code
Turning this on converts a class of silent wrong-behaviour bugs into compile errors. Note the restore at the end: the directive is global and persists across file boundaries, so leaving it set to none will break any library file that relies on implicit nets.打開它,就能把一整類無聲的錯誤行為臭蟲轉成編譯錯誤。注意結尾的還原:這個指示子是全域的,而且會跨檔案持續生效,所以把它留在 none 狀態會弄壞任何依賴隱含連線的函式庫檔案。これを有効にすると、黙って挙動を誤らせる一群のバグがコンパイルエラーに変わる。末尾で元に戻していることに注意せよ。この指示子はグローバルでファイル境界を越えて持続するので、none のままにしておくと暗黙のネットに依存するライブラリファイルを壊してしまう。
A related note: an undeclared identifier used as a procedural assignment target is not implicitly created — it is an error, because the implicit object would be a net and a net cannot be assigned procedurally. The slide states this as “illegal within a behavior”.相關的一點:被當成程序性指定目標的未宣告識別字不會被隱含建立 —— 那是錯誤,因為隱含建立出來的物件會是 net,而 net 不能被程序性指定。投影片把這件事寫成「illegal within a behavior」。関連する注意点として、手続き代入の代入先に使われた未宣言の識別子は暗黙には作られない。これはエラーになる。暗黙に作られるオブジェクトはネットであり、ネットには手続き代入ができないからだ。スライドはこれを「illegal within a behavior」と述べている。
| Type型別型 | Stores儲存內容格納するもの | Notes註記注記 |
|---|---|---|
reg | Four-valued bits, any width四值位元,任意寬度四値のビット、幅は任意 | Unsigned by default. reg signed [7:0] x; in Verilog-2001 makes it signed.預設為無號。在 Verilog-2001 中 reg signed [7:0] x; 可讓它變成有號。既定は符号なし。Verilog-2001 では reg signed [7:0] x; と書けば符号つきになる。 |
integer | A signed integer, at least 32 bits, two’s complement一個有號整數,至少 32 位元,二補數符号つき整数。少なくとも 32 ビット、2 の補数表現 | Four-valued like reg. The idiomatic loop counter. Mixing it with reg in an expression changes the signedness of the whole expression — see §8.11.和 reg 一樣是四值的。慣用的迴圈計數器。把它和 reg 混在同一個運算式中會改變整個運算式的有號性 —— 見 §8.11。reg と同じく四値である。ループカウンタの定番。式の中で reg と混ぜると式全体の符号性が変わる —— §8.11 を参照。 |
real | IEEE-754 doubleIEEE-754 倍精度IEEE-754 倍精度 | Two-valued: it cannot hold x or z, and it initialises to 0.0, not x. Assigning a real to a reg rounds to the nearest integer.二值:它無法存放 x 或 z,而且初值是 0.0,不是 x。把 real 指定給 reg 會四捨五入到最近的整數。二値である。x も z も保持できず、初期値は x ではなく 0.0 である。real を reg に代入すると最近接の整数へ丸められる。 |
time | 64-bit unsigned64 位元無號64 ビット符号なし | Designed to hold the result of $time.設計用來存放 $time 的結果。$time の結果を保持するために設けられた型。 |
realtime | Same as real與 real 相同real と同じ | Holds $realtime. The separate name is documentation only.用來存放 $realtime。另立名稱純粹是為了文件可讀性。$realtime を保持するためのもの。名前が別なのは文書上の意味合いだけである。 |
reg a, b; // two scalar regs reg [15:0] counter; // one 16-bit reg reg [7:0] shift_reg [0:15]; // SIXTEEN 8-bit regs: an array, not a 128-bit value integer c; // signed, >= 32 bits
The difference between lines 2 and 3 is the one to be sure of. reg [15:0] counter is a single object sixteen bits wide: you can add to it, shift it, pass it as a port. reg [7:0] shift_reg [0:15] is sixteen separate eight-bit objects: you can index it, and that is almost all you can do with it. §6.8 spells out the consequences.第 2 列與第 3 列的差別是必須弄清楚的。reg [15:0] counter 是一個十六位元寬的單一物件:你可以對它做加法、位移,把它當埠傳遞。reg [7:0] shift_reg [0:15] 則是十六個分開的八位元物件:你可以對它做索引,而你能做的也差不多就只有這樣。§6.8 會把後果講清楚。2 行目と 3 行目の違いは必ず押さえておくこと。reg [15:0] counter は幅 16 ビットの単一オブジェクトであり、加算もシフトもでき、ポートとして渡せる。reg [7:0] shift_reg [0:15] は 8 ビットのオブジェクト 16 個であり、添字で選べるだけで、できることはほぼそれだけである。その帰結は §6.8 で詳しく述べる。
The rules, from the slide, with the reasoning:取自投影片的規則,附上理由:スライドから取った規則と、その理由。
v[m:n], the left index is the MSB of the result and the right index is the LSB. This holds regardless of whether the declared range is ascending or descending — but the part-select’s direction must match the declaration, so for reg [7:0] v you may write v[5:1] and not v[1:5].在部分選擇 v[m:n] 中,左索引是結果的 MSB,右索引是 LSB。無論宣告範圍是遞增或遞減都成立 —— 但部分選擇的方向必須與宣告一致,所以對 reg [7:0] v 你可以寫 v[5:1],不能寫 v[1:5]。部分選択 v[m:n] では、左の添字が結果の MSB、右が LSB である。これは宣言された範囲が昇順でも降順でも成り立つ —— ただし部分選択の向きは宣言と一致していなければならない。reg [7:0] v に対して v[5:1] は書けるが v[1:5] は書けない。v[base +: width] and v[base -: width], whose width is constant even though the base is not. This is the construct to reach for when you want a variable byte lane out of a wide bus.在 Verilog-1995 中,部分選擇的索引必須是常數運算式。變數索引只能用於單一位元選擇,或透過 Verilog-2001 的索引式部分選擇 v[base +: width] 與 v[base -: width],其中 width 是常數,base 則不必是。當你想從一條寬匯流排取出可變的位元組通道時,就該伸手拿這個語法。Verilog-1995 では部分選択の添字は定数式でなければならない。変数の添字が使えるのは単一ビット選択か、Verilog-2001 のインデックス付き部分選択 v[base +: width] と v[base -: width] を経由する場合だけである。後者は base が変数でも width は定数である。広いバスから可変のバイトレーンを取り出したいときに使う構文がこれだ。x rather than raising an error, and a write to an out-of-bounds index is silently discarded.越界索引回傳 x,而不是引發錯誤;對越界索引的寫入則被無聲丟棄。範囲外の添字はエラーにならず x を返す。範囲外の添字への書き込みは黙って捨てられる。Worked from the slide, with word[7:0] = 8'b1100_0100:依投影片演練,取 word[7:0] = 8'b1100_0100:スライドに沿って、word[7:0] = 8'b1100_0100 として計算する。
| Expression運算式式 | Value值値 | Reasoning理由理由 |
|---|---|---|
word[3:0] | 4'b0100 | The low nibble.低四位元。下位 4 ビット。 |
word[5:1] | 5'b00010 | Bits 5,4,3,2,1 = 0,0,0,1,0. Five bits, not four.位元 5,4,3,2,1 = 0,0,0,1,0。五個位元,不是四個。ビット 5,4,3,2,1 = 0,0,0,1,0。4 ビットではなく5 ビットである。 |
word[7:5] | 3'b110 | Bits 7,6,5 = 1,1,0.位元 7,6,5 = 1,1,0。ビット 7,6,5 = 1,1,0。 |
word[9:4] | illegal / x不合法/x不正/x | Bits 9 and 8 do not exist. A constant out-of-range select is usually a compile error; a variable one returns x at run time.位元 9 與 8 不存在。常數的越界選擇通常是編譯錯誤;變數的越界選擇在執行期回傳 x。ビット 9 と 8 は存在しない。定数による範囲外選択はたいていコンパイルエラーになり、変数によるものは実行時に x を返す。 |
Slide 23 gives word[5:1] = 4'b0010. The bits are right but the width is wrong: a part-select from index 5 down to index 1 spans $5 - 1 + 1 = 5$ bits, so the correct answer is 5'b00010. The general formula for the width of v[m:n] in a descending range is $m - n + 1$. The distinction is not pedantry — it determines zero-padding and the result width of any expression the select feeds, which §8.11 shows can silently change an answer.第 23 頁投影片寫 word[5:1] = 4'b0010。位元是對的,但寬度錯了:從索引 5 到索引 1 的部分選擇跨了 $5 - 1 + 1 = 5$ 個位元,所以正確答案是 5'b00010。在遞減範圍中,v[m:n] 寬度的通式是 $m - n + 1$。這個區別不是吹毛求疵 —— 它決定補零方式,以及任何吃進這個選擇的運算式的結果寬度,而 §8.11 會說明那可以無聲地改掉答案。スライド 23 枚目には word[5:1] = 4'b0010 とある。ビットは正しいが幅が誤っている。添字 5 から添字 1 までの部分選択は $5 - 1 + 1 = 5$ ビットにまたがるので、正しくは 5'b00010 である。降順の範囲における v[m:n] の幅の一般式は $m - n + 1$ である。この区別は細かすぎる話ではない。ゼロ拡張の仕方と、その選択を受け取る式の結果幅を決めるからであり、§8.11 で見るようにそれは黙って答えを変えうる。
A memory in Verilog-1995 is simply an array of regs:在 Verilog-1995 中,記憶體就只是一個 reg 的陣列:Verilog-1995 におけるメモリは、単に reg の配列である。
reg [31:0] cache [0:1023]; // 1024 words of 32 bits = 1 K x 32 reg [31:0] one_word; reg one_bit; one_word = cache[1022]; // read a whole word one_bit = one_word[31]; // then select a bit from it
Read the declaration carefully, because the two ranges mean different things and sit on different sides of the name. The range before the identifier is the word width; the range after it is the address space. reg [31:0] cache [0:1023] is a thousand-and-twenty-four-entry array of thirty-two-bit words, and reversing the two gives you a completely different object.仔細讀這個宣告,因為兩個範圍意思不同,而且分別落在名稱的兩側。識別字前面的範圍是字寬;後面的範圍是位址空間。reg [31:0] cache [0:1023] 是一個有一千零二十四個項目、每項三十二位元的陣列,而把兩者對調會得到一個完全不同的物件。この宣言は注意して読むこと。二つの範囲は意味が異なり、名前の反対側に置かれている。識別子の前の範囲は語長、後の範囲はアドレス空間である。reg [31:0] cache [0:1023] は 32 ビット語を 1024 個持つ配列であり、二つを入れ替えるとまったく別のオブジェクトになる。
The restriction the slide highlights is that individual bits of a memory cannot be addressed directly in Verilog-1995: cache[1022][31] is a syntax error, and you must copy the word into a temporary first, as above. IEEE 1364-2001 relaxed this and permits a bit-select or part-select of an array element, so cache[1022][31] and cache[1022][15:8] are legal in any modern tool. Both forms appear in real code; the two-step version is still worth knowing because it is what the slide shows and what older code does.投影片強調的限制是,在 Verilog-1995 中無法直接定址記憶體的個別位元:cache[1022][31] 是語法錯誤,你必須像上面那樣先把整個字複製到一個暫時變數。IEEE 1364-2001 放寬了這點,允許對陣列元素做位元選擇或部分選擇,所以 cache[1022][31] 與 cache[1022][15:8] 在任何現代工具上都合法。真實程式碼裡兩種寫法都看得到;兩步驟的版本仍值得知道,因為投影片就是那樣寫,舊程式碼也是那樣寫。スライドが強調する制限は、Verilog-1995 ではメモリの個々のビットを直接アドレスできないことである。cache[1022][31] は構文エラーで、上のようにいったん語を一時変数へコピーしなければならない。IEEE 1364-2001 はこれを緩め、配列要素のビット選択・部分選択を許したので、cache[1022][31] も cache[1022][15:8] も現代のツールでは合法である。実際のコードには両方の書き方が現れる。二段階の版も、スライドがそう書いており古いコードもそうしている以上、知っておく価値がある。
Other limitations that are still true in 2001 and catch people out:其他在 2001 版仍然成立、而且常坑人的限制:2001 年版でも依然として成り立ち、人をはめる他の制限。
if (cache == 0) is meaningless. Only an element is an operand.記憶體不能整個當成運算式中的運算元:if (cache == 0) 毫無意義。只有單一元素才是運算元。メモリは全体を式の中で使えない。if (cache == 0) は無意味である。オペランドになれるのは要素だけである。for loop, or with $readmemb / $readmemh, which load a text file of binary or hex values into an array and are the standard way to preload a ROM or a golden-vector table: $readmemh("golden.hex", expected); not synthesisable for ASIC, though many FPGA flows honour it for block-RAM initialisation.你無法一次指定整個陣列。請用 for 迴圈初始化,或用 $readmemb / $readmemh,它們會把一個裝著二進位或十六進位值的文字檔載進陣列,是預載 ROM 或黃金向量表的標準作法:$readmemh("golden.hex", expected); 對 ASIC 而言not synthesisable,不過許多 FPGA 流程會尊重它來做 block-RAM 的初始化。配列全体を一度に代入することはできない。for ループで初期化するか、$readmemb / $readmemh を使う。これらは二進または十六進の値を並べたテキストファイルを配列に読み込むもので、ROM やゴールデンベクタ表をプリロードする標準的な方法である。$readmemh("golden.hex", expected); ASIC ではnot synthesisableだが、多くの FPGA フローはブロック RAM の初期化のためにこれを尊重する。wire [7:0] bus_array [0:31];) but far rarer; the slide’s array_2d_p is an example.net 的陣列也是合法的(wire [7:0] bus_array [0:31];),但罕見得多;投影片的 array_2d_p 就是一例。ネットの配列も合法だが(wire [7:0] bus_array [0:31];)はるかに珍しい。スライドの array_2d_p がその例である。Finally, a scale warning. A behavioural array costs the simulator a full four-valued word per entry — on the order of eight bytes per 32-bit word plus overhead. A 1 K×32 cache is trivial; a 1 G×64 main-memory model is not, and this is why testbenches for processors model memory sparsely with an associative structure rather than a dense array. For the DUT side, a memory this size would never be synthesised from reg anyway — it would be a compiled SRAM macro instantiated from the foundry memory compiler, with the behavioural array used only in the simulation model.最後是一個關於規模的警告。行為式陣列每一項都要花掉模擬器一個完整的四值字 —— 一個 32 位元字大約八個位元組再加上額外開銷。1 K×32 的快取不算什麼;1 G×64 的主記憶體模型就很可觀了,這也是為什麼處理器的測試平台會用關聯式結構稀疏地模擬記憶體,而不是用密集陣列。至於 DUT 那一側,這種規模的記憶體本來也絕不會由 reg 合成出來 —— 它會是由晶圓廠記憶體編譯器產生、再實例化進來的 SRAM 巨集,行為式陣列只用在模擬模型裡。最後に規模についての警告。振る舞いレベルの配列は、1 要素あたり完全な四値の語をシミュレータに消費させる。32 ビット語ならおよそ 8 バイトに加えてオーバーヘッドがかかる。1 K×32 のキャッシュは取るに足らないが、1 G×64 の主記憶モデルはそうではない。プロセッサのテストベンチが密な配列ではなく連想的な構造でメモリを疎にモデル化するのはこのためである。DUT 側については、この規模のメモリがそもそも reg から合成されることはない。ファウンドリのメモリコンパイラが生成した SRAM マクロをインスタンス化し、振る舞い配列はシミュレーションモデルの中だけで使う。
Problem: State whether each is legal, and why. (a) reg r; assign r = a & b; (b) wire w; always @(a) w = a; (c) reg r; and (r, a, b); (d) reg r; always @(a or b) r = a & b; (e) in a testbench, reg x; DUT u1 (.in(x)); (f) inside the DUT, output y; reg y; (g) inside the DUT, input c; reg c; (h) reg [7:0] m [0:3]; wire [7:0] o; assign o = m[2]; (i) reg [7:0] m [0:3]; dut u2 (.mem(m));問題:說明以下各項是否合法,並說明理由。(a) reg r; assign r = a & b; (b) wire w; always @(a) w = a; (c) reg r; and (r, a, b); (d) reg r; always @(a or b) r = a & b; (e) 在測試平台中,reg x; DUT u1 (.in(x)); (f) 在 DUT 內部,output y; reg y; (g) 在 DUT 內部,input c; reg c; (h) reg [7:0] m [0:3]; wire [7:0] o; assign o = m[2]; (i) reg [7:0] m [0:3]; dut u2 (.mem(m));問題:次の各項が合法か否かと、その理由を述べよ。(a) reg r; assign r = a & b; (b) wire w; always @(a) w = a; (c) reg r; and (r, a, b); (d) reg r; always @(a or b) r = a & b; (e) テストベンチ内で reg x; DUT u1 (.in(x)); (f) DUT 内部で output y; reg y; (g) DUT 内部で input c; reg c; (h) reg [7:0] m [0:3]; wire [7:0] o; assign o = m[2]; (i) reg [7:0] m [0:3]; dut u2 (.mem(m));
(a) Illegal — a reg may never be the target of a continuous assignment. (b) Illegal — a net may never be a procedural target. (c) Illegal — a reg may never be a primitive gate’s output. (d) Legal — procedural assignment to a reg; this infers combinational logic, not a flip-flop, although the sensitivity list should be @(*). (e) Legal — the actual connected to an input port may be a register; this is exactly how a testbench drives a DUT. (f) Legal — the formal of an output port may be a register, which is how you assign an output in an always block. (g) Illegal — the formal of an input port must be a net; an input is driven continuously from outside. (h) Legal — a single array element is a perfectly ordinary 8-bit operand. (i) Illegal — a memory cannot be passed through a port.(a) 不合法 —— reg 絕不能是連續指定的目標。(b) 不合法 —— net 絕不能是程序性指定的目標。(c) 不合法 —— reg 絕不能是基本閘的輸出。(d) 合法 —— 對 reg 的程序性指定;這會推論出組合邏輯而不是正反器,不過敏感度列表應該寫成 @(*)。(e) 合法 —— 接到 input 埠的實際參數可以是 register;測試平台驅動 DUT 的方式正是如此。(f) 合法 —— output 埠的形式參數可以是 register,你就是這樣在 always 區塊中指定輸出的。(g) 不合法 —— input 埠的形式參數必須是 net;輸入是從外面持續被驅動的。(h) 合法 —— 單一陣列元素就是一個再普通不過的 8 位元運算元。(i) 不合法 —— 記憶體不能透過埠傳遞。(a) 不正 —— reg は連続代入の代入先になれない。(b) 不正 —— ネットは手続き代入の代入先になれない。(c) 不正 —— reg はプリミティブゲートの出力になれない。(d) 合法 —— reg への手続き代入である。これはフリップフロップではなく組合せ論理を推論する。ただし感度リストは @(*) と書くべきである。(e) 合法 —— input ポートにつなぐ実引数はレジスタでよい。テストベンチが DUT を駆動するのはまさにこの形である。(f) 合法 —— output ポートの仮引数はレジスタでよく、always ブロックで出力に代入するのはこの形である。(g) 不正 —— input ポートの仮引数はネットでなければならない。入力は外から継続的に駆動される。(h) 合法 —— 配列要素ひとつはごく普通の 8 ビットオペランドである。(i) 不正 —— メモリはポート経由で渡せない。
Collapse all nine into one sentence: continuous things drive nets, procedural things drive registers, and arrays are not first-class objects.把這九題壓縮成一句話:持續性的東西驅動 net,程序性的東西驅動 register,而陣列不是一等公民。九問を一文に圧縮するとこうなる。継続的なものはネットを駆動し、手続き的なものはレジスタを駆動する。そして配列は第一級のオブジェクトではない。
Problem: reg [0:7] v = 8'b1010_0011; — note the ascending range. Give (a) v[0] (b) v[0:3] (c) v[6:7] (d) the width of v[2:6]. (e) With reg [7:0] mem [0:3]; how do you read bit 2 of word 1 in strict Verilog-1995, and in Verilog-2001? (f) What is v[8]?問題:reg [0:7] v = 8'b1010_0011; —— 注意這是遞增範圍。求 (a) v[0] (b) v[0:3] (c) v[6:7] (d) v[2:6] 的寬度。(e) 給定 reg [7:0] mem [0:3];,在嚴格的 Verilog-1995 與在 Verilog-2001 中,各要怎麼讀取第 1 個字的第 2 位元?(f) v[8] 是多少?問題:reg [0:7] v = 8'b1010_0011; —— 範囲が昇順であることに注意せよ。(a) v[0] (b) v[0:3] (c) v[6:7] (d) v[2:6] の幅を求めよ。(e) reg [7:0] mem [0:3]; があるとき、厳密な Verilog-1995 と Verilog-2001 で、語 1 のビット 2 をどう読むか。(f) v[8] はいくつか。
With an ascending range [0:7] the left index is still the MSB, so index 0 holds the leading 1 of 1010_0011 and index 7 holds the trailing 1.在遞增範圍 [0:7] 下,左索引仍然是 MSB,所以索引 0 存著 1010_0011 開頭的那個 1,索引 7 存著結尾的那個 1。昇順の範囲 [0:7] でも左の添字が MSB のままなので、添字 0 は 1010_0011 の先頭の 1 を、添字 7 は末尾の 1 を保持する。
(a) 1. (b) 4'b1010 — the first four bits. (c) 2'b11 — the last two. (d) $6 - 2 + 1 = 5$ bits, value 5'b10001. (e) 1995: tmp = mem[1]; bit = tmp[2]; with reg [7:0] tmp;. 2001: bit = mem[1][2]; directly. (f) Out of range → x (a constant out-of-range select is normally flagged at compile time; the run-time answer is x).(a) 1。(b) 4'b1010 —— 前四個位元。(c) 2'b11 —— 最後兩個。(d) $6 - 2 + 1 = 5$ 個位元,值為 5'b10001。(e) 1995:tmp = mem[1]; bit = tmp[2];,其中 reg [7:0] tmp;。2001:直接寫 bit = mem[1][2];。(f) 越界 → x(常數的越界選擇通常在編譯期就會被標出來;執行期的答案是 x)。(a) 1。(b) 4'b1010 —— 先頭 4 ビット。(c) 2'b11 —— 末尾 2 ビット。(d) $6 - 2 + 1 = 5$ ビットで、値は 5'b10001。(e) 1995:reg [7:0] tmp; を用意して tmp = mem[1]; bit = tmp[2];。2001:bit = mem[1][2]; と直接書ける。(f) 範囲外 → x(定数による範囲外選択は通常コンパイル時に指摘される。実行時の答えは x である)。
The trap is reading v[6:7] as backwards. It is not: for an ascending declaration the select must also ascend, and v[7:6] would be the illegal one.陷阱在於把 v[6:7] 讀成寫反了。它沒有寫反:對遞增宣告而言,選擇也必須遞增,反而是 v[7:6] 才不合法。罠は v[6:7] を逆向きだと読んでしまうことである。逆ではない。昇順の宣言に対しては選択も昇順でなければならず、不正なのはむしろ v[7:6] のほうである。
A parameter is a named constant whose value is fixed at elaboration — after parsing, before simulation starts — and which may be given a different value for each instance of the module that declares it.parameter 是一個具名常數,其值在展開期(elaboration)確定 —— 在剖析之後、模擬開始之前 —— 而且可以為宣告它的那個模組的每個實例給不同的值。parameter は名前付きの定数であり、その値はエラボレーション時、すなわち構文解析の後、シミュレーション開始の前に確定する。そして、それを宣言したモジュールのインスタンスごとに別の値を与えられる。
parameter size = 16; reg [size-1:0] a; // width follows the parameter parameter num_b = 2'b01; // sized constant parameter av_delay = (min_delay + max_delay) / 2; // computed from other parameters
The slide’s three motivations are worth expanding, because the third is the one that changes how you work:投影片列的三個動機值得展開,因為第三個會改變你的工作方式:スライドが挙げる三つの動機は展開する価値がある。三つ目は働き方そのものを変えるからだ。
if (state == IDLE) says something; if (state == 3'b000) says nothing. State encodings, opcode values, FIFO depths and address map boundaries should all be named.可讀性。if (state == IDLE) 說了一件事;if (state == 3'b000) 什麼也沒說。狀態編碼、opcode 值、FIFO 深度與位址對映邊界,全都應該取名字。可読性。if (state == IDLE) は何かを語るが、if (state == 3'b000) は何も語らない。状態の符号化、オペコードの値、FIFO の深さ、アドレスマップの境界は、すべて名前を付けるべきである。Verilog-2001 added localparam, which behaves identically but cannot be overridden. Use it for anything derived rather than chosen — localparam ADDR_W = $clog2(DEPTH); — so that an override of DEPTH cannot leave ADDR_W inconsistent. Treat parameter as the module’s public interface and localparam as its private constants.Verilog-2001 加入了 localparam,行為完全相同,但不能被覆寫。凡是衍生出來、而非由人挑選的量都該用它 —— localparam ADDR_W = $clog2(DEPTH); —— 這樣覆寫 DEPTH 就不會讓 ADDR_W 變得不一致。把 parameter 當成模組的公開介面,把 localparam 當成它的私有常數。Verilog-2001 は localparam を追加した。挙動は同じだがオーバーライドできない。選ぶものではなく導かれるものにはこちらを使う —— localparam ADDR_W = $clog2(DEPTH); —— そうすれば DEPTH のオーバーライドが ADDR_W を不整合のまま残すことはない。parameter をモジュールの公開インタフェース、localparam をその私的な定数と考えるとよい。
#( )7.2 作法 I —— 用 #( ) 直接替換7.2 方法 I —— #( ) による直接置換The parameter values are supplied at the point of instantiation, in the instantiating module.參數值在實例化的位置、在實例化它的那個模組裡提供。パラメータ値は、インスタンス化する側のモジュールの、インスタンス化のその場で与える。
module modXnor (y_out, a, b); parameter size = 8, delay = 15; output [size-1:0] y_out; input [size-1:0] a, b; wire [size-1:0] #delay y_out = a ~^ b; // bitwise xnor, net delay = delay endmodule module Param; wire [7:0] y1_out; wire [3:0] y2_out; reg [7:0] b1, c1; reg [3:0] b2, c2; modXnor G1 (y1_out, b1, c1); // defaults: size = 8, delay = 15 modXnor #(4, 5) G2 (y2_out, b2, c2); // override: size = 4, delay = 5 endmodule
Three mechanical facts about #(4, 5):關於 #(4, 5) 的三個機械性事實:#(4, 5) についての機械的な事実が三つある。
size is declared first, so it gets 4.這些值是依宣告順序對應到參數的,和位置式埠連接完全一樣。size 宣告在前,所以它拿到 4。値は宣言順にパラメータへ対応づけられる。位置によるポート接続とまったく同じである。size が先に宣言されているので 4 を受け取る。Verilog-2001 added named parameter override, which removes both hazards and is what you should write:Verilog-2001 加入了具名參數覆寫,它消除了上述兩個危害,也是你該寫的形式:Verilog-2001 は名前付きパラメータ・オーバーライドを追加した。上の二つの危険をともに取り除くもので、書くべきはこちらである。
modXnor #(.size(4), .delay(5)) G2 (.y_out(y2_out), .a(b2), .b(c2));
Named overrides are order-independent, self-documenting, and skipping a parameter is trivial. Combined with named port connections, as above, an instantiation becomes immune to someone reordering the module’s declarations — which is worth a great deal when the module is maintained by somebody else.具名覆寫與順序無關、自我說明,而且要跳過某個參數也很容易。再如上面那樣搭配具名埠連接,實例化就對「有人重排了模組宣告順序」免疫 —— 當那個模組由別人維護時,這一點的價值非常高。名前付きオーバーライドは順序に依存せず、自己文書的であり、パラメータを飛ばすのも容易である。上のように名前付きポート接続と組み合わせれば、インスタンス化は「誰かがモジュールの宣言順を並べ替えた」ことに対して免疫を持つ。そのモジュールを他人が保守しているとき、この価値は非常に大きい。
Notice how the parameter reaches into the declarations: output [size-1:0] y_out; means the port width itself depends on the override. G1’s y_out is eight bits and G2’s is four, from the same source text. That is the whole point, and it is why parameters must be resolved at elaboration: the shape of the netlist depends on them.注意參數如何伸進宣告裡:output [size-1:0] y_out; 意味著埠寬度本身取決於覆寫值。G1 的 y_out 是八位元,G2 的是四位元,而兩者出自同一份原始碼。這正是重點所在,也是為什麼參數必須在展開期就解析完畢:網表的形狀取決於它們。パラメータが宣言の中にまで及ぶことに注目せよ。output [size-1:0] y_out; はポート幅そのものがオーバーライドに依存することを意味する。同一のソーステキストから、G1 の y_out は 8 ビット、G2 のそれは 4 ビットになる。これこそが要点であり、パラメータがエラボレーション時に解決されなければならない理由でもある。ネットリストの形がそれに依存するからだ。
The slide notes it in passing and it deserves emphasis: a module instantiation may not have a delay associated with it. # before an instance name therefore always means parameter override. This is why mymod #5 U1 (...) does not delay anything — it overrides the first parameter with 5, and if the module has no parameters it is an error, while if it has one with a different meaning you have silently mis-elaborated your design. Primitive gates are the opposite: nand #1 G1 (...) is a delay, because parameters may not be associated with a primitive gate. And a UDP declaration may not contain parameter declarations at all, though a UDP instance may carry a delay.投影片順口提了一句,而它值得強調:模組實例化不可以帶延遲。因此實例名稱前面的 # 永遠是參數覆寫。這就是為什麼 mymod #5 U1 (...) 並不會延遲任何東西 —— 它是把第一個參數覆寫成 5,而如果該模組根本沒有參數,那是錯誤;如果它有一個意義完全不同的參數,那你就無聲地把設計展開錯了。基本閘則相反:nand #1 G1 (...) 是延遲,因為基本閘不可以搭配參數。而 UDP 宣告根本不能包含參數宣告,不過 UDP 的實例可以帶延遲。スライドはついでに触れているだけだが、強調に値する。モジュールのインスタンス化に遅延を付けることはできない。したがってインスタンス名の前の # は常にパラメータのオーバーライドである。mymod #5 U1 (...) が何も遅らせないのはこのためだ。これは最初のパラメータを 5 で上書きする。モジュールにパラメータが無ければエラーになり、意味の異なるパラメータが一つあれば、設計は黙って誤ってエラボレートされる。プリミティブゲートは逆である。nand #1 G1 (...) は遅延である。プリミティブゲートにパラメータは付けられないからだ。また UDP の宣言にはパラメータ宣言を一切置けないが、UDP のインスタンスは遅延を持てる。
defparam7.3 作法 II —— 用 defparam 間接替換7.3 方法 II —— defparam による間接置換The alternative keeps the instantiation clean and puts the overrides somewhere else entirely, addressing each parameter by its hierarchical path name.另一種作法讓實例化保持乾淨,把覆寫完全放到別的地方,並以階層式路徑名稱指名每一個參數。もう一つの方法はインスタンス化を綺麗なまま保ち、オーバーライドをまったく別の場所に置いて、各パラメータを階層パス名で指す。
module hdref_Param; // a top-level module wire [7:0] y1_out; wire [3:0] y2_out; reg [7:0] b1, c1; reg [3:0] b2, c2; modXnor G1 (y1_out, b1, c1), G2 (y2_out, b2, c2); // no overrides written here endmodule module annotate; // a separate annotation module defparam hdref_Param.G2.size = 4, // hierarchical reference name hdref_Param.G2.delay = 5; endmodule
The appeal is obvious: every configuration choice for a whole design can be collected in one file, and the design source stays generic. Historically this is how timing annotation and configuration management were done before SDF and before Verilog-2001 configurations existed.它的吸引力很明顯:整個設計的每一項組態選擇都能集中在同一個檔案裡,而設計原始碼維持泛用。在歷史上,這正是 SDF 出現之前、Verilog-2001 configuration 出現之前,時序標註與組態管理的作法。魅力は明らかだ。設計全体の構成上の選択を一つのファイルに集められ、設計ソースは汎用のままでいられる。歴史的には、SDF 以前、Verilog-2001 の configuration 以前には、タイミング注釈と構成管理をこうやって行っていた。
The appeal is also a trap, and modern practice is firmly against it:但這份吸引力同時是個陷阱,現代實務堅決反對它:しかしその魅力は同時に罠でもあり、現代の実務はこれに断固として反対している。
defparam, or accept it only with restrictions. Anything you cannot synthesise cannot be in the DUT.合成工具一般會拒絕 defparam,或只在有限制的情況下接受。任何不能合成的東西都不該出現在 DUT 裡。合成ツールは一般に defparam を拒否するか、制限付きでしか受け付けない。合成できないものは DUT に入れてはならない。defparams in different files target the same parameter, IEEE 1364 does not define which wins; the answer depends on compilation order.它以一種沒人看得見的方式對順序敏感。如果兩個不同檔案中的 defparam 指向同一個參數,IEEE 1364 並未定義誰勝出;答案取決於編譯順序。誰にも見えない形で順序に依存する。異なるファイルの二つの defparam が同じパラメータを指した場合、どちらが勝つかを IEEE 1364 は定義していない。答えはコンパイル順に依存する。Know it, recognise it in legacy code, be able to state what it does in an exam — and write #(.name(value)) in anything you ship.認識它、能在舊程式碼裡認出它、考試時說得出它的作用 —— 然後在你要交付的東西裡一律寫 #(.name(value))。知っておき、レガシーコードの中で見分けられ、試験でその働きを説明できるようにしておくこと —— そして出荷するものには必ず #(.name(value)) と書くこと。
| Pitfall陷阱落とし穴 | What happens會發生什麼何が起きるか |
|---|---|
| Overriding a width parameter but not the connected net覆寫了寬度參數,卻沒改接上去的連線幅のパラメータは上書きしたのに、つなぐネットを直していない | modXnor #(.size(4)) G (y2_out, ...) with wire [7:0] y2_out: the port is 4 bits, the net 8. Verilog zero-pads or truncates silently; the upper four bits of y2_out stay z and nothing warns you.modXnor #(.size(4)) G (y2_out, ...) 搭配 wire [7:0] y2_out:埠是 4 位元,連線是 8 位元。Verilog 會無聲地補零或截斷;y2_out 的高四位元停在 z,而且沒有任何警告。wire [7:0] y2_out に対して modXnor #(.size(4)) G (y2_out, ...)。ポートは 4 ビット、ネットは 8 ビットである。Verilog は黙ってゼロ拡張か切り捨てを行う。y2_out の上位 4 ビットは z のまま残り、警告は一切出ない。 |
| Using a parameter before it is declared在參數被宣告之前就使用它宣言される前にパラメータを使う | Parameters are elaborated in order within a module; parameter av = (min + max)/2; requires min and max to be already-declared parameters, not plain identifiers.參數在模組內是依序展開的;parameter av = (min + max)/2; 要求 min 與 max 是已經宣告過的參數,而不是普通的識別字。パラメータはモジュール内で宣言順にエラボレートされる。parameter av = (min + max)/2; は min と max がすでに宣言済みのパラメータであることを要求する。ただの識別子では駄目である。 |
| Expecting a parameter to change during simulation期待參數在模擬過程中改變シミュレーション中にパラメータが変わると期待する | It cannot. The slide is explicit: the value may not be changed during simulation, only during compilation/elaboration. If you need a run-time constant, you need a register loaded once from a configuration bus.辦不到。投影片說得很明白:參數值不可在模擬期間改變,只能在編譯/展開期改變。如果你需要一個執行期常數,那你需要的是一個由組態匯流排載入一次的暫存器。できない。スライドは明言している。パラメータ値はシミュレーション中には変更できず、コンパイル/エラボレーション時にのみ変更できる。実行時の定数が要るなら、構成バスから一度だけロードされるレジスタが必要である。 |
| Relying on a parameter’s implicit width依賴參數的隱含寬度パラメータの暗黙の幅に依存する | An unsized parameter n = 5; takes the width of its value in context and can surprise you in a concatenation. Write parameter [7:0] n = 8'd5; if the width matters.未指定寬度的 parameter n = 5; 會依上下文取其值的寬度,在串接中可能讓你大吃一驚。如果寬度要緊,就寫 parameter [7:0] n = 8'd5;。幅を指定しない parameter n = 5; は文脈に応じてその値の幅を取るので、連結の中で思わぬ結果になりうる。幅が重要なら parameter [7:0] n = 8'd5; と書くこと。 |
Problem: Using the modXnor / Param code above: (a) What are the width and delay of G1 and of G2? (b) If b2 = 4'b0110 and c2 = 4'b0101 are applied at $t = 0$, when and to what does y2_out change? (c) Rewrite the G2 instantiation using the Verilog-2001 named form, overriding only delay. (d) Write the defparam that would achieve the same thing, and say why you would not use it.問題:使用上面的 modXnor / Param 程式碼:(a) G1 與 G2 的寬度與延遲各是多少?(b) 如果在 $t = 0$ 施加 b2 = 4'b0110 與 c2 = 4'b0101,y2_out 在什麼時候變成什麼?(c) 用 Verilog-2001 的具名形式重寫 G2 的實例化,只覆寫 delay。(d) 寫出能達到同樣效果的 defparam,並說明你為什麼不會用它。問題:上の modXnor / Param のコードを用いる。(a) G1 と G2 の幅と遅延はそれぞれいくつか。(b) $t = 0$ で b2 = 4'b0110 と c2 = 4'b0101 を与えたとき、y2_out はいつ、何に変化するか。(c) Verilog-2001 の名前付き形式で G2 のインスタンス化を書き直し、delay だけをオーバーライドせよ。(d) 同じ効果を得る defparam を書き、なぜそれを使わないのかを述べよ。
(a) G1 takes the defaults: 8 bits wide, net delay 15. G2 is overridden positionally, so size = 4 and delay = 5: 4 bits wide, delay 5.(a) G1 取預設值:寬 8 位元,連線延遲 15。G2 以位置式覆寫,所以 size = 4、delay = 5:寬 4 位元,延遲 5。(a) G1 は既定値を取る。幅 8 ビット、ネット遅延 15。G2 は位置指定でオーバーライドされているので size = 4、delay = 5、すなわち幅 4 ビット、遅延 5 である。
(b) ~^ is bitwise XNOR, which is 1 where the bits agree. 0110 ~^ 0101: bit 3 0↔0 → 1; bit 2 1↔1 → 1; bit 1 1↔0 → 0; bit 0 0↔1 → 0. So the value is 4'b1100, and with a net delay of 5 it appears at $t = 5$, replacing the initial x. (Before that, the net is 4'bxxxx: it is driven, so it takes its driver’s value, and the driver has not produced one yet.)(b) ~^ 是逐位元 XNOR,兩位元相同時為 1。0110 ~^ 0101:位元 3 0↔0 → 1;位元 2 1↔1 → 1;位元 1 1↔0 → 0;位元 0 0↔1 → 0。所以值是 4'b1100,而在連線延遲 5 之下,它在 $t = 5$ 出現,取代原本的 x。(在那之前連線是 4'bxxxx:它有驅動源,所以取驅動源的值,而驅動源還沒產生出值來。)(b) ~^ はビットごとの XNOR で、ビットが一致するところが 1 になる。0110 ~^ 0101:ビット 3 は 0↔0 → 1、ビット 2 は 1↔1 → 1、ビット 1 は 1↔0 → 0、ビット 0 は 0↔1 → 0。よって値は 4'b1100 であり、ネット遅延 5 により $t = 5$ で現れ、それまでの x を置き換える。(それ以前はネットは 4'bxxxx である。駆動されているのでドライバの値を取るが、ドライバがまだ値を出していないからだ。)
(c) modXnor #(.delay(5)) G2 (.y_out(y2_out), .a(b2), .b(c2)); — note that size now stays at its default 8, so y2_out would need to be redeclared 8 bits wide, or you override both. This is exactly the pitfall in the table above: named overrides make it easy to change one parameter and forget its consequences.(c) modXnor #(.delay(5)) G2 (.y_out(y2_out), .a(b2), .b(c2)); —— 注意 size 現在停在預設值 8,所以 y2_out 得改宣告成 8 位元寬,不然就兩個參數都覆寫。這正是上表中的那個陷阱:具名覆寫讓你很容易改了一個參數卻忘了它的後果。(c) modXnor #(.delay(5)) G2 (.y_out(y2_out), .a(b2), .b(c2)); —— ここで size は既定の 8 のままになるので、y2_out を 8 ビット幅に宣言し直すか、両方のパラメータをオーバーライドする必要がある。これはまさに上の表の落とし穴である。名前付きオーバーライドは、一つのパラメータだけ変えてその帰結を忘れることを容易にする。
(d) defparam Param.G2.size = 4, Param.G2.delay = 5; placed in any module. You would not use it because synthesis rejects it, the override is invisible from the instantiation, and the result depends on file order if anyone writes a second defparam for the same target.(d) 在任何模組中放 defparam Param.G2.size = 4, Param.G2.delay = 5;。你不會用它,因為合成會拒絕它,覆寫從實例化處完全看不見,而且只要有人為同一個目標再寫第二個 defparam,結果就取決於檔案順序。(d) どのモジュールでもよいので defparam Param.G2.size = 4, Param.G2.delay = 5; と書く。使わない理由は、合成が拒否すること、インスタンス化の場所からオーバーライドがまったく見えないこと、そして同じ対象に誰かが二つ目の defparam を書けば結果がファイル順に依存することである。
Verilog’s operators split into classes by how many operands they take and what shape of result they produce. That second column is the one to learn: it tells you whether you get a word back or a single bit, which determines whether the result is usable as a bus or only as a condition.Verilog 的運算子依照它們取幾個運算元、以及產生什麼形狀的結果而分類。第二欄才是要學的:它告訴你拿回來的是一個字還是單一位元,而那決定了結果能不能當匯流排用,還是只能當條件用。Verilog の演算子は、取るオペランドの数と、生み出す結果の形によって分類される。学ぶべきは二列目である。返ってくるのが語か 1 ビットかを教えてくれ、それが結果をバスとして使えるのか条件としてしか使えないのかを決める。
| Class類別分類 | Operands運算元數オペランド数 | Result結果結果 | Symbols符號記号 |
|---|---|---|---|
| Arithmetic算術算術 | 2 (unary + - take 1)2(單元的 + - 取 1 個)2(単項の + - は 1) | Binary word二進位字二進の語 | + - * / % ** |
| Bitwise位元運算ビット演算 | 2 (~ takes 1)2(~ 取 1 個)2(~ は 1) | Binary word二進位字二進の語 | ~ & | ^ ~^ ^~ |
| Reduction縮減リダクション | 1 | One bit一個位元1 ビット | & ~& | ~| ^ ~^ ^~ |
| Logical邏輯論理 | 2 (! takes 1)2(! 取 1 個)2(! は 1) | Boolean (1 bit)布林(1 位元)ブール(1 ビット) | ! && || |
| Equality相等等価 | 2 | Boolean (1 bit)布林(1 位元)ブール(1 ビット) | == != === !== |
| Relational關係関係 | 2 | Boolean (1 bit)布林(1 位元)ブール(1 ビット) | < <= > >= |
| Shift位移シフト | 2 (value, amount)2(值、位移量)2(値、シフト量) | Binary word二進位字二進の語 | << >> <<< >>> |
| Conditional條件条件 | 3 | Expression運算式式 | ? : |
| Concatenation串接連結 | any number任意個任意個 | Binary word二進位字二進の語 | { , }, {n{ }} |
Note the symbol collisions, which are the richest source of beginner bugs in the language: & is bitwise AND and reduction AND; ^~ is bitwise XNOR and reduction XNOR; & versus && and | versus || differ by one character and by everything else. The parser tells them apart by arity: one operand means reduction, two means bitwise.注意符號的衝突,它們是這個語言中初學者臭蟲最豐富的來源:& 既是逐位元 AND 也是縮減 AND;^~ 既是逐位元 XNOR 也是縮減 XNOR;& 與 &&、| 與 || 只差一個字元,其餘全都不同。剖析器是靠運算元個數來區分它們的:一個運算元就是縮減,兩個就是逐位元。記号の衝突に注意せよ。これは本言語における初学者のバグの最も豊かな供給源である。& はビットごとの AND でもありリダクション AND でもある。^~ はビットごとの XNOR でもリダクション XNOR でもある。& と &&、| と || は 1 文字しか違わないが、それ以外はすべて違う。パーサはオペランドの数で区別する。1 個ならリダクション、2 個ならビット演算である。
assign sum = A + B; assign diff = A - B; assign neg = -A; // unary minus: two's complement negate assign q = A / B; // integer division, truncates toward zero assign r = A % B; // modulus; the sign follows the FIRST operand
Four points that matter in hardware:在硬體上要緊的四點:ハードウェア上で重要な点が四つある。
x or z bit in either operand makes the whole result x. Arithmetic has no controlling values — a carry can propagate from the bottom to the top — so the simulator refuses to guess any bit. One unknown bit in a 32-bit addend blackens all 32 output bits. This is the most common way an x explodes across a datapath.任一運算元中只要有一個 x 或 z 位元,整個結果就是 x。算術沒有控制值 —— 進位可以從最低位一路傳到最高位 —— 所以模擬器拒絕猜測任何一個位元。一個 32 位元加數裡的一個未知位元,就會把 32 個輸出位元全都塗黑。這是 x 在資料路徑上爆開最常見的方式。どちらかのオペランドに x か z のビットが一つでもあれば、結果全体が x になる。算術に支配値は無い —— 桁上げが最下位から最上位まで伝わりうる —— ので、シミュレータはどのビットも推測することを拒む。32 ビットの加数に不定ビットが一つあれば、出力 32 ビットすべてが黒くなる。データパス上で x が爆発する最も一般的な経路がこれである。x, silently. Not an error, not a trap.除以零得到全部 x,而且是無聲的。不是錯誤,也不會觸發例外。ゼロ除算はすべて x になる。しかも黙ってそうなる。エラーでもトラップでもない。/ and % with non-constant operands synthesise to enormous combinational dividers, or are rejected outright. Division by a power of two is a right shift and costs nothing; everything else needs a multi-cycle divider you write yourself. The same is true of %. * is fine — it maps to a Booth or Wallace multiplier.運算元非常數的 / 與 % 會合成出巨大的組合除法器,或是直接被拒絕。除以 2 的冪次就是右移,不花成本;其餘全都需要你自己寫一個多週期除法器。% 也一樣。* 則沒問題 —— 它會對應到 Booth 或 Wallace 乘法器。オペランドが定数でない / と % は巨大な組合せ除算器に合成されるか、そのまま拒否される。2 の冪による除算は右シフトであり費用はかからないが、それ以外は自分で書く多サイクルの除算器が要る。% も同じである。* は問題ない。Booth 乗算器や Wallace 乗算器に対応づけられる。reg is unsigned, integer is signed. -A where A is reg [3:0] holding 3 gives 4'b1101, which as an unsigned value is 13, not −3 — and comparing it with anything will use that interpretation. §8.11 returns to this.有號性來自運算元的型別。reg 是無號的,integer 是有號的。當 A 是存著 3 的 reg [3:0] 時,-A 得到 4'b1101,作為無號值是 13,不是 −3 —— 而把它拿去跟任何東西比較,都會採用那個解讀。§8.11 會回到這一點。符号性はオペランドの型から来る。reg は符号なし、integer は符号つきである。A が 3 を保持する reg [3:0] のとき、-A は 4'b1101 になり、符号なし値としては −3 ではなく 13 である。そして何かと比較すればその解釈が使われる。§8.11 でこの話に戻る。Bitwise operators pair up the bits of their two operands and apply the four-value table of §5.2 to each pair independently. The result is as wide as the wider operand; the shorter operand is zero-extended on the left to match.位元運算子把兩個運算元的位元一一配對,對每一對獨立套用 §5.2 的四值表。結果寬度等於較寬的那個運算元;較短的運算元會在左側補零以對齊。ビット演算子は二つのオペランドのビットを一対一で組にし、各組に独立に §5.2 の四値表を適用する。結果の幅は広い方のオペランドに等しく、短い方は左側にゼロが詰められて長さを合わせる。
| Expression運算式式 | Result結果結果 | Note註記注記 |
|---|---|---|
~(1010) | 0101 | Unary; width preserved.單元運算;寬度維持不變。単項演算。幅は保たれる。 |
(01) & (11) | 01 | |
(01) | (11) | 11 | |
(01) ^ (11) | 10 | |
(01) ~^ (11) | 01 | XNOR: 1 where the bits agree.XNOR:兩位元相同時為 1。XNOR。ビットが一致するところが 1。 |
4'b1010 & 2'b11 | 4'b0010 | The 2-bit operand becomes 4'b0011 first. The top two bits are therefore ANDed with zeros and vanish.那個 2 位元的運算元會先變成 4'b0011。因此高兩位元是跟零做 AND,就此消失。2 ビットのオペランドはまず 4'b0011 になる。したがって上位 2 ビットはゼロとの AND になり、消えてしまう。 |
That last row is the zero-extension rule biting. Masking a wide bus with a short literal silently clears the top of the bus. Write the mask at full width, or use a replication: {4{1'b1}}.最後那一列正是補零規則在咬人。用一個短字面值去遮罩一條寬匯流排,會無聲地把匯流排的高位清掉。請把遮罩寫成完整寬度,或是用複製寫法:{4{1'b1}}。最後の行はまさにゼロ拡張の規則が噛みついているところである。短いリテラルで広いバスをマスクすると、バスの上位が黙って消える。マスクは完全な幅で書くか、繰り返し記法 {4{1'b1}} を使うこと。
For unsigned operands — which is everything declared reg or wire without signed — the padding bits are 0. For signed operands, Verilog-2001 sign-extends instead. Mixing the two in one expression makes the whole expression unsigned and therefore zero-extended, which is how a negative number quietly becomes a very large positive one.對無號運算元 —— 也就是所有宣告成 reg 或 wire 而未加 signed 的東西 —— 補上去的位元是 0。對有號運算元,Verilog-2001 改為做符號擴展。把兩者混在同一個運算式裡,會讓整個運算式變成無號,因此採補零 —— 一個負數就是這樣悄悄變成一個非常大的正數的。符号なしのオペランド —— すなわち signed を付けずに reg や wire と宣言したものすべて —— では詰められるビットは 0 である。符号つきのオペランドでは、Verilog-2001 は代わりに符号拡張を行う。両者を一つの式に混ぜると式全体が符号なしになり、ゼロ拡張される。負の数が静かに非常に大きい正の数へ変わるのはこの経路である。
Reduction operators take one operand and fold all of its bits together with the named operation, producing a single bit. &v is the AND of every bit of v; ^v is the parity of v.縮減運算子取一個運算元,用指名的運算把它所有位元摺疊在一起,產生單一位元。&v 是 v 每個位元的 AND;^v 是 v 的同位元。リダクション演算子はオペランドを一つ取り、その全ビットを指定された演算で畳み込み、1 ビットを生む。&v は v の全ビットの AND であり、^v は v のパリティである。
| Expression運算式式 | Result結果結果 | Why原因理由 |
|---|---|---|
&(0101) | 0 | Not all bits are 1.並非所有位元都是 1。すべてのビットが 1 ではない。 |
|(0101) | 1 | At least one bit is 1.至少有一個位元是 1。少なくとも一つのビットが 1 である。 |
&(01xx) | 0 | Bit 2 is a definite 0, and 0 is controlling for AND — the unknowns never get a vote.位元 2 是確定的 0,而 0 對 AND 是控制值 —— 那些未知根本沒有投票權。ビット 2 は確定した 0 であり、0 は AND の支配値である —— 不定には投票権が無い。 |
|(01xx) | 1 | There is a definite 1, controlling for OR.存在一個確定的 1,它對 OR 是控制值。確定した 1 があり、それが OR の支配値になる。 |
&(11xx) | x | No controlling value anywhere, so the answer genuinely depends on the unknowns.到處都沒有控制值,所以答案確實取決於那些未知。どこにも支配値が無いので、答えは本当に不定に依存する。 |
^(01xx) | x | XOR has no controlling value at all.XOR 根本沒有控制值。XOR には支配値がまったく無い。 |
These are extremely useful and under-used. &addr is a one-line all-ones detector; |data tests “any bit set” and is the idiomatic non-zero test; ^data is a parity tree; ~|flags is “no flags set”. Each synthesises to a balanced tree of gates with depth $\lceil \log_2 n \rceil$, which is exactly what you would have drawn by hand.這些運算子極為好用,卻被用得太少。&addr 是一行寫成的全 1 偵測器;|data 測試「有任何位元被設起」,是慣用的非零測試;^data 是同位元樹;~|flags 是「沒有任何旗標被設起」。每一個都會合成成一棵深度為 $\lceil \log_2 n \rceil$ 的平衡閘樹,而那正是你手畫會畫出來的東西。これらは極めて有用でありながら十分に使われていない。&addr は 1 行で書けるオール 1 検出器、|data は「どれかのビットが立っているか」を調べる定番の非ゼロ判定、^data はパリティツリー、~|flags は「フラグが一つも立っていない」である。いずれも深さ $\lceil \log_2 n \rceil$ のバランスしたゲートツリーに合成される。手で描いたとしてもそうなるものだ。
The ambiguity with the bitwise operators is resolved by arity alone. In a & b the & is bitwise; in &b it is reduction; and in a & &b — legal, if unpleasant — the first is bitwise and the second reduction.與位元運算子之間的歧義,僅靠運算元個數就解決了。在 a & b 中 & 是逐位元;在 &b 中是縮減;而在 a & &b 中 —— 合法,雖然不討喜 —— 第一個是逐位元,第二個是縮減。ビット演算子との曖昧さは、オペランドの数だけで解消される。a & b の & はビット演算、&b ではリダクション、そして a & &b では —— 合法だが気持ちのよい書き方ではない —— 最初がビット演算、二つ目がリダクションである。
Logical operators treat each operand as a single truth value: an operand is true if any of its bits is 1, false if all bits are 0, and unknown if it contains x or z and no 1. They always return one bit.邏輯運算子把每個運算元當成單一真值:只要有任一位元是 1,運算元就為真;所有位元都是 0 則為假;若含有 x 或 z 而且沒有 1,則為未知。它們一律回傳一個位元。論理演算子は各オペランドを単一の真理値として扱う。いずれかのビットが 1 なら真、すべてのビットが 0 なら偽、x または z を含み 1 が無ければ不定である。戻り値は常に 1 ビットである。
if ((b != c) && (index == 0)) ... if ((inword == 1) || (a != d)) ...
The pair of tables below is the single most examinable thing in this section.下面這一對表,是本節最會考的單一項目。以下の二つの表は、本節で最も試験に出る項目である。
| == | 0 | 1 | z | x |
|---|---|---|---|---|
| 0 | 1 | 0 | x | x |
| 1 | 0 | 1 | x | x |
| z | x | x | x | x |
| x | x | x | x | x |
| === | 0 | 1 | z | x |
|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | 0 |
| z | 0 | 0 | 1 | 0 |
| x | 0 | 0 | 0 | 1 |
== asks “are these the same value?” and honestly answers “I cannot tell” when an operand contains an unknown. === asks “are these the same bit patterns, treating x and z as ordinary symbols?” and always returns a hard 0 or 1. The consequences:== 問的是「這兩個是同一個值嗎?」,當運算元含有未知時,它誠實地回答「我沒辦法判斷」。=== 問的是「這兩個是同一組位元樣式嗎?把 x 與 z 當成一般符號看待」,而且永遠回傳確實的 0 或 1。後果是:== は「これらは同じ値か」と問い、オペランドに不定が含まれるときは正直に「判断できない」と答える。=== は「これらは同じビットパターンか。x と z も普通の記号として扱って」と問い、常に確定した 0 か 1 を返す。その帰結は次のとおりである。
4'b1x01 == 4'b1x01 is x, because the x bits might be anything and might differ. 4'b1x01 === 4'b1x01 is 1.4'b1x01 == 4'b1x01 的結果是 x,因為那兩個 x 位元可能是任何值,也可能彼此不同。4'b1x01 === 4'b1x01 的結果是 1。4'b1x01 == 4'b1x01 は x である。二つの x ビットは何であってもよく、互いに異なるかもしれないからだ。4'b1x01 === 4'b1x01 は 1 である。if (a == b) with an x anywhere takes the false branch, because an if treats x as false. The check silently passes or silently fails depending on which way round you wrote it. This is the x-optimism of §5.5 and it is why §4.7 insisted on !== in a checker.當任何地方有 x 時,if (a == b) 會走假的那一支,因為 if 把 x 當成假。檢查會無聲地通過或無聲地失敗,取決於你把條件寫成哪一邊。這就是 §5.5 的 x 樂觀,也是 §4.7 堅持在檢查器中用 !== 的原因。どこかに x があると、if (a == b) は偽の枝を取る。if が x を偽として扱うからだ。条件をどちら向きに書いたかによって、チェックは黙って通りもすれば黙って落ちもする。これが §5.5 の x 楽観性であり、§4.7 がチェッカで !== を使えと念を押した理由である。=== and !== are not synthesisable. There is no gate that can distinguish x from 0 — x does not exist in hardware. They belong in testbenches and assertions only.=== 與 !== 是not synthesisable的。沒有任何一個閘能分辨 x 與 0 —— x 在硬體裡並不存在。它們只屬於測試平台與斷言。=== と !== はnot synthesisableである。x と 0 を区別できるゲートは存在しない。x はハードウェアには存在しないからだ。これらはテストベンチとアサーションにのみ属する。Also note != is the logical inequality (x-contagious) and !== the case inequality; and that !a is a one-bit logical negation while ~a is a full-width bitwise complement. !4'b0100 is 1'b0; ~4'b0100 is 4'b1011. Substituting one for the other in an if usually still “works”, which is what makes the bug durable.另外請注意:!= 是邏輯不等(會被 x 傳染),!== 是 case 不等;以及 !a 是單一位元的邏輯否定,而 ~a 是全寬度的逐位元補數。!4'b0100 是 1'b0;~4'b0100 是 4'b1011。在 if 裡把其中一個換成另一個,通常還是「會動」,而這正是這類臭蟲難以根除的原因。さらに注意すべき点。!= は論理不等(x に伝染される)、!== はケース不等である。また !a は 1 ビットの論理否定で、~a は全幅のビット単位の補数である。!4'b0100 は 1'b0、~4'b0100 は 4'b1011 である。if の中で一方を他方に置き換えてもたいてい「動いてしまう」ので、この種のバグはしぶとい。
< <= > >= compare magnitudes and return one bit, or x if either operand contains an unknown bit. The operands are compared as unsigned unless both are signed — see §8.11, because this rule causes real bugs.< <= > >= 比較大小並回傳一個位元;若任一運算元含有未知位元則回傳 x。除非兩個運算元都是有號的,否則一律以無號比較 —— 見 §8.11,因為這條規則會造成真實的臭蟲。< <= > >= は大小を比較して 1 ビットを返す。どちらかのオペランドに不定ビットがあれば x を返す。両方が符号つきでない限り、オペランドは符号なしとして比較される —— §8.11 を参照。この規則は実際にバグを生む。
if ((a < size - 1) || (b >= 3)) ...
Watch the precedence in that line: - binds tighter than <, so it reads a < (size-1) as intended. But < binds tighter than ||, which is also what you want — and tighter than &, which frequently is not. See §8.10.注意那一行裡的優先順序:- 比 < 結合得更緊,所以它如你所願地讀成 a < (size-1)。但 < 比 || 結合得更緊,這也是你要的 —— 而且比 & 更緊,那就常常不是你要的了。見 §8.10。その行の優先順位に注意せよ。- は < より強く結合するので、意図どおり a < (size-1) と読まれる。しかし < は || より強く結合し、これも望みどおりである一方、& よりも強く結合する。こちらはしばしば望みどおりではない。§8.10 を参照。
result = (a << 3); // left 3; vacated bits filled with 0 result = (a >> 2); // right 2; vacated bits filled with 0 (LOGICAL shift) result = (a >>> 2); // Verilog-2001 ARITHMETIC right shift: sign-extends // ... but only if 'a' is declared signed
Both << and >> fill with zeros, always, regardless of signedness. The arithmetic forms <<< and >>> were added in Verilog-2001; >>> replicates the sign bit only when the left operand is of a signed type, which is the detail people miss when they write >>> on a plain reg and get a logical shift anyway.<< 與 >> 一律補零,與有號性無關。算術形式 <<< 與 >>> 是 Verilog-2001 新增的;>>> 只有在左運算元是有號型別時才會複製符號位元,而這正是有人在普通的 reg 上寫 >>>、結果還是拿到邏輯位移時漏掉的細節。<< と >> は符号性に関係なく常にゼロを詰める。算術形式の <<< と >>> は Verilog-2001 で追加された。>>> が符号ビットを複製するのは左オペランドが符号つきの型のときだけであり、素の reg に >>> と書いて結局論理シフトになる人が見落とすのがこの点である。
A shift by a constant is free in hardware — it is wiring. A shift by a variable amount is a barrel shifter, roughly $n\log_2 n$ multiplexers; still cheap, but not free. If the shift amount is x, the whole result is x.位移常數量在硬體上是免費的 —— 它就是接線。位移可變量則是一個桶形位移器,大約 $n\log_2 n$ 個多工器;仍然便宜,但不是免費。如果位移量是 x,整個結果就是 x。定数によるシフトはハードウェアでは無料である。単なる配線だからだ。可変量のシフトはバレルシフタになり、おおむね $n\log_2 n$ 個のマルチプレクサを要する。安くはあるが無料ではない。シフト量が x なら結果全体が x になる。
conditional_expression ::= expression1 ? expression2 : expression3conditional_expression ::= expression1 ? expression2 : expression3conditional_expression ::= expression1 ? expression2 : expression3
This is the only ternary operator in the language and the workhorse of RTL multiplexing:這是這個語言中唯一的三元運算子,也是 RTL 多工的主力:これは本言語で唯一の三項演算子であり、RTL のマルチプレクサの主力である。
wire [1:0] select;
wire [15:0] D1, D2, D3, D4;
wire [15:0] bus = (select == 2'b00) ? D1 :
(select == 2'b01) ? D2 :
(select == 2'b10) ? D3 :
(select == 2'b11) ? D4 : 16'bx;
Chained like this it is a priority multiplexer, and the trailing 16'bx is the default arm. Writing x rather than 0 there is deliberate: it marks the case as unreachable, lets the unknown propagate if it ever is reached, and tells synthesis that it may treat the case as a don’t-care and optimise the logic.像這樣串起來,它就是一個優先權式多工器,而結尾的 16'bx 是預設分支。在那裡寫 x 而不是 0 是刻意的:它標示這個情況不可到達,萬一真的到達了就讓未知傳播下去,同時告訴合成工具可以把這個情況視為 don't-care 而去最佳化邏輯。このように連ねれば優先順位付きマルチプレクサになり、末尾の 16'bx が既定の枝である。そこに 0 ではなく x と書くのは意図的である。その場合が到達不能であることを示し、万一到達したら不定を伝播させ、同時に合成ツールにはその場合をドントケアとして論理を最適化してよいと伝える。
The behaviour with an ambiguous condition is the slide’s main point and a favourite exam question. If the condition evaluates to x, the simulator does not pick a branch. It evaluates both expressions and merges them bit by bit:條件為不明確值時的行為是投影片的主要重點,也是考試的最愛。如果條件求值為 x,模擬器不會挑任何一支。它會把兩個運算式都求值,然後逐位元合併:条件が曖昧なときの振る舞いはスライドの主眼であり、試験でも好まれる。条件が x に評価された場合、シミュレータはどちらの枝も選ばない。両方の式を評価し、ビットごとにマージする。
| ?: | 0 | 1 | x |
|---|---|---|---|
| 0 | 0 | x | x |
| 1 | x | 1 | x |
| x | x | x | x |
Row = bit of the true-expression, column = bit of the false-expression.列 = 真值運算式的位元,行 = 假值運算式的位元。行 = 真の式のビット、列 = 偽の式のビット。
The rule in words: where the two arms agree, keep the value; where they differ, emit x. It is the most informative answer available — if both branches would have produced a 1 in some bit, then that bit is 1 whichever way the unknown condition resolves.用文字敘述這條規則:兩支一致的地方就保留該值;兩支不同的地方就送出 x。這是所能給出最有資訊量的答案 —— 如果兩支在某個位元上都會產生 1,那麼不論那個未知條件最後解成哪一邊,該位元就是 1。規則を言葉で言えばこうだ。両方の枝が一致するところはその値を残し、食い違うところは x を出す。これは得られる中で最も情報量の多い答えである。両方の枝があるビットで 1 を出すなら、不定な条件がどちらに解決してもそのビットは 1 だからだ。
Example: s ? 4'b1100 : 4'b1010 with s = x gives 4'b1xx0. Bits 3 and 0 agree (1 and 0); bits 2 and 1 differ.例子:s ? 4'b1100 : 4'b1010 在 s = x 時給出 4'b1xx0。位元 3 與位元 0 一致(1 與 0);位元 2 與位元 1 不同。例:s = x のとき s ? 4'b1100 : 4'b1010 は 4'b1xx0 になる。ビット 3 とビット 0 は一致し(1 と 0)、ビット 2 とビット 1 は食い違う。
The slide also states that z is not allowed in the condition. In practice a z in the condition is treated exactly like an x and triggers the same bitwise merge — the point of the rule is that you must not rely on it, because a floating selector has no defined meaning.投影片也說條件中不允許 z。實務上條件裡的 z 會被完全當成 x 對待,觸發同樣的逐位元合併 —— 這條規則的重點在於你不可以依賴它,因為浮接的選擇訊號沒有任何已定義的意義。スライドは条件に z は許されないとも述べている。実際には条件中の z は x とまったく同じに扱われ、同じビットごとのマージが起きる。この規則の要点は、それに依存してはならないということである。浮いたセレクタには定義された意味が無いからだ。
Contrast this with the behavioural if, which treats x as false and picks the else branch (§5.5). The conditional operator is more faithful to the hardware: a real multiplexer with an undefined select does output something unpredictable on the bits where the inputs differ, and something predictable where they agree.把這個拿去跟行為式的 if 對照,後者把 x 當成假而走 else 分支(§5.5)。條件運算子更忠實於硬體:一個選擇訊號未定義的真實多工器,在兩個輸入不同的位元上確實會輸出不可預測的東西,而在兩者相同的位元上輸出可預測的東西。これを、x を偽として else の枝を取る振る舞い記述の if(§5.5)と対比してほしい。条件演算子のほうがハードウェアにより忠実である。セレクトが未定義の実際のマルチプレクサは、入力が食い違うビットでは確かに予測不能なものを出力し、一致するビットでは予測可能なものを出力する。
{a, b} // join: width = width(a) + width(b)
{4{a}} // replicate: {a, a, a, a}
{{3{a}}, {2{b}}, {4{c}}} // correct nesting of several replications
{1'b0, byte_in} // zero-extend to 9 bits before adding
{byte_in[0], byte_in[7:1]} // rotate right by one — pure wiring
Slide 33 writes {3{a}, 2{b}, 4{c}}. This is not legal Verilog. A replication is itself a braced expression, so each one needs its own pair of braces inside the outer concatenation: {{3{a}}, {2{b}}, {4{c}}}. The intended value — nine bits, a a a b b c c c c — is right; only the syntax is wrong. Write the nested form.第 33 頁投影片寫的是 {3{a}, 2{b}, 4{c}}。這不是合法的 Verilog。複製本身就是一個帶大括號的運算式,所以在外層串接裡,每一個都需要自己的一對大括號:{{3{a}}, {2{b}}, {4{c}}}。它想表達的值 —— 九個位元,a a a b b c c c c —— 是對的;只有語法錯了。請寫巢狀形式。スライド 33 枚目には {3{a}, 2{b}, 4{c}} とある。これは合法な Verilog ではない。繰り返しはそれ自体が波括弧で囲まれた式なので、外側の連結の中では各々に自分の波括弧の対が必要である。{{3{a}}, {2{b}}, {4{c}}}。意図した値 —— 9 ビットで a a a b b c c c c —— は正しく、誤っているのは構文だけである。入れ子の形で書くこと。
Two further rules. First, every operand of a concatenation must have a known width, so an unsized constant such as 'b0 or a plain 5 is illegal inside braces; write 1'b0 or 4'd5. Second, the replication count must be a constant expression — it determines the width of a wire, so it must be known at elaboration.還有兩條規則。第一,串接的每個運算元都必須有已知的寬度,所以像 'b0 這種未指定寬度的常數、或是裸寫的 5,在大括號裡都是不合法的;請寫 1'b0 或 4'd5。第二,複製次數必須是常數運算式 —— 它決定一條線的寬度,所以必須在展開期就已知。規則がさらに二つある。第一に、連結のオペランドはすべて既知の幅を持たねばならないので、'b0 のような幅なし定数や裸の 5 は波括弧の中では不正である。1'b0 や 4'd5 と書くこと。第二に、繰り返し回数は定数式でなければならない。配線の幅を決めるので、エラボレーション時に既知でなければならないからだ。
A concatenation is also a legal assignment target, which is often the neatest way to unpack a result: {carry_out, sum} = a + b; captures the carry and the sum in one statement, and is far clearer than widening the operands by hand.串接也是合法的指定目標,這常常是拆解結果最漂亮的方式:{carry_out, sum} = a + b; 一條敘述就同時接住進位與和,比起自己手動把運算元加寬要清楚得多。連結は合法な代入先でもあり、結果を分解するのに最も美しい方法であることが多い。{carry_out, sum} = a + b; は 1 文でキャリーと和の両方を受け取る。自分で手作業でオペランドを広げるよりはるかに明快である。
The slide gives six levels. The real table has fifteen, and the levels it omits — the bitwise and logical operators — are exactly where mistakes happen. Here is the whole thing, highest first:投影片給了六個層級。真正的表有十五個,而它省略掉的那些層級 —— 位元運算子與邏輯運算子 —— 正是出錯的地方。以下是完整的表,由高到低:スライドは 6 段階を示している。本当の表は 15 段階あり、省かれた段 —— ビット演算子と論理演算子 —— こそ間違いが起きる場所である。以下に全体を高い順に示す。
| Level層級段 | Operators運算子演算子 | Associativity結合性結合性 |
|---|---|---|
| 1 (highest)1(最高)1(最高) | + - ! ~ & ~& | ~| ^ ~^ ^~ (all unary)+ - ! ~ & ~& | ~| ^ ~^ ^~(全部為單元運算)+ - ! ~ & ~& | ~| ^ ~^ ^~(すべて単項) | right右右 |
| 2 | ** (power, Verilog-2001)**(次方,Verilog-2001)**(べき乗、Verilog-2001) | left左左 |
| 3 | * / % | left左左 |
| 4 | + - (binary)+ -(二元運算)+ -(二項) | left左左 |
| 5 | << >> <<< >>> | left左左 |
| 6 | < <= > >= | left左左 |
| 7 | == != === !== | left左左 |
| 8 | & (binary bitwise AND)&(二元逐位元 AND)&(二項のビット単位 AND) | left左左 |
| 9 | ^ ^~ ~^ (binary)^ ^~ ~^(二元運算)^ ^~ ~^(二項) | left左左 |
| 10 | | (binary bitwise OR)|(二元逐位元 OR)|(二項のビット単位 OR) | left左左 |
| 11 | && | left左左 |
| 12 | || | left左左 |
| 13 (lowest)13(最低)13(最低) | ?: | right右右 |
Three traps live in this table:這張表裡住著三個陷阱:この表には三つの罠が潜んでいる。
& binds looser than ==. So a & b == c parses as a & (b == c) — almost certainly not what you meant. This is inherited from C and is the most notorious precedence bug in both languages.逐位元 & 比 == 結合得更鬆。所以 a & b == c 會被剖析成 a & (b == c) —— 幾乎肯定不是你的本意。這是從 C 繼承來的,也是兩種語言中最惡名昭彰的優先順序臭蟲。ビット単位の & は == より弱く結合する。したがって a & b == c は a & (b == c) と構文解析される —— ほぼ確実に意図とは違う。これは C から受け継いだもので、両言語で最も悪名高い優先順位のバグである。~a & b is (~a) & b, and -a ** b is (-a) ** b.單元運算子結合得最緊。~a & b 是 (~a) & b,而 -a ** b 是 (-a) ** b。単項演算子が最も強く結合する。~a & b は (~a) & b であり、-a ** b は (-a) ** b である。?: is right-associative and lowest. That is what makes the chained multiplexer in §8.8 parse correctly without parentheses around each arm.?: 是右結合,而且最低。這正是 §8.8 那個串接式多工器不必在每一支外面加括號也能正確剖析的原因。?: は右結合で、しかも最も弱い。§8.8 の連ねたマルチプレクサが各枝を括弧で囲まなくても正しく解析されるのはこのためである。The slide’s advice — if unsure, use parentheses — is the correct professional habit, not a confession of weakness. Nobody has ever been criticised in a code review for parenthesising an expression that did not need it; people are criticised weekly for the opposite.投影片的建議 —— 不確定就加括號 —— 是正確的專業習慣,不是示弱的自白。從來沒有人在程式碼審查中因為替一個不必加括號的運算式加了括號而被批評;因為相反的理由被批評的人,倒是每週都有。スライドの助言 —— 自信が無ければ括弧を使え —— は弱さの告白ではなく、正しい職業上の習慣である。要らない括弧を付けたせいでコードレビューで批判された者はいない。その逆で批判される者なら毎週いる。
This is not on the slides and it causes more real bugs than everything that is. Verilog computes the width of an expression before evaluating it, and the left-hand side of the assignment participates in that calculation.這一節不在投影片上,而它造成的真實臭蟲比投影片上所有東西加起來都多。Verilog 會在求值之前先算出運算式的寬度,而且指定式的左手邊也參與這個計算。これはスライドには無いが、スライドにあるすべてを合わせたより多くの実際のバグを生んでいる。Verilog は式を評価する前にその幅を計算し、しかも代入の左辺もその計算に加わる。
Operands come in two kinds:運算元分成兩種:オペランドには二種類ある。
?:, and the left operand of a shift.由上下文決定(context-determined):運算元會被加寬到周圍運算式中最大參與者的寬度,包含指定目標在內。這適用於算術與位元運算子的運算元、?: 的兩支,以及位移的左運算元。文脈依存(context-determined):オペランドは、周囲の式の中で最大の参加者の幅まで広げられる。代入先も含めてである。これは算術演算子とビット演算子のオペランド、?: の両方の枝、シフトの左オペランドに当てはまる。?:, and everything inside a concatenation or replication.自身決定(self-determined):運算元以自己天然的寬度求值,不管周圍是什麼。這適用於縮減、邏輯與關係運算子的運算元、位移的右運算元、?: 的條件,以及串接或複製裡面的一切。自己決定(self-determined):オペランドは周囲に関係なく自身の自然な幅で評価される。これはリダクション・論理・関係演算子のオペランド、シフトの右オペランド、?: の条件、そして連結や繰り返しの中身すべてに当てはまる。Work through the classic case. wire [3:0] a, b; with a = 4'd12 and b = 4'd10:把經典案例走一遍。wire [3:0] a, b;,其中 a = 4'd12、b = 4'd10:典型例を通して見る。wire [3:0] a, b; で a = 4'd12、b = 4'd10 とする。
| Statement敘述文 | Width used採用的寬度使われる幅 | Result結果結果 |
|---|---|---|
wire [3:0] s4 = a + b; | 4 bits4 位元4 ビット | 22 mod 16 = 4'd6. The carry is lost.22 mod 16 = 4'd6。進位遺失了。22 mod 16 = 4'd6。キャリーが失われる。 |
wire [4:0] s5 = a + b; | 5 bits, because the LHS is 5 wide5 位元,因為左手邊寬 55 ビット。左辺が 5 ビット幅だからである | 5'd22. The carry is preserved — and nothing in the right-hand side changed.5'd22。進位被保留下來 —— 而右手邊什麼都沒改。5'd22。キャリーは保たれる —— しかも右辺は何も変えていない。 |
wire [4:0] s6 = {a} + b; | Concatenation is self-determined at 4 bits, then zero-extended to 5串接是自身決定的 4 位元,然後補零到 5 位元連結は自己決定で 4 ビット、その後 5 ビットへゼロ拡張される | 5'd6. Adding braces threw away the carry.5'd6。加上大括號反而把進位丟掉了。5'd6。波括弧を付けたせいでキャリーが捨てられた。 |
wire [4:0] s7 = (a + b) >> 1; | The shift’s left operand is context-determined at 5 bits位移的左運算元由上下文決定為 5 位元シフトの左オペランドは文脈依存で 5 ビットになる | 22 >> 1 = 5'd11, the correct average.22 >> 1 = 5'd11,正確的平均值。22 >> 1 = 5'd11。正しい平均である。 |
wire [3:0] s8 = (a + b) >> 1; | 4 bits throughout全程 4 位元全体を通して 4 ビット | 6 >> 1 = 4'd3. A silently wrong average.6 >> 1 = 4'd3。一個無聲算錯的平均值。6 >> 1 = 4'd3。黙って誤った平均である。 |
The pair s7 / s8 is the averaging circuit every student writes and half get wrong. The source text is identical; only the declared width of the target differs, and that alone decides whether the carry survives long enough to be shifted back in.s7 / s8 這一對,就是每個學生都會寫、而有一半會寫錯的平均值電路。原始碼文字完全相同;不同的只有指定目標所宣告的寬度,而光是那一點就決定了進位能不能活得夠久、好被位移回來。s7 / s8 の組は、どの学生も書き、その半数が間違える平均化回路である。ソーステキストは同一で、違うのは代入先の宣言された幅だけだ。その一点だけで、キャリーがシフトで戻されるまで生き延びられるかどうかが決まる。
{c_out, sum} = a + b;. Never rely on an implicit truncation you did not write down.把 n 位元加法的結果宣告成 n+1 位元,或是明確地接住進位:{c_out, sum} = a + b;。永遠不要倚賴一個你沒有寫下來的隱含截斷。n ビットの加算の結果は n+1 ビットで宣言するか、キャリーを明示的に受け取る。{c_out, sum} = a + b;。自分で書いていない暗黙の切り捨てに頼ってはならない。integer (signed) with reg (unsigned) in a comparison. If any operand is unsigned the whole expression is evaluated as unsigned, so integer i = -1; reg [3:0] a = 4'd5; if (a > i) is false: −1 becomes 32'hFFFFFFFF, which is larger than 5. Cast deliberately with $signed() / $unsigned() when you must mix.絕不要在比較中把 integer(有號)與 reg(無號)混用。只要任何一個運算元是無號的,整個運算式就以無號求值,所以 integer i = -1; reg [3:0] a = 4'd5; if (a > i) 的結果是假:−1 變成 32'hFFFFFFFF,比 5 大。必須混用時,就用 $signed() / $unsigned() 刻意做轉型。比較の中で integer(符号つき)と reg(符号なし)を混ぜてはならない。オペランドのどれか一つでも符号なしなら式全体が符号なしとして評価されるので、integer i = -1; reg [3:0] a = 4'd5; if (a > i) は偽になる。−1 は 32'hFFFFFFFF となり 5 より大きいからだ。どうしても混ぜるなら $signed() / $unsigned() で意図的にキャストすること。Finally, the slide’s list of what may appear as an operand, which is worth having: a net, a register, a constant, a number, a bit-select, a part-select, a memory element, a function call, or a concatenation of any of these. Note what is absent — a whole memory, a module instance name, a task call (tasks return nothing).最後是投影片列出的「什麼可以當運算元」清單,值得收著:一條 net、一個 register、一個常數、一個數字、一個位元選擇、一個部分選擇、一個記憶體元素、一次 function 呼叫,或以上任何東西的串接。也請注意缺席的是什麼 —— 整個記憶體、模組實例名稱、task 呼叫(task 不回傳任何東西)。最後に、スライドが挙げるオペランドになりうるものの一覧は手元に置く価値がある。ネット、レジスタ、定数、数値、ビット選択、部分選択、メモリ要素、関数呼び出し、およびこれらの連結である。そこに無いものにも注意せよ —— メモリ全体、モジュールのインスタンス名、タスク呼び出し(タスクは何も返さない)。
Problem: a = 4'b1100, b = 4'b1010, c = 4'b10x0. Evaluate: (a) a & b (b) a && b (c) a ^ b (d) ^a (e) &c (f) |c (g) ^c (h) a == c (i) a === c (j) a >> 1 (k) {a[1:0], b[3:2]} (l) !a (m) ~a (n) a & b == b.問題:a = 4'b1100、b = 4'b1010、c = 4'b10x0。求值:(a) a & b (b) a && b (c) a ^ b (d) ^a (e) &c (f) |c (g) ^c (h) a == c (i) a === c (j) a >> 1 (k) {a[1:0], b[3:2]} (l) !a (m) ~a (n) a & b == b。問題:a = 4'b1100、b = 4'b1010、c = 4'b10x0 とする。次を評価せよ。(a) a & b (b) a && b (c) a ^ b (d) ^a (e) &c (f) |c (g) ^c (h) a == c (i) a === c (j) a >> 1 (k) {a[1:0], b[3:2]} (l) !a (m) ~a (n) a & b == b。
(a) 4'b1000 — bitwise. (b) 1'b1 — logical: both operands are non-zero, so true. Compare with (a): same symbol family, completely different answer. (c) 4'b0110. (d) 1'b0 — 1100 has two ones, even parity. (e) 1'b0 — bit 0 is a definite 0, controlling for AND. (f) 1'b1 — bit 3 is a definite 1. (g) 1'bx — XOR has no controlling value. (h) 1'bx — == is x-contagious. (i) 1'b0 — === compares bit patterns literally; 1100 and 10x0 differ, so a hard 0. (j) 4'b0110 — logical right shift, zero filled. (k) 4'b0010 — a[1:0] = 00, b[3:2] = 10. (l) 1'b0 — a is non-zero so logically true, negated to false; one bit. (m) 4'b0011 — four bits. (n) Trap: == binds tighter than &, so this is a & (b == b) = 4'b1100 & 1'b1 = 4'b1100 & 4'b0001 = 4'b0000. If you wanted (a & b) == b you had to write the parentheses.(a) 4'b1000 —— 逐位元。(b) 1'b1 —— 邏輯:兩個運算元都非零,所以為真。拿它跟 (a) 比較:同一族的符號,答案卻完全不同。(c) 4'b0110。(d) 1'b0 —— 1100 有兩個 1,偶同位。(e) 1'b0 —— 位元 0 是確定的 0,對 AND 是控制值。(f) 1'b1 —— 位元 3 是確定的 1。(g) 1'bx —— XOR 沒有控制值。(h) 1'bx —— == 會被 x 傳染。(i) 1'b0 —— === 照字面比較位元樣式;1100 與 10x0 不同,所以是確實的 0。(j) 4'b0110 —— 邏輯右移,補零。(k) 4'b0010 —— a[1:0] = 00,b[3:2] = 10。(l) 1'b0 —— a 非零故邏輯上為真,取否定後為假;一個位元。(m) 4'b0011 —— 四個位元。(n) 陷阱:== 比 & 結合得更緊,所以這是 a & (b == b) = 4'b1100 & 1'b1 = 4'b1100 & 4'b0001 = 4'b0000。如果你要的是 (a & b) == b,那就得把括號寫出來。(a) 4'b1000 —— ビット演算。(b) 1'b1 —— 論理演算。両オペランドとも非ゼロなので真。(a) と比べよ。同じ系統の記号でまったく違う答えになる。(c) 4'b0110。(d) 1'b0 —— 1100 には 1 が二つ、偶パリティ。(e) 1'b0 —— ビット 0 は確定した 0 で、AND の支配値。(f) 1'b1 —— ビット 3 は確定した 1。(g) 1'bx —— XOR に支配値は無い。(h) 1'bx —— == は x に伝染される。(i) 1'b0 —— === はビットパターンを文字どおり比較する。1100 と 10x0 は異なるので確定した 0。(j) 4'b0110 —— 論理右シフト、ゼロ詰め。(k) 4'b0010 —— a[1:0] = 00、b[3:2] = 10。(l) 1'b0 —— a は非ゼロなので論理的に真、否定して偽。1 ビットである。(m) 4'b0011 —— 4 ビット。(n) 罠:== は & より強く結合するので、これは a & (b == b) = 4'b1100 & 1'b1 = 4'b1100 & 4'b0001 = 4'b0000 である。(a & b) == b が欲しければ括弧を書かねばならない。
Problem: (a) reg [3:0] a = 4'd9, b = 4'd9; — give the value of avg for reg [3:0] avg = (a + b) >> 1; and for reg [4:0] avg5 = (a + b) >> 1;. (b) y = s ? 8'b1111_0000 : 8'b1100_1100; — give y for s = 1, 0 and x. (c) Why does {1'b0, a} + {1'b0, b} behave differently from a + b when assigned to a 5-bit target? (d) reg [7:0] d; integer n = -2; if (d > n) with d = 8'd200 — true or false?問題:(a) reg [3:0] a = 4'd9, b = 4'd9; —— 分別給出 reg [3:0] avg = (a + b) >> 1; 與 reg [4:0] avg5 = (a + b) >> 1; 的 avg 值。(b) y = s ? 8'b1111_0000 : 8'b1100_1100; —— 給出 s = 1、0 與 x 時的 y。(c) 當指定給一個 5 位元目標時,{1'b0, a} + {1'b0, b} 為什麼與 a + b 行為不同?(d) reg [7:0] d; integer n = -2; if (d > n),其中 d = 8'd200 —— 真還是假?問題:(a) reg [3:0] a = 4'd9, b = 4'd9; のとき、reg [3:0] avg = (a + b) >> 1; と reg [4:0] avg5 = (a + b) >> 1; それぞれの値を示せ。(b) y = s ? 8'b1111_0000 : 8'b1100_1100; について、s = 1、0、x のときの y を示せ。(c) 5 ビットの代入先に代入するとき、{1'b0, a} + {1'b0, b} は a + b とどう違うのか。(d) d = 8'd200 のもとで reg [7:0] d; integer n = -2; if (d > n) は真か偽か。
(a) 9 + 9 = 18. With a 4-bit target the whole expression is 4 bits: 18 mod 16 = 2, then 2 >> 1 = 1 — badly wrong. With a 5-bit target the addition is done in 5 bits: 18, then 18 >> 1 = 9 — correct. Identical source text, different answer, decided solely by the declaration of the target.(a) 9 + 9 = 18。目標是 4 位元時,整個運算式就是 4 位元:18 mod 16 = 2,接著 2 >> 1 = 1 —— 錯得離譜。目標是 5 位元時,加法以 5 位元進行:18,接著 18 >> 1 = 9 —— 正確。原始碼文字一模一樣,答案卻不同,而這完全由目標的宣告決定。(a) 9 + 9 = 18。代入先が 4 ビットなら式全体が 4 ビットになる。18 mod 16 = 2、続いて 2 >> 1 = 1 —— ひどく誤っている。代入先が 5 ビットなら加算は 5 ビットで行われ、18 のあと 18 >> 1 = 9 —— 正しい。ソーステキストは同一なのに答えが違い、それを決めるのは代入先の宣言だけである。
(b) s = 1: 8'b1111_0000. s = 0: 8'b1100_1100. s = x: merge bit by bit, keeping agreements. Comparing 11110000 with 11001100: bits 7,6 agree as 1; bits 5,4 are 1 vs 0 → x; bit 3 is 0 vs 1 → x; bit 2 is 0 vs 1 → x; bits 1,0 agree as 0. Result 8'b11xxxx00.(b) s = 1:8'b1111_0000。s = 0:8'b1100_1100。s = x:逐位元合併,保留一致的部分。把 11110000 與 11001100 相比:位元 7、6 一致為 1;位元 5、4 是 1 對 0 → x;位元 3 是 0 對 1 → x;位元 2 是 0 對 1 → x;位元 1、0 一致為 0。結果是 8'b11xxxx00。(b) s = 1:8'b1111_0000。s = 0:8'b1100_1100。s = x:ビットごとにマージし、一致するところを残す。11110000 と 11001100 を比べると、ビット 7、6 は 1 で一致、ビット 5、4 は 1 対 0 → x、ビット 3 は 0 対 1 → x、ビット 2 は 0 対 1 → x、ビット 1、0 は 0 で一致。結果は 8'b11xxxx00。
(c) Nothing — in that particular case they agree, because the 5-bit target already forces the addition to 5 bits. But the braced version is robust: it carries its own extension, so it still produces 18 even if someone later narrows the target to 4 bits (the truncation then happens on assignment rather than inside the adder). Explicit extension says what you mean instead of relying on a context rule the next reader may not know.(c) 沒有不同 —— 在那個特定情況下兩者一致,因為 5 位元的目標已經把加法強制成 5 位元了。但加了大括號的版本比較穩健:它自己帶著擴展,所以就算日後有人把目標改窄成 4 位元,它仍然會算出 18(截斷改成發生在指定時,而不是在加法器內部)。明確的擴展把你的意思說出來,而不是倚賴一條下一位讀者可能不知道的上下文規則。(c) 違わない —— その特定の場合には一致する。5 ビットの代入先がすでに加算を 5 ビットに強制しているからだ。しかし波括弧を付けた版のほうが頑健である。自前で拡張を持ち歩くので、後で誰かが代入先を 4 ビットに狭めても 18 を計算する(切り捨ては加算器の中ではなく代入時に起きる)。明示的な拡張は、次の読み手が知らないかもしれない文脈規則に頼らず、意図をそのまま述べている。
(d) False — and if you answered “true, because 200 > −2” you fell into the trap. d is unsigned, so the whole comparison is evaluated as unsigned, and n = −2 is reinterpreted as 32'hFFFF_FFFE = 4 294 967 294, which is far larger than 200. The unsigned conversion turns a small negative number into an enormous positive one. Correct it with if ($signed({1'b0, d}) > n), or simply never compare an integer with a reg.(d) 假 —— 如果你答「真,因為 200 > −2」,那你就掉進陷阱了。d 是無號的,所以整個比較以無號求值,而 n = −2 會被重新解讀成 32'hFFFF_FFFE = 4 294 967 294,比 200 大得多。無號轉換把一個小負數變成了一個巨大的正數。修正方式是寫 if ($signed({1'b0, d}) > n),或者乾脆永遠不要拿 integer 跟 reg 比較。(d) 偽 —— 「200 > −2 だから真」と答えたなら罠にはまっている。d は符号なしなので比較全体が符号なしで評価され、n = −2 は 32'hFFFF_FFFE = 4 294 967 294 と読み替えられる。これは 200 よりはるかに大きい。符号なし変換は小さな負数を巨大な正数に変えてしまう。if ($signed({1'b0, d}) > n) と書いて直すか、そもそも integer と reg を比較しないことである。
§6.3 said that a variable is visible by its bare name only inside the scope that declares it. Verilog provides one escape: every named object in the design has a unique hierarchical path name, formed by joining instance names with dots from a known root down to the object.§6.3 說過,變數只有在宣告它的範圍內才能用裸名稱看見。Verilog 提供一個逃生口:設計中每個具名物件都有一個唯一的階層式路徑名稱,由實例名稱以點號串接,從一個已知的根一路接到該物件。§6.3 で述べたように、変数が裸の名前で見えるのは、それを宣言したスコープの内側だけである。Verilog はその抜け道を一つ用意している。設計中の名前付きオブジェクトはすべて一意の階層パス名を持ち、既知のルートからそのオブジェクトまでインスタンス名をドットでつないで作られる。
w declared at two levels is two separate objects, told apart by their full paths.階層式路徑名稱。在兩個層級各自宣告的同名識別字 w 是兩個分開的物件,靠完整路徑來分辨。階層パス名。二つの階層でそれぞれ宣言された同名の識別子 w は別々のオブジェクトであり、完全なパスによって区別される。The syntax is instance.object, repeated as deep as necessary: X.w, X.Y.Z.w. The parts before the final name are instance names, not module names — which is exactly right, because a module instantiated three times has three distinct copies of every variable and you need to name the copy, not the template. A path may also address a named begin...end block, a task, a function, or a parameter: hdref_Param.G2.size in §7.3 is a parameter addressed this way.語法是 instance.object,需要多深就重複多少次:X.w、X.Y.Z.w。最後那個名字之前的各段都是實例名稱,不是模組名稱 —— 這完全正確,因為一個被實例化三次的模組,其每個變數都有三份相異的副本,而你要指名的是副本,不是樣板。路徑也可以指向一個具名的 begin...end 區塊、一個 task、一個 function,或一個參數:§7.3 中的 hdref_Param.G2.size 就是以這種方式指名的參數。構文は instance.object で、必要なだけ繰り返す。X.w、X.Y.Z.w。最後の名前より前の各要素はインスタンス名であってモジュール名ではない。これはまったく正しい。三回インスタンス化されたモジュールは各変数のコピーを三つ持ち、名指しすべきはテンプレートではなくコピーだからである。パスは名前付き begin...end ブロック、タスク、関数、パラメータも指せる。§7.3 の hdref_Param.G2.size はこの方法で指されたパラメータである。
Verilog resolves a hierarchical name by first trying to match the leading identifier against an instance or named block in the current scope (a downward reference), and failing that, walking up the hierarchy looking for a module whose name matches (an upward reference). A path starting with the top-level module name is unambiguous from anywhere, which is why defparam paths are written that way.Verilog 解析階層式名稱的方式是:先試著把開頭的識別字對應到目前範圍中的某個實例或具名區塊(向下參考),若失敗則沿著階層往上走,尋找名稱相符的模組(向上參考)。以頂層模組名稱開頭的路徑,從任何地方看都毫無歧義,這也是 defparam 的路徑都那樣寫的原因。Verilog が階層名を解決する手順はこうである。まず先頭の識別子を現在のスコープ内のインスタンスまたは名前付きブロックに対応づけようとし(下方参照)、それが失敗したら階層を上へたどって名前の一致するモジュールを探す(上方参照)。トップレベルのモジュール名で始まるパスはどこから見ても曖昧でなく、defparam のパスがそう書かれるのもこのためである。
The deck’s closing example is a four-bit ripple-carry adder whose internal carries are observed from the testbench without ever bringing them out as ports:投影片的收尾範例是一個四位元漣波進位加法器,它的內部進位完全不用拉成埠,就能從測試平台觀察到:スライドの締めくくりの例は 4 ビットのリップルキャリー加算器であり、内部キャリーをポートとして外に出すことなくテストベンチから観測している。
module test_Add_rca_4 ();
reg [3:0] a, b;
reg c_in;
wire [3:0] sum;
wire c_out;
initial
begin
$monitor($time, " c_out=%b c_in4=%b c_in3=%b c_in2=%b c_in=%b",
c_out, M1.c_in4, M1.c_in3, M1.c_in2, c_in);
end
initial
begin
// stimulus patterns go here
end
Add_rca_4 M1 (sum, c_out, a, b, c_in); // instantiate the DUT
endmodule
module Add_rca_4 (sum, c_out, a, b, c_in);
output [3:0] sum;
output c_out;
input [3:0] a, b;
input c_in;
wire c_in4, c_in3, c_in2; // internal carries, no ports
Add_full G1 (sum[0], c_in2, a[0], b[0], c_in);
Add_full G2 (sum[1], c_in3, a[1], b[1], c_in2);
Add_full G3 (sum[2], c_in4, a[2], b[2], c_in3);
Add_full G4 (sum[3], c_out, a[3], b[3], c_in4);
endmodule
Why this is worth doing: a ripple-carry adder’s whole character is the carry chain, and the carry chain is invisible from outside. If the sum is wrong for some input pair, watching c_in2 … c_in4 tells you immediately which full adder broke the chain, instead of leaving you to bisect by hand. The alternative — adding debug output ports to Add_rca_4 — pollutes the design’s interface with something that exists only for verification and that you will then have to remember to remove.為什麼值得這樣做:漣波進位加法器的整個性格就在進位鏈上,而進位鏈從外面看不見。如果某組輸入下和算錯了,看 c_in2 … c_in4 會立刻告訴你是哪一個全加器弄斷了鏈條,不必讓你自己去手動二分搜尋。另一條路 —— 替 Add_rca_4 加上除錯輸出埠 —— 會用只為驗證而存在的東西污染設計的介面,而且之後你還得記得把它拿掉。これが価値を持つ理由はこうだ。リップルキャリー加算器の性格はまるごとキャリーチェーンにあり、そのキャリーチェーンは外からは見えない。ある入力の組で和が誤っていたとき、c_in2 … c_in4 を見ればどの全加算器が鎖を切ったのかが即座に分かる。手で二分探索する必要はない。もう一方の道 —— Add_rca_4 にデバッグ用の出力ポートを足す —— は、検証のためだけに存在するもので設計のインタフェースを汚し、しかも後で取り除くのを覚えておかねばならない。
You can go further. M1.G2.sum reaches into the second full adder; test_Add_rca_4.M1.c_in2 is the same net written absolutely. Hierarchical names also work as targets: M1.c_in2 = 1'b0; is a legal procedural assignment from the testbench, and force M1.c_in2 = 1'b0; overrides the net’s own drivers until a matching release. Forcing a value is the standard way to inject a fault and confirm that your checker actually catches it.還可以更進一步。M1.G2.sum 伸進第二個全加器;test_Add_rca_4.M1.c_in2 則是把同一條連線寫成絕對路徑。階層式名稱也可以當指定目標:M1.c_in2 = 1'b0; 是測試平台可以合法執行的程序性指定,而 force M1.c_in2 = 1'b0; 會覆蓋該連線自己的驅動源,直到對應的 release 為止。強制一個值,是注入故障並確認你的檢查器真的抓得到它的標準作法。さらに踏み込める。M1.G2.sum は二つ目の全加算器に手を伸ばし、test_Add_rca_4.M1.c_in2 は同じネットを絶対パスで書いたものである。階層名は代入先にもなる。M1.c_in2 = 1'b0; はテストベンチから実行できる合法な手続き代入であり、force M1.c_in2 = 1'b0; は対応する release までそのネット自身のドライバを上書きする。値を強制するのは、故障を注入してチェッカが本当にそれを捕まえるかを確認する標準的な方法である。
bind, interfaces, or an explicit debug bus) rather than reach in arbitrarily.它在階層變動下很脆弱。改個實例名稱、包一層、或讓合成把設計攤平並做 uniquify,所有參考舊結構的路徑都會無聲地對不上。一個塞滿深路徑的測試平台,只要 RTL 一重構就會壞掉,這也是為什麼業界的測試平台偏好刻意地開出觀察點(SystemVerilog 的 bind、interface,或一條明確的除錯匯流排),而不是任意地伸手進去。階層の変更に対して脆い。インスタンス名を変える、階層を一枚かぶせる、あるいは合成が設計を平坦化して uniquify するだけで、旧構造を参照していたパスはすべて黙って一致しなくなる。深いパスで埋まったテストベンチは RTL をリファクタリングするたびに壊れる。産業的なテストベンチが、任意に手を伸ばすのではなく観測点を意図的に用意する(SystemVerilog の bind、インタフェース、明示的なデバッグバス)のはこのためである。M1.c_in2 compiles against the RTL and fails against the post-synthesis netlist — the classic reason an otherwise-good testbench cannot be reused for gate-level simulation. Keep hierarchical probing in a separate, clearly marked block you can disable.那條連線在閘級網表裡可能根本不存在。合成可以自由地合併、改名或最佳化掉一條內部連線。監看 M1.c_in2 的測試平台能對 RTL 編譯通過,對合成後的網表卻會失敗 —— 這是一個原本不錯的測試平台無法沿用到閘級模擬的經典原因。請把階層式探測放在一個分開、標示清楚、可以關掉的區塊裡。そのネットはゲートレベルネットリストに存在しないかもしれない。合成は内部ネットを自由に併合・改名・最適化除去できる。M1.c_in2 を監視するテストベンチは RTL に対してはコンパイルできても、合成後のネットリストに対しては失敗する。良くできたテストベンチがゲートレベルシミュレーションに流用できない古典的な理由がこれである。階層プローブは、分離して明示し、無効化できるブロックに置くこと。force/release for deliberate fault injection and never leave one in a regression.讀是無害的,寫不是。監看一個內部訊號不會改變任何東西。對它做指定、或強制它,會讓模擬去描述一個並不存在的電路。force/release 只用於刻意的故障注入,而且絕不要把它留在回歸測試裡。読むのは無害だが、書くのはそうではない。内部信号を監視しても何も変わらない。そこへ代入したり force したりすれば、シミュレーションは存在しない回路を記述することになる。force/release は意図的な故障注入にのみ使い、リグレッションに残してはならない。defparam (§7.3), and the reason that feature carries the same fragility warnings.參數同樣可以被指名,這正是 defparam(§7.3)背後的全部機制,也是那項功能帶著同樣脆弱性警告的原因。パラメータも指定できる。これが defparam(§7.3)の仕組みのすべてであり、あの機能が同じ脆さの警告を背負っている理由でもある。Problem: Using the adder above, with a = 4'b0111, b = 4'b0001, c_in = 0: (a) give sum, c_out and the three internal carries. (b) Write the hierarchical name for the sum output terminal of the third full adder. (c) The testbench also declares its own wire c_in2;. Does M1.c_in2 still refer to the adder’s net? (d) Why would $monitor($time, " %b", M1.c_in2); stop working after you replace the RTL adder with the synthesised netlist?問題:使用上面的加法器,取 a = 4'b0111、b = 4'b0001、c_in = 0:(a) 給出 sum、c_out 與三個內部進位。(b) 寫出第三個全加器的 sum 輸出端子的階層式名稱。(c) 測試平台自己也宣告了一條 wire c_in2;。M1.c_in2 還是指向加法器裡那條連線嗎?(d) 為什麼把 RTL 加法器換成合成後的網表之後,$monitor($time, " %b", M1.c_in2); 就不能用了?問題:上の加算器で a = 4'b0111、b = 4'b0001、c_in = 0 とする。(a) sum、c_out、および三つの内部キャリーを示せ。(b) 三つ目の全加算器の sum 出力端子の階層名を書け。(c) テストベンチ自身も wire c_in2; を宣言している。M1.c_in2 は依然として加算器内のネットを指すか。(d) RTL の加算器を合成後のネットリストに置き換えると、なぜ $monitor($time, " %b", M1.c_in2); が動かなくなるのか。
(a) 7 + 1 + 0 = 8 = 5'b01000, so sum = 4'b1000 and c_out = 0. The carries ripple all the way: G1 gets 1+1+0 → sum 0, carry 1, so c_in2 = 1. G2 gets 1+0+1 → sum 0, carry 1, so c_in3 = 1. G3 gets 1+0+1 → sum 0, carry 1, so c_in4 = 1. G4 gets 0+0+1 → sum 1, carry 0, so c_out = 0. This input is the worst case for a ripple-carry adder — the carry propagates through every stage, and the delay you would measure here is the critical path.(a) 7 + 1 + 0 = 8 = 5'b01000,所以 sum = 4'b1000、c_out = 0。進位一路漣波過去:G1 吃到 1+1+0 → 和為 0、進位為 1,所以 c_in2 = 1。G2 吃到 1+0+1 → 和為 0、進位為 1,所以 c_in3 = 1。G3 吃到 1+0+1 → 和為 0、進位為 1,所以 c_in4 = 1。G4 吃到 0+0+1 → 和為 1、進位為 0,所以 c_out = 0。這組輸入是漣波進位加法器的最壞情況 —— 進位傳遍每一級,而你在這裡量到的延遲就是關鍵路徑。(a) 7 + 1 + 0 = 8 = 5'b01000 なので sum = 4'b1000、c_out = 0 である。キャリーは最後まで波及する。G1 は 1+1+0 → 和 0、キャリー 1 なので c_in2 = 1。G2 は 1+0+1 → 和 0、キャリー 1 なので c_in3 = 1。G3 は 1+0+1 → 和 0、キャリー 1 なので c_in4 = 1。G4 は 0+0+1 → 和 1、キャリー 0 なので c_out = 0。この入力はリップルキャリー加算器にとって最悪の場合であり、キャリーが全段を伝播する。ここで測られる遅延がクリティカルパスである。
(b) M1.G3.sum, or absolutely, test_Add_rca_4.M1.G3.sum. (Whether sum is the formal’s name depends on Add_full’s port list; the path to the instance is M1.G3 either way.)(b) M1.G3.sum,或寫成絕對路徑 test_Add_rca_4.M1.G3.sum。(sum 是不是形式參數的名稱,取決於 Add_full 的埠清單;但無論如何,到該實例的路徑都是 M1.G3。)(b) M1.G3.sum、絶対パスで書けば test_Add_rca_4.M1.G3.sum。(sum が仮引数の名前かどうかは Add_full のポートリスト次第だが、そのインスタンスまでのパスはいずれにせよ M1.G3 である。)
(c) Yes. The path is resolved from the instance M1 downwards, so M1.c_in2 is unambiguously the adder’s net. The testbench’s own c_in2 is a different object reached by the bare name. This is legal but a terrible idea — two different nets with the same name in one file is an invitation to misread the code.(c) 會。路徑是從實例 M1 往下解析的,所以 M1.c_in2 毫無歧義就是加法器裡的那條連線。測試平台自己的 c_in2 是另一個物件,用裸名稱存取。這合法,但是個糟糕的主意 —— 在同一個檔案裡有兩條同名的不同連線,等於邀請別人讀錯程式碼。(c) 指す。パスはインスタンス M1 から下向きに解決されるので、M1.c_in2 は曖昧さなく加算器のネットである。テストベンチ自身の c_in2 は裸の名前でアクセスされる別のオブジェクトだ。これは合法だがひどい考えである。同じファイルに同名の異なるネットが二つあるのは、コードを読み違えてくれと誘っているようなものだ。
(d) Synthesis has no obligation to preserve internal net names. c_in2 may have been renamed, merged into a carry-chain cell, or optimised away entirely, so the path no longer resolves and the compile fails. The fix is to keep hierarchical probes out of the reusable part of the testbench, or to tell synthesis to preserve specific nets — at a cost in optimisation quality.(d) 合成沒有義務保留內部連線的名稱。c_in2 可能已被改名、被併進某個進位鏈元件,或被完全最佳化掉,於是那條路徑解析不出來,編譯就失敗。修法是把階層式探測排除在測試平台可重用的部分之外,或是叫合成工具保留特定的連線 —— 代價是最佳化品質下降。(d) 合成は内部ネット名を保存する義務を負わない。c_in2 は改名されたか、キャリーチェーンのセルに併合されたか、完全に最適化で消えたかもしれず、パスが解決できなくなってコンパイルが失敗する。対策は、階層プローブをテストベンチの再利用可能な部分から外しておくか、特定のネットを保存するよう合成ツールに指示することである。後者は最適化の質を代償にする。
Everything in this lecture reduces to a small number of statements you should be able to produce under exam conditions without looking anything up.本講的一切都可以歸結成少數幾句話,而你應該能在考試情境下不查任何資料就寫得出來。本講の内容はすべて、少数の文に帰着する。試験の場で何も見ずに書き出せるようになっておくべきものだ。
out <= #d in; inside an always.傳輸延遲需要在 always 裡寫 out <= #d in;。伝送遅延には always の中で out <= #d in; と書く必要がある。reg, outputs are wire.頂層模組無埠;DUT 輸入是 reg,輸出是 wire。トップモジュールはポート無し。DUT の入力は reg、出力は wire。initial blocks for monitoring, stimulus and $finish.監看、激勵與 $finish 各用分開的 initial 區塊。監視・刺激・$finish はそれぞれ別の initial ブロックに分ける。$display prints now (active); $strobe and $monitor print at the end of the slot.$display 當下就印(active);$strobe 與 $monitor 在時間槽結束時才印。$display はその場で表示し(active)、$strobe と $monitor はスロットの終わりに表示する。$monitor is active; it ignores changes in $time.只有一個 $monitor 會作用;它會忽略 $time 的變化。有効な $monitor は一つだけであり、$time の変化は無視する。$dumpfile then $dumpvars(0, tb) for a full VCD.要完整的 VCD,就先 $dumpfile 再 $dumpvars(0, tb)。完全な VCD を取るには $dumpfile の後に $dumpvars(0, tb)。~x keeps it at x for ever.時脈要初始化,否則 ~x 會讓它永遠停在 x。クロックは初期化すること。さもないと ~x のせいで永遠に x のままになる。!== in checkers, never !=.檢查器裡用 !==,絕不要用 !=。チェッカでは !== を使い、!= は決して使わない。x is ignorance; z is a floating node.0、1、x(未知)、z(高阻抗)。x 是無知;z 是浮接節點。0、1、x(不定)、z(ハイインピーダンス)。x は無知であり、z は浮いたノードである。and(0,x)=0, or(1,x)=1, nand(0,x)=1.控制值壓過一切:and(0,x)=0、or(1,x)=1、nand(0,x)=1。支配値が優先する。and(0,x)=0、or(1,x)=1、nand(0,x)=1。wire conflict → x; wand → 0 wins; wor → 1 wins; z never wins.連線解析:wire 衝突 → x;wand → 0 勝;wor → 1 勝;z 永遠不會勝。ネットの解決:wire の衝突 → x、wand → 0 が勝つ、wor → 1 が勝つ、z は決して勝たない。if treats x as false — x-optimism, and the cause of RTL/gate mismatches.if 把 x 當成假 —— 這就是 x 樂觀,也是 RTL 與閘級不一致的成因。if は x を偽として扱う —— これが x 楽観性であり、RTL とゲートの不一致の原因である。reg does not mean flip-flop.reg 不代表正反器。reg はフリップフロップを意味しない。x), undriven net = z, register = x.在時刻 0:有驅動源的 net = 驅動源的值(通常是 x),無驅動源的 net = z,register = x。時刻 0 では、駆動されているネット = ドライバの値(通常 x)、駆動されていないネット = z、レジスタ = x。`default_nettype none.未宣告的識別字會變成一條 1 位元 wire —— 請用 `default_nettype none。未宣言の識別子は 1 ビットの wire になる —— `default_nettype none を使うこと。v[m:n] is $m-n+1$ bits; left index is the MSB; out of range gives x.部分選擇 v[m:n] 有 $m-n+1$ 個位元;左索引是 MSB;越界會給 x。部分選択 v[m:n] は $m-n+1$ ビット。左の添字が MSB であり、範囲外は x を返す。#(4,5) overrides in declaration order; #(.size(4)) is better; defparam uses hierarchical names and is deprecated.#(4,5) 依宣告順序覆寫;#(.size(4)) 更好;defparam 使用階層式名稱,且已被標為不建議使用。#(4,5) は宣言順にオーバーライドする。#(.size(4)) のほうがよい。defparam は階層名を使い、非推奨である。# on a module instance is always a parameter, never a delay. On a primitive it is always a delay.模組實例上的 # 永遠是參數,絕不是延遲。基本元件上的 # 則永遠是延遲。モジュールインスタンスに付く # は常にパラメータであり、決して遅延ではない。プリミティブに付く # は常に遅延である。&.縮減回傳一個位元,逐位元回傳一個字;& 靠運算元個數來區分。リダクションは 1 ビットを返し、ビット演算は語を返す。& はオペランドの数で区別される。== is x-contagious; === is literal and unsynthesisable.== 會被 x 傳染;=== 照字面比較,而且不可合成。== は x に伝染され、=== は文字どおりの比較で合成できない。?: with an x condition merges both arms bitwise.條件為 x 的 ?: 會把兩支逐位元合併。条件が x の ?: は両方の枝をビットごとにマージする。{{3{a}}, {2{b}}}.複製需要自己的大括號:{{3{a}}, {2{b}}}。繰り返しには自分の波括弧が要る。{{3{a}}, {2{b}}}。& binds looser than ==. Parenthesise.逐位元 & 比 == 結合得更鬆。請加括號。ビット単位の & は == より弱く結合する。括弧を付けること。Lecture 3 takes the delay model further into user-defined primitives and the full syntax of delay specification. Lecture 4 replaces gate primitives with behavioural always blocks, at which point the stratified event queue of §2.4 stops being background knowledge and becomes the thing that decides whether your flip-flops work. Lectures 5 to 7 draw the line this lecture only sketched, between the subset of Verilog that a synthesis tool can turn into cells and the rest, which lives in the testbench. Everything here about nets, registers, four-value logic and operator semantics is assumed from this point on.Lecture 3 把延遲模型推得更遠,進入使用者自訂基本元件與延遲規格的完整語法。Lecture 4 用行為式 always 區塊取代閘基本元件,到那時 §2.4 的分層事件佇列就不再只是背景知識,而會變成決定你的正反器能不能動的那個東西。Lecture 5 到 7 則把本講只勾勒出輪廓的那條線畫清楚:Verilog 中合成工具能變成元件的那個子集,與其餘只能住在測試平台裡的部分。本講關於 net、register、四值邏輯與運算子語意的一切,從這裡起都會被預設為已知。Lecture 3 は遅延モデルをさらに進め、ユーザ定義プリミティブと遅延指定の完全な構文に入る。Lecture 4 はゲートプリミティブを振る舞い記述の always ブロックで置き換える。その時点で §2.4 の層化イベントキューは背景知識であることをやめ、フリップフロップが動くかどうかを決める当のものになる。Lecture 5 から 7 は、本講が輪郭を描いただけの線をはっきりと引く。合成ツールがセルに変えられる Verilog の部分集合と、テストベンチに住む残りとの線である。ネット、レジスタ、四値論理、演算子の意味論について本講で述べたことは、ここから先はすべて既知として扱われる。