wire אחד מול הרבה
עד עכשיו כל אות שראינו היה ברוחב ביט אחד. תכנונים אמיתיים כמעט אף פעם לא נראים כך: כתובות הן של 16 או 32 ביט, bus-ים של נתונים הם של 8 או 64, ופיקסלים של RGB הם של 24. Verilog נותנת מנגנון אחד להפוך כל אות למרובה ביטים: מוסיפים טווח בסוגריים מרובעים.
wire [7:0] data; // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg [15:0] address; // 16-bit reg
output reg [31:0] result; // 32-bit module output
המספרים בסוגריים הם מיקומי הביטים של הביט המשמעותי ביותר ושל הפחות משמעותי. [7:0] אומר "לאות הזה יש ביטים שממוספרים מ-7 ועד 0", ובסך הכול יוצא 8 ביטים. המספר הראשון הוא האינדקס הגבוה. השני הוא הנמוך.
מדי פעם תראו [0:7] במקום: אותו מספר ביטים, עם endianness הפוך מבחינת חיתוך. הצורה [high:low] היא המוסכמה השולטת בתעשייה. היצמדו אליה אלא אם יש לכם סיבה טובה שלא.
דוגמה מלאה: מחבר של 8 ביט
כל + מחבר את שני הווקטורים של 8 ביט ומפיק תוצאה של 9 ביט. ליטרלים מספריים כמו 8'd10 אומרים "ערך עשרוני 10 ברוחב 8 ביט". נכסה אותם בליטרלים מספריים.
חיתוך: שליפת ביטים
כשיש לכם וקטור, אפשר לשלוף ממנו ביטים בודדים או טווחים רציפים:
אל תתעכבו על שורת ה-force ב-testbench: אנחנו רק צריכים דרך להזריק ערך כדי להדגים חיתוך. החלק המעניין הוא החיתוכים עצמם.
כמה כללים:
- כיוון החיתוך חייב להתאים להצהרה. אם הצהרתם
[7:0], חותכים עם[high:low]. היפוך הכיוון הוא שגיאת תחביר. - חיתוך מחוץ לטווח מפיק
x(לא ידוע) בסימולציה. כלי הסינתזה עשוי להזהיר או להחזיר שגיאה. - בחירת ביט מבוססת על האינדקס שכתבתם, החל מאפס:
data[0]הוא הביט שנקרא0, ובהצהרה של[7:0]זה ה-LSB.
חיתוכים עם בסיס משתנה: +: ו--:
צורך נפוץ: "תנו לי 8 ביטים החל מביט N". אי אפשר לכתוב ישירות data[N+7:N], כי Verilog דורשת ששני קצוות הטווח יהיו קבועים. התחביר שפותר את זה:
data[base +: width] // width bits starting at `base`, going UP
data[base -: width] // width bits starting at `base`, going DOWN
הרוחב קבוע (אנחנו בוחרים 8 ביטים בכל פעם), אבל הבסיס יכול להיות ביטוי שמחושב בזמן ריצה. זה בדיוק מה שצריך לזיכרונות עם כתובת לכל בית, ליציאות של shift register וכדומה.
מערכים: צעד מעבר לווקטורים
וקטור הוא אות אחד מרובה ביטים. מערך הוא אוסף של וקטורים שכל אחד מהם ממוען בנפרד:
reg [31:0] mem [0:1023];
בהצהרה הזו יש שני טווחים, ולכל אחד משמעות אחרת:
[31:0]הוא הממד ה-packed: הרוחב של כל מילה בודדת.[0:1023]הוא הממד ה-unpacked: כמה מילים יש.
כך ש-mem הוא 1024 רגיסטרים נפרדים של 32 ביט. ניגשים לאחד מהם עם אינדקס יחיד:
mem[5] = 32'hCAFE_BABE; // write word 5
data = mem[address]; // read the word at `address`
זה זיכרון קטנטן שמחזיק ריבועים. תכנונים אמיתיים משתמשים באותה תבנית כדי להחזיק register file, טבלאות חיפוש, FIFO וכל אחסון אחר על השבב שגדול מווקטור יחיד.
packed מול unpacked: למה זה חשוב
ההפרדה בין ממדים packed ל-unpacked מופיעה בכל מקום. לדעת מה זה מה חוסך הרבה דיבאג:
- וקטור packed הוא אות אחד. אפשר להתייחס אליו כולו כמספר:
data + 1עובד,data == 32'h0עובד,data[7:0]עובד. - מערך unpacked הוא הרבה אותות. אי אפשר להתייחס אליו כולו כמספר:
mem + 1הוא שגיאת תחביר. צריך קודם לבחור מילה מסוימת.
גם כמה ממדים packed הם חוקיים:
reg [3:0][7:0] regs; // 4 bytes packed together into a 32-bit signal
regs[0] הוא בית (הבית הנמוך). regs כולו הוא 32 ביט. SystemVerilog משתמשת בזה הרבה.
כמה ממדים unpacked יוצרים זיכרון דו-ממדי:
reg [31:0] frame [0:479][0:639]; // 480x640 of 32-bit pixels
ניגשים לפיקסל בודד עם frame[y][x]. כך ייראה buffer של תמונה ב-HDL.
מה הלאה
עכשיו אתם יודעים להצהיר על אות בכל רוחב שתצטרכו ולעבוד איתו. הדף הבא, פרמטרים, מראה איך להפוך את הרוחבים האלה לניתנים להגדרה, כך שאותו מודול יעבוד ב-8 ביט במופע אחד וב-32 במופע אחר. אחר כך נעבור לכללים לכתיבת מספרים ליטרליים (8'b1010_1100, 32'hDEAD_BEEF), ולערכי x/z שמופיעים בכל פעם שמשהו לא מוזן.
שאלות נפוצות
מה זה וקטור ב-Verilog?
וקטור הוא אות מרובה ביטים. מצהירים עליו על ידי הוספת טווח ל-wire או ל-reg: wire [7:0] data הוא wire של 8 ביט. המספרים בסוגריים הם מיקומי הביטים: במקרה הזה ביט 7 הוא המשמעותי ביותר וביט 0 הוא הפחות משמעותי. אפשר לחתוך ביטים בודדים (data[3]) או טווחים רציפים (data[7:4]).
מה המשמעות של [7:0] ב-Verilog?
[7:0] מצהיר על טווח מביט 7 ועד ביט 0, כולל: אות של 8 ביט שבו ביט 7 הוא הביט המשמעותי ביותר. המספר הראשון הוא האינדקס הגבוה, והשני הוא הנמוך. אפשר גם לכתוב [0:7] לאינדוקס little-endian, אבל הצורה [high:low] היא ללא ספק המוסכמה הנפוצה יותר בקוד בתעשייה.
איך חותכים ביטים ב-Verilog?
משתמשים באינדוקס בסוגריים מרובעים. data[3] בוחר ביט אחד. data[7:4] בוחר את ארבעת הביטים העליונים כווקטור של 4 ביט. החיתוך חייב להיות באותו כיוון כמו ההצהרה: אם הצהרתם [7:0], חתכו עם [high:low]. SystemVerilog מוסיפה גם data[3 +: 4] לחיתוכים ברוחב קבוע עם בסיס משתנה.
מה ההבדל בין מערך packed למערך unpacked ב-Verilog?
מערך packed הוא bus רציף אחד: reg [31:0] word הוא אות אחד של 32 ביט. מערך unpacked (או 'זיכרון') הוא אוסף של מילים עצמאיות: reg [31:0] mem [0:1023] הוא 1024 רגיסטרים נפרדים של 32 ביט. אפשר לקרוא או לכתוב מילה שלמה של מערך unpacked, אבל אי אפשר לפעול על כולו כאות אחד.
איך מצהירים על זיכרון ב-Verilog?
reg [31:0] mem [0:1023]; מצהיר על זיכרון של 1024 רשומות, כל אחת ברוחב 32 ביט. זוג הסוגריים הראשון הוא רוחב המילה (packed), והשני הוא מספר המילים (unpacked). ניגשים לרשומה עם mem[address], ואפשר לקרוא או לכתוב חלק מהרשומה עם mem[address][7:0] כשאינדוקס של SystemVerilog-2005 מופעל.