Menu

Verilog always block: לוגיקה צירופית וסדרתית

איך בלוקים של always עובדים, מה ההבדל בין always @(*) צירופי לבין always @(posedge clk) שמסונכרן לשעון, והכללים שקובעים איזו חומרה כל אחד מייצר.

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

סוס העבודה של Verilog התנהגותי

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

יש שתי צורות של always שתפגשו הכי הרבה:

  1. always @(*): רץ מחדש כש_כל_ אות שנקרא בבלוק משתנה. בונה לוגיקה צירופית.
  2. always @(posedge clk): רץ מחדש רק בקצה העולה של clk. בונה לוגיקה סדרתית מסונכרנת לשעון (flip-flops).

קיימות צורות נוספות (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)), אבל שתי הצורות שלמעלה מכסות כמעט כל בלוק בר סינתזה שתכתבו.

always @(*) צירופי

שלושה דברים שכדאי לשים לב אליהם:

  • out הוא reg. כל מה שמקבל השמה בתוך always חייב להיות reg. מילת המפתח לא אומרת "זה flip-flop"; כאן היא אומרת רק "אני נכתב מתוך בלוק פרוצדורלי".
  • always @(*). ה-* אומר "תתעורר בכל פעם שמשהו שאני קורא משתנה". הסימולטור מחשב את רשימת הרגישות בעצמו. אפשר לכתוב את הרשימה ידנית, always @(sel), אבל @(*) בטוח יותר, כי אות שנשכח ברשימה הוא מקור קלאסי לבאגים.
  • אין שעון. הבלוק הזה מתאר לוגיקה צירופית. הסינתיסייזר מייצר לוגיקה שמחשבת את out ישירות מתוך sel, בלי flip-flops ובלי צורך בפין שעון.

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

always @(posedge clk) סדרתי

ההבדלים העיקריים לעומת הגרסה הצירופית:

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

הסינתיסייזר מזהה את הצורה הזו, רגישות לשעון והשמה לא חוסמת, ומייצר אוגר של 4 ביטים (ארבעה flip-flops), יחד עם הלוגיקה הצירופית שמחשבת את count + 1 וה-mux שבוחר בין reset להגדלה.

ההבחנה בסינתזה

אותו קוד מקור של מודול יכול לתאר שני רכיבי חומרה שונים לגמרי, לפי הצורה של בלוק ה-always:

בלוקחומרה
always @(*) y = expr;לוגיקה צירופית טהורה. בלי זיכרון.
always @(posedge clk) y <= expr;Flip-flop. לוכד את expr פעם אחת בכל מחזור שעון.
always @(*) if (en) y = expr;Latch, בדרך כלל באג. במקרה ה-"else" נשמר הערך הישן.
always @(posedge clk) if (en) y <= expr;Flip-flop עם enable. לוכד רק כש-en גבוה.

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

גרסאות של רשימת הרגישות

תפגשו גם כמה רשימות רגישות פחות נפוצות:

  • always @(a or b or c): רשימה מפורשת. Verilog-2001 הוסיפה את המפריד ,: always @(a, b, c). שתי הצורות עובדות.
  • always @(posedge clk or negedge reset_n): reset אסינכרוני. הבלוק רץ בקצה עולה של השעון או בקצה יורד של ה-reset. משתמשים בזה כשה-reset חייב להשפיע מיד, בלי לחכות לשעון הבא.
  • always @(negedge clk): סנכרון לקצה היורד. נדיר; יש תכנונים שמשתמשים בזה ל-flip-flops "מופעלי קצה יורד" שלוכדים בקצה היורד במקום בעולה.

בתכנונים חדשים העדיפו always @(*) ללוגיקה צירופית ו-always @(posedge clk) ללוגיקה סדרתית. השתמשו ב-reset אסינכרוני רק כשהתכנון באמת צריך אותו.

שני בלוקים הם שני רכיבי חומרה

כמה בלוקים של always באותו מודול הם בלתי תלויים, וכל אחד מהם הופך לרכיב חומרה משלו:

הבלוק המסונכרן לשעון מייצר אוגר של flip-flops. הבלוק הצירופי מייצר שער XOR. הם חיים זה לצד זה, ואף אחד מהם לא יודע על השני. שני הפלטים משתנים בלוחות זמנים שונים לגמרי.

מה בלוקים של always לא יכולים לעשות

כמה דברים שנראים מפתים אבל אסורים:

  • השמה ל-wire: היעד חייב להיות reg. הקומפיילר אוכף את זה.
  • השמה לאותו reg משני בלוקים שונים של always: יוצרת התנהגות לא מוגדרת בסימולציה ולא עוברת סינתזה. מקור הזנה אחד לכל אות.
  • קריאה וכתיבה של אותו אות באותו בלוק צירופי באופן שיוצר לולאת משוב: always @(*) x = x + 1; היא לולאה באפס השהיה שהסימולטור לא יכול לפתור.

את שני הראשונים הקומפיילר תופס. השלישי מופיע לפעמים רק בזמן הסימולציה, בצורה של תקיעה.

מה הלאה

המסמך הבא, בלוק initial, עוסק באח של always: בלוק שרץ בדיוק פעם אחת בתחילת הסימולציה. הוא סוס העבודה של ה-testbenches. אחריו מגיעים הכללים של השמה חוסמת מול לא חוסמת, שקובעים אם הבלוק המסונכרן שלכם עושה את מה שהתכוונתם.

שאלות נפוצות

מה זה בלוק always ב-Verilog?

always פותח בלוק פרוצדורלי שרץ מחדש בכל פעם שהאותות ברשימת הרגישות שלו משתנים. יש שני סוגים: always @(*) בונה לוגיקה צירופית (רץ מחדש כשכל כניסה שהיא משתנה), ו-always @(posedge clk) בונה לוגיקה סדרתית (רץ מחדש בכל קצה עולה של clk). גוף של בלוק always יכול להכיל if, case, for והשמות פרוצדורליות.

מה ההבדל בין always @(*) לבין always @(posedge clk)?

always @(*) רגיש לכל אות שנקרא בבלוק, והוא מייצר לוגיקה צירופית בלי זיכרון. always @(posedge clk) רגיש רק לקצה העולה של clk, והוא מייצר flip-flops שלוכדים מצב פעם אחת בכל מחזור שעון. לראשון אין שעון ואין אוגר, לשני יש את שניהם.

מה זו רשימת רגישות ב-Verilog?

רשימת האותות אחרי @ שקובעת מתי בלוק always רץ מחדש. @(*) הוא קיצור של 'כל אות שנקרא בבלוק'. @(posedge clk) רץ רק בקצה העולה של clk. @(posedge clk or negedge reset_n) רץ בכל אחד משני האירועים, ומשמש ל-reset אסינכרוני. טעות ברשימת הרגישות היא אחד המקורות הנפוצים ביותר לפער בין סימולציה לסינתזה.

אפשר לבצע השמה ל-wire בתוך בלוק always?

לא. בלוקים של always יכולים לבצע השמה רק ל-reg (או ל-logic ב-SystemVerilog), והקומפיילר אוכף את זה. אם רוצים ש-wire יהיה הפלט של לוגיקה פרוצדורלית, מצהירים על reg ביניים, מזינים אותו בתוך always, ומחברים את ה-wire ל-reg עם assign מחוץ לבלוק. או פשוט הופכים את ה-wire ל-reg.

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

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

להתחיל