לוגיקה של שני מצבים מול ארבעה מצבים
בתוכנה, ביט הוא 0 או 1. ב-Verilog, ביט יכול לקבל אחד מ_ארבעה_ ערכים:
0: ה-wire מוזן לנמוך.1: ה-wire מוזן לגבוה.x: הערך של ה-wire לא ידוע. הסימולטור לא יכול לדעת.z: ה-wire נמצא בעכבה גבוהה. שום דבר לא מזין אותו.
מודל ארבעת המצבים קיים כי לחומרה יש בדיוק אותה בעיה. wire אמיתי יכול להיות מחובר לנמוך, מחובר לגבוה, לא מוגדר (מוזן משני מקורות שנאבקים זה בזה) או צף (בלי מקור הזנה בכלל). הסימולטור חייב למדל את כל הארבעה כדי להיות שימושי.
איך x מופיע
הריצו את זה והסתכלו על הפלט:
a מוצהר אבל אף פעם לא נכתב, ולכן הוא נשאר x. החישוב x + 5 מפיק x: כל חשבון עם ערך לא ידוע מפיק ערך לא ידוע. בפלט תראו x במקום כל ביט, כלומר aaaa שלם של x-ים.
מקורות נפוצים של x בתכנונים שלכם:
regשמוצהר אבל אף פעם לא עובר reset (רוב קוד ה-Verilog שמיועד לסינתזה משתמש ב-reset מפורש כדי לאפס אותם).- פקודת
caseבליdefault, שמקבלת ערך כניסה שאף מקרה לא התאים לו. wireשאיבד את מקור ההזנה היחיד שלו אחרי refactor.- שרשרת
if/elseשבה ענף אחד לא מבצע השמה לאות שהענף האחר כן מבצע (נוצר latch עםxאם לא מכוסה). - קריאה מעבר לסוף של וקטור או מערך.
התפשטות X: קצת x הורס הכול
הדבר האכזרי ב-x הוא שהוא מתפשט. ביט x אחד באופרנד הופך את כל התוצאה ל-x:
שימו לב ש-0 & x הוא 0 (AND עם 0 הוא תמיד 0) ו-1 | x הוא 1 (OR עם 1 הוא תמיד 1). הסימולטור פסימי ברמת הביט, אבל עדיין מכבד זהויות. פעולות חשבון והשוואה לא נדיבות כל כך.
זו הסיבה שרגיסטר אחד שלא אותחל יכול לגרום ל-bus פלט שלם להפוך ל-xxxx. עקבו אחורה מכל x ותמצאו את המקור.
איך z מופיע
z הוא הערך של wire שאף אחד לא מזין:
שתי תבניות בקטע הזה:
floatingרק מוצהר ואף פעם לא מוזן. ערך ברירת המחדל שלו הואz.data_outהוא tri-state מכוון. כש-enableנמוך, היציאה משתחררת במפורש ל-z. כך מזין של bus "מרפה" כדי שמזין אחר יוכל להשתלט.
בלוגיקה פנימית, z כמעט תמיד שגוי. בפין דו-כיווני או ב-bus משותף, z הוא בדיוק מה שצריך.
השוואה עם == מול ===
אופרטור השוויון הרגיל == מחזיר x כשלאחד האופרנדים יש ביט x או z:
=== (ובן זוגו !==) מבצע השוואה קפדנית ביט אחר ביט, כולל x ו-z. השתמשו בו בכל פעם שצריך לבדוק ב-testbench אם ערך הוא x/z או לא. === לא ניתן לסינתזה, אבל בתוך בלוק initial ב-testbench זה לא משנה.
פונקציית המערכת $isunknown(expr) היא הדרך הנקייה ביותר לשאול "האם יש בביטוי הזה ביטים של x או z?": היא מחזירה 1 אם כן ו-0 אם לא.
שימוש ב-x כ-don't-care מכוון
תבנית שנויה במחלוקת אבל לגיטימית: 'x במקרה ה-default של מכונת מצבים אומר לכלי הסינתזה "המצב הזה לא ניתן להשגה, תבצע אופטימיזציה בחופשיות":
case (state)
IDLE: next_state = go ? RUNNING : IDLE;
RUNNING: next_state = done ? IDLE : RUNNING;
default: next_state = 'x; // unreachable
endcase
כלי הסינתזה יכול להשתמש ב-x כדי למזג מצבים ולהקטין את מספר השערים. בסימולציה, אם ההיגיון שלכם היה שגוי וה-default כן מושג, תראו x מתפשט מ-next_state והבאג הופך לגלוי מיד.
השתמשו בזה רק אחרי שחשבתם היטב אם ה-default באמת לא ניתן להשגה. אם לא, הגדירו את ה-default למצב בטוח.
מתכון נפוץ לדיבאג
אתם בוהים בצורת גל מלאה ב-x. המתכון:
- מצאו את ה-
xהמוקדם ביותר. לכו אחורה בזמן בצורת הגל. האות הראשון שהפך ל-xהוא הקרוב ביותר למקור. - מצאו את מקור ההזנה שלו. פתחו את קוד המקור. מה מבצע השמה לאות הזה?
assign? בלוקalways? - בדקו את הכניסות של מקור ההזנה. אם בצד הימני של מקור ההזנה יש
x, ההתפשטות עושה בדיוק את מה שהיא אמורה לעשות: הבאג נמצא במעלה הזרם. - אם למקור ההזנה יש כניסות נקיות אבל הוא מפיק
x, מקור ההזנה לא שלם.caseבלי default,ifבליelse, רגיסטר בלי reset.
רוב הבאגים של סערת x מסתכמים באחד מאלה: reset חסר, default חסר, או תת-מודול שלא מחובר.
מה הלאה
עכשיו יש לכם את כל התמונה של טיפוסי הנתונים: wire/reg, וקטורים, פרמטרים, ליטרלים מספריים ומודל הלוגיקה בעל ארבעת המצבים. הפרק הבא מתחיל להשתמש בכל זה כדי לבנות ביטויים: אופרטורים מכל הסוגים, כולל האופרטורים ברמת הביט שלא היה להם היגיון בתוכנה.
שאלות נפוצות
מה המשמעות של x ב-Verilog?
x הוא הערך הלא ידוע. אות שערכו x יכול להיות 0 או 1, והסימולטור לא יכול לדעת. הוא מופיע כשאות לא מוזן, כששני מקורות הזנה מתנגשים, כשקוראים רגיסטר לפני reset, ובכל מקום שבו התנהגות לא מוגדרת הייתה מתפשטת אחרת בשקט. התייחסו ל-x כאל סימן לבאג: הוא כמעט אף פעם לא אומר את מה שאתם רוצים.
מה המשמעות של z ב-Verilog?
z הוא ערך העכבה הגבוהה: ה-wire לא מוזן בכלל. זה המצב הלגיטימי של יציאות tri-state (bus-ים של נתונים, פינים דו-כיווניים), אבל באותות פנימיים z הוא בדרך כלל טעות שמשמעותה 'שום דבר לא מחובר כאן'. כלי סינתזה דוחים את רוב התבניות של z מחוץ לאפשור מפורש של יציאת tri-state.
למה הפלט שלי ב-Verilog הוא xxxx?
כמעט תמיד כי אות לא מוזן על ידי שום דבר, או כי פעולה הפיצה x מאות אחר. לכו אחורה: איזה אות הוא x, מה מזין אותו, והאם מקור ההזנה פעיל? החשודים הנפוצים הם מקרי default חסרים בפקודות case, רגיסטרים שלא עוברים reset, ו-wire-ים שאיבדו את מקור ההזנה שלהם אחרי refactor.
איך בודקים x או z ב-Verilog?
משתמשים באופרטור ===, שמשווה ביט אחר ביט באופן מדויק כולל x ו-z. a === 1'bx אמת כש-a הוא באמת x. האופרטור הרגיל == מחזיר x כשלאחד האופרנדים יש ביט x, ולכן a == 1'bx אף פעם לא נותן את התשובה שאתם רוצים. יש גם $isunknown(a), שמחזיר ערך בוליאני מסודר.
האם אפשר להשתמש ב-x כברירת מחדל ב-Verilog?
כן, וזו טכניקה מכוונת בפקודות case: default: out = 'x; אומר לכלי הסינתזה 'אני מבטיח שהמקרה הזה לא קורה, תבצע אופטימיזציה בחופשיות'. המחיר הוא שאם זה כן קורה בסימולציה, x מתפשט ואתם רואים את הבאג. השתמשו בזה כשכבר הוכחתם שה-default לא ניתן להשגה, לא כדרך להימנע מכתיבת המקרה.