Menu

Verilog if-else: לוגיקה מותנית בבלוקים פרוצדורליים

איך if/else עובד בתוך בלוק always, מלכודת ה-latch שתופסת מתחילים, וחומרת ה-priority encoder ששרשרת של else if מייצרת.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

תחביר מוכר, מודל מנטלי שונה

if/else נראה בדיוק כמו ב-C:

if (condition) begin
    // ... statements ...
end else begin
    // ... statements ...
end

אבל הכללים שונים, כי Verilog היא לא תוכנה. שני דברים שכדאי לזכור:

  1. if/else חי רק בתוך בלוק פרוצדורלי. אי אפשר לכתוב if עצמאי ברמת המודול.
  2. מה ש-if/else הופך אליו בסינתזה תלוי בסוג הבלוק. בבלוק צירופי הוא הופך ל-multiplexer או ל-priority encoder. בבלוק מסונכרן לשעון הוא הופך ל-flip-flop עם לוגיקת עדכון מותנית.

בתוך בלוק צירופי

הבלוק הצירופי always @(*) רץ מחדש בכל פעם ש-a, b או c משתנים. שרשרת ה-if/else בוחרת ענף אחד, מבצעת השמה ל-max, והבלוק מסתיים. מכיוון ש-max מקבל השמה תמיד (בכל מסלול יש השמה), הסינתיסייזר מייצר לוגיקה צירופית טהורה, בלי latch.

שימו לב ש-max מוצהר כ-reg למרות שאין flip-flop בחומרה. אותו כלל כמו תמיד: כל מה שמקבל השמה בתוך always חייב להיות reg.

מלכודת ה-latch

זה הבאג הנפוץ ביותר בקוד צירופי של מתחילים:

// WRONG - infers a latch
always @(*) begin
    if (enable)
        out = data;
    // no else! when `enable` is low, what does `out` do?
end

הסינתיסייזר קורא "כש-enable נמוך, out לא מקבל השמה", ומחליט ש-out חייב לזכור את הערך הקודם שלו. כדי לזכור ערך צריך תא אחסון, ולכן הכלי מכניס latch. Latches בתכנונים סינכרוניים גורמים לבעיות תזמון, קשה לאפס אותם, וכמעט אף פעם הם לא מה שהתכוונתם.

שתי דרכים לתקן:

שתיהן מייצרות את אותה חומרה צירופית: mux של 2 ל-1. תבנית "ברירת מחדל בראש הבלוק" מתאימה יותר כשיש הרבה השמות מותנות לאותו אות.

בתוך בלוק מסונכרן לשעון

שימו לב מה שונה מהמקרה הצירופי:

  • הבלוק הוא always @(posedge clk): הטריטוריה של flip-flops.
  • ההשמה משתמשת ב-<= (לא חוסמת).
  • אין else למקרה של "לא reset ולא enable". זה בסדר. בבלוק מסונכרן לשעון, כששום ענף לא מופעל, ה-flip-flop פשוט מחזיק את הערך הקודם שלו, וזה בדיוק מה ש-flip-flop עושה פיזית. לא מוסק latch, כי האות כבר הוא אוגר.

זה המקום היחיד שבו השמטת else בטוחה. מחוץ לבלוקים מסונכרנים לשעון, תמיד טפלו בכל מסלול.

שרשרת else if: priority encoder

לשרשרת של הוראות else if יש עדיפות מובלעת: תנאים מוקדמים גוברים על מאוחרים:

ל-requests[0] יש העדיפות הגבוהה ביותר: אם הוא דולק, ה-grant הוא 0 בלי קשר למה שהביטים הגבוהים יותר עושים. הסינתיסייזר הופך את השרשרת ל-mux מדורג: בודק קודם את ביט 0, אחר כך את ביט 1, ביט 2 וביט 3. כל רמה מוסיפה קצת השהיה.

אם התנאים מוציאים זה את זה, למשל בפענוח כניסת one-hot, משפט case (המסמך הבא) מייצר חומרה שטוחה ומהירה יותר משרשרת else if. השתמשו בצורת ה-case כשאין דרישת עדיפות אמיתית.

if בלי else בקוד מסונכרן לשעון

בלוק מסונכרן לשעון לא צריך else, כי "להחזיק את הערך הקודם" היא ברירת המחדל. כך בונים enables:

always @(posedge clk) begin
    if (load) target <= incoming;
    // no else: when load is low, target keeps its value
end

זה אוגר עם load enable. רוב אוגרי ה-pipeline, המונים ואוגרי התצורה משתמשים בתבנית הזו.

begin/end והוראות בודדות

כמו ב-C, אפשר להשמיט את begin/end בהוראה בודדת:

if (a) out = 1;
else   out = 0;

לכל מה שיותר מהוראה אחת, השתמשו בבלוק:

if (a) begin
    out = 1;
    flag = 1;
end else begin
    out = 0;
    flag = 0;
end

שתי התבניות מתערבבות בחופשיות. מדריכי סגנון בדרך כלל ממליצים להשתמש תמיד ב-begin/end, כדי שהוספת הוראה שנייה לא תכאב.

מה הלאה

המסמך הבא, משפט case, עוסק ב-case, הכלי הנכון לפענוח רב כיווני (מכונות מצבים, ניתוב opcodes, טבלאות ROM). אחריו מגיעות לולאות for, ששונות בדקות מבנות הדודות שלהן בתוכנה, כי הן נפרשות בזמן ה-elaboration.

שאלות נפוצות

איך משפט if עובד ב-Verilog?

if (cond) statement; מריץ את statement כש-cond שונה מאפס. אפשר לעטוף כמה הוראות ב-begin ... end. מוסיפים else statement; לענף החלופי, או משרשרים עם else if (other_cond) .... if/else קיים רק בתוך בלוקים פרוצדורליים, initial או always, ולא ברמה העליונה של מודול.

מה זה latch מוסק ב-Verilog?

Latch שכלי הסינתזה יצר בלי שביקשתם, כי בלוק ה-always הצירופי שלכם לא ביצע השמה לאות בכל מסלול. הכלי רואה 'אם a אז out = 1' בלי else, מחליט שבמקרה שלא טופל צריך לזכור את הערך הקודם, ומייצר latch. Latches הם כמעט תמיד טעות; הפתרון הוא לתת לכל אות ערך ברירת מחדל בראש הבלוק או else מפורש.

איך נמנעים מ-latches מוסקים ב-Verilog?

בבלוק צירופי always @(*), ודאו שכל reg של פלט מקבל השמה בכל מסלול קוד. התבנית הנקייה ביותר היא לקבוע ערכי ברירת מחדל בראש הבלוק, ואז לדרוס אותם לפי תנאי. הקומפיילר בדרך כלל מתריע כשהוא מסיק latch: התייחסו לאזהרה כאל שגיאה.

מה נוצר בסינתזה משרשרת if-else ב-Verilog?

Priority encoder. ל-if הראשון יש העדיפות הגבוהה ביותר, ה-else if הבא נבדק רק אם הראשון שקרי, וכן הלאה. בחומרה זה הופך לשרשרת של muxes שסדר העדיפויות צרוב בה. אם התנאים מוציאים זה את זה, משפט case עם אותה לוגיקה עובר לעיתים קרובות סינתזה לחומרה שטוחה יותר ונקרא ברור יותר.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל