assign מתאר אמת קבועה
הצהרת wire יוצרת אות. assign מתאר מה מזין אותו. הקשר רציף: מה שנמצא בצד ימין, צד שמאל שווה לו, בכל נקודה בזמן המדומה:
wire y;
assign y = a & b;
שני דברים משתמעים מזוג השורות האלה:
yקיים כ-wire במעגל.yהוא בכל רגע ה-AND ביט אחר ביט שלaו-b. משנים אחת מהכניסות, ו-yעוקב.
אין שעון ואין אירוע שמפעיל את העדכון. הסימולטור רואה ש-a או b השתנו, מסמן את y כמלוכלך, ומחשב מחדש את הביטוי. בחומרה זה ממופה לכמה שערי AND: צירופי, בלי מצב, מיידי.
הצורה המובלעת
אפשר לשלב את ההצהרה ואת ההשמה בשורה אחת:
wire y = a & b;
זה זהה לשתי השורות שלמעלה. שימושי ל-wires מקומיים שאף אחד מחוץ לתחום הזה לא מתעניין בהם. הרבה מדריכי סגנון דווקא מעדיפים את הצורה המובלעת, כי היא שמה את ההצהרה ממש ליד המשוואה.
מה assign יכול להזין
היעד של assign חייב להיות מסוג net: ב-Verilog רגיל זה wire (או אחד מבני הדודים הנדירים יותר כמו tri, wand, wor). הוא לא יכול להיות reg. אם הצהרתם בטעות על היעד כ-reg:
reg y;
assign y = a & b; // ERROR: cannot drive reg with assign
הקומפיילר יגיד לכם. או שמשנים את היעד ל-wire, או שמעבירים את הלוגיקה לבלוק always @(*), שבו reg הוא היעד החוקי.
צד ימין יכול להיות כל דבר שמחושב לערך: ליטרלים, אותות, פרמטרים, ביטויי אופרטורים, קריאות לפונקציות. הוא יכול לערבב כמה רוחבי כניסה; חלים כללי ההרחבה הסטנדרטיים.
מתי להשתמש ב-assign ומתי ב-always
שניהם יכולים לייצר לוגיקה צירופית. הבחירה היא בעיקר עניין של איך הקוד נקרא:
assignהכי מתאים כשהקשר הוא ביטוי יחיד. מחברים, muxes פשוטים עם?:, מסכות, ביטי parity, כל מה שאפשר לכתוב בשורה אחת.always @(*)הכי מתאים כשצריך הוראות פרוצדורליות. משפטיcaseרב כיווניים,if/else ifמקוננים, כל דבר שמרוויח מ-regs ביניים עם שמות. אנחנו מכסים את זה ב-בלוק always.
הנה אותו mux של 4 ל-1 כתוב בשתי הדרכים:
שני המודולים עוברים סינתזה לבערך אותו multiplexer. גרסת ה-assign היא שורת קוד אחת; גרסת ה-always היא שש. בארבעה מקרים זה קרוב; בשישה עשר מקרים בלוק ה-case ברור יותר לקריאה.
תבניות נפוצות
לוגיקה צירופית פשוטה
assign sum = a + b;
assign carry = a[7] & b[7];
assign equal = (data == 8'hFF);
ביטוי אחד, wire אחד. הלחם והחמאה של assign.
mux של 2 ל-1
assign out = sel ? a : b;
ביטוי תנאי יחיד: הסינתיסייזר הופך אותו ל-mux יחיד של 2 ל-1. הכתיב הנקי ביותר האפשרי ל"בחר בין a ל-b".
אריזת ביטים
assign status = {error, overflow, ready, busy, 4'b0};
שרשור בצד ימין של assign הוא הדרך לארוז דגלים לבייט סטטוס. התוצאה מחושבת ומוזנת ברציפות.
פלט tri-state
assign data_pin = output_enable ? data_out : 1'bz;
כש-output_enable גבוה, מזינים את הפין. כשהוא נמוך, משחררים לעכבה גבוהה. זו התבנית הקנונית בפינים של שבב, שבהם כמה מקורות הזנה עשויים לחלוק wire.
מקביליות: כמה assign לא רצים לפי סדר
תזכורת שלא תפסיק להיות רלוונטית: כמה הוראות assign באותו מודול כולן רצות במקביל. הן לא רצף:
assign y = a & b; // exists at all times
assign z = a | b; // also exists at all times, independently
הסדר בקובץ לא משנה. שתי המשוואות נכונות בו זמנית. הסינתיסייזר עשוי למקם את שער ה-AND לפני שער ה-OR או אחריו; זה לא משנה, שני השערים פועלים ברציפות.
אם אתם רוצים התנהגות שנראית סדרתית, אתם מחפשים בלוק always (וכנראה שעון). זה פרק אחר.
כמה מקורות הזנה: תבנית האפיק
ל-wire יכול להיות יותר מ-assign אחד שמכוון אליו, אבל כמעט אף פעם לא רוצים את זה, חוץ מאפיקי tri-state. שני מקורות הזנה שנלחמים על wire יוצרים התנהגות לא מוגדרת:
assign y = a;
assign y = b; // BAD - two drivers, simulator picks one or x's it out
התבנית הלגיטימית: כל מקור הזנה משחרר ל-z כשהוא לא פעיל, ולכל היותר אחד פעיל בכל רגע.
assign bus = device_a_active ? data_from_a : 1'bz;
assign bus = device_b_active ? data_from_b : 1'bz;
זה עובד כי בכל רגע נתון, לכל היותר אחד משני הביטויים התלת מקומיים מפיק ערך שאינו z. הערך בפועל של ה-wire הוא של מקור ההזנה שלא משחרר.
בלוגיקה פנימית, כלומר בכל מקום שאינו פין של שבב או אפיק משותף על השבב, מקור הזנה אחד לכל wire. באגים של כמה מקורות הזנה מגעילים לדיבוג.
מה assign לא יכול לעשות
יש דברים ש-assign הוא הכלי הלא נכון בשבילם:
- אחסון.
assignמתאר קשרים צירופיים; הוא לא יכול להכניס flip-flop. אם צריך שערך ייזכר בין מחזורי שעון, זה בלוקalways @(posedge clk). - לוגיקה פרוצדורלית רב שלבית. אי אפשר לכתוב
if/elseאוcaseבתוךassign. הכי קרוב שאפשר להגיע הוא שרשרת?:, שנעשית מכוערת אחרי שלושה ענפים. - הזנת אוגרים מתוך בלוק פרוצדורלי. יעדי
regצריכים השמה פרוצדורלית, לאassign.
הכרת הגבולות היא מה שעוזר להחליט מתי לעבור ל-always.
מה הלאה
ראיתם עכשיו את כל הצד המבני של Verilog: הצהרה על מודולים, יצירת מופעים שלהם, וחיווט לוגיקה צירופית עם assign. הפרק הבא עובר לבלוקים פרוצדורליים: המבנים initial ו-always, שבהם זמן וסדר מתחילים להיות חשובים.
שאלות נפוצות
מה זו השמה רציפה ב-Verilog?
assign target = expression; מצהיר על קשר קבוע ורציף: target תמיד שווה ל-expression. בכל פעם שאות כלשהו בביטוי משתנה, הסימולטור מחשב מחדש את צד ימין ומעדכן את target. אין שעון ואין אירוע: הקשר נכון בכל נקודה בזמן.
לאילו יעדים אפשר לבצע assign ב-Verilog?
assign יכול להזין wire, אבל אף פעם לא reg. היעד חייב להיות מסוג net. אם צריך לבצע השמה למשהו בתוך בלוק always, מצהירים עליו כ-reg. הקומפיילר ידחה assign x = ... אם x הוא reg, וידחה x = ... בתוך always אם x הוא wire.
מתי להשתמש ב-assign ומתי בבלוק always?
השתמשו ב-assign ללוגיקה צירופית פשוטה: ביטוי אחד נכנס, אות אחד יוצא, בלי צורך ב-if/else. השתמשו ב-always @(*) כשהלוגיקה צריכה הוראות פרוצדורליות (case, שרשרת if/else if, לולאת for). שניהם מייצרים חומרה צירופית; הבחירה היא עניין של קריאות.
אפשר לבצע כמה assign לאותו wire ב-Verilog?
רק אם ממדלים אפיק tri-state שבו כל מקור הזנה משחרר את ה-wire ל-z כשהוא לא פעיל. שני assign שמנסים להזין את ה-wire לערכים מוגדרים באותו זמן יוצרים התנגשות: הסימולטור עשוי לבחור אחד, או להפוך את האות ל-X, תלוי בכלי. בלוגיקה צירופית רגילה, מקור הזנה אחד לכל wire.