פרוייקט חומרה פתוחה


מדריך Verilog

מדריך זה הוא חלק מאתר חומרה פתוחה
שפת ורילוג היא שפת תיאור חומרה (HDL), כלומר משתמשים בה כדי לתאר מערך של רכיבים אלקטרוניים. התיאור יכול להיעשות ברמת השערים הלוגיים (gate level), ברמת הרגיסטרים (register level), או ברמה ההתנהגותית (behavioral level). כל אחת מרמות אלו היא רמה שונה של הפשטה, כאשר רמת השערים היא הפחות מופשטת, והרמה ההתנהגותית היא המופשטת ביותר.
רמת הרגיסטרים (RTL) היא הרמה שבה נהוג בדרך כלל לתאר מעגלים. ברמה זאת לא נכנסים לפירוט של השערים אלא משתמשים ברכיבים האלקטרוניים הלוגיים הסטנדרטים, בדר"כ מתוך ספריות מוכנות מראש.
לאחר כתיבת קוד הורילוג מריצים את הקוד על ידי תוכנת סימולציה כדי לודא שהמעגל מבצע את המשימות שהוא אמור לבצע, ומבצעים סינטזה על ידי תוכנה יעודית שמממשת מעגל אשר מקיים את דרישות הקוד.

תוכן
יסודות
    סוגי הנתונים בשפה
    סימון מספרים בשפת ורילוג
    הצבה
    אופרטורים
הצהרות שונות
    תנאי if
    תנאי case
    לולאת forever
    בלוק begin/end
    בלוק fork/join
מבנים בשפה
    שערים
    בלוק always
    בלוק initial
מודול
מידול השהיות
משימות מערכת
קישורים חיצוניים


יסודות
הערות בטקסט בשפת verilog מסומנות באופן דומה להערות בשפת C, באופן הבא:  /*<comment>*/ . בנוסף, ניתן על ידי הסימון // להגדיר את המשך השורה מהסימן והלאה כהערה.
סימנים מותרים בשפה (לשמות): אותיות, ספרות, קו תחתון, $. השפה היא case sensitive.

סוגי הנתונים בשפה
נתונים מסוג רשת (net): צמתים או חוטים במעגל שאנו רוצים לסמן בשם כדי שיהיה נוח להשתמש בהם, כוללים שלושה סוגי משתנים: wire, wand, wor. כאשר wand=wire+and, wor=wire+or. משתנים אלו מתנהגים כמו חוט פיזי, כלומר כדי שיהיה להם ערך כלשהו צריך להציב בהם ערך באופן רציף.

נתונים מסוג רגיסטר: סוג אחד של משתנה: reg. ברגע שמציבים ערך במשתנה מסוג רגיסטר, הוא מחזיק אותו עד שמציבים בו ערך חדש.

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

wire Y1;
reg Y2;
הצהרה על סקלר
wire [2:0] Y3;
reg [9:0] Y4;
הצהרה על ווקטור
Y4[8]=Y2;
דוגמה לשימוש

חשוב לשמור על עקביות בהצהרת הטווח, [9:0] לא שקול ל- [0:9].

סוגי פורטים: כל אחד מהנתונים מסוג רשת או רגיסטר יכול להיות נגיש ככניסה לרכיב (input), יציאה מהרכיב (output) או ככניסה\יציאה (inout). על פורטים מסוג רגיסטר יש להצהיר פעמים (פעם כפורט ופעם כרגיסטר), ועל פורטים מסוג רשת מספיק להצהיר כפורט.

input A,B,Cin, clk;
output sum, Cout; 

reg A,B,Cin;         

דוגמה: פורטים עבור full adder
סינכרוני. בכניסות לרכיב יש רגיסטרים


integer: משתנה כללי לשימוש בלולאות, ללא מימוש ספציפי קבוע בשלב הסינטזה. מחזיק ערך מספרי, ללא טווח, בעל סימן לפי שיטת המשלים ל- 2.

integer N;
הצהרה

פרמטרים: קבוע מסוג integer.

parameter A=4'b1001;
הצהרה

ההצבה לתוך הפרמטר A התבצעה על פי קונבנציות סימון המספרים בשפה (מוסבר להלן).


סימון מספרים בשפת ורילוג
קבוע מספרי ניתן לסמן בבסיס בינארי, אוקטלי, עשרוני (ברירת המחדל) או הקסדצימלי. ניתן ונהוג לציין את גודל הקבוע המספרי בביטים. לקו תחתון (underscore, _ ) אין השפעה על הערך ומשתמשים בו כדי להפוך את המספר לקריא יותר.

<size>'<radix><value>
תחביר
 4'b1001
16'h8AD1
3_1415
דוגמאות

הערות
סימוני הבסיסים: b - בינארי, o - אוקטלי, d - דצימלי,  h - הסקדצימלי. מותר להכניס רווחים בין הגודל לבסיס לערך המספר.
ברירת המחדל לגודל המספר הוא 32 ביט, כך שהמספר שבדוגמה האחרונה ישמר כך: 00000000000000000000000000031415
כאשר ערך המספר חורג מגודל המספר שהוצהר עליו, הביטים השמאליים (המשמעותיים יותר) של המספר מקוצצים לפי מספר הביטים המוצהרים.
כאשר ערך המספר קטן מהגודל המוצהר, המספר ירופד משמאל באפסים אם הביט המשמעותי הוא 0 או 1, ב-Z-ים אם הביט המשמעותי הוא Z, וב-X-ים אם הביט המשמעותי הוא X.
שני הסימנים הללו (Z,X) מחליפים ביט במקרה שערכו בלתי ידוע (X), או שהוא באימפדנס גבוה (Z).

הצבה
שפת ורילוג היא, כאמור, שפת תיאור חומרה, ולפעולה של רכיבי חומרה יש בדרך כלל אופי מקבילי. אך להוצאה לפועל של תוכניות יש אופי טורי. כדי להתגבר על ההבדל הזה ורילוג מציעה מספר טכניקות, וביניהן האפשרות להשתמש באחת משני סוגי הצבה.
הראשונה היא מסוג nonblocking והיא נעשית באמצעות סימן שיוויון = , ההצבה תתבצע שורה- שורה (אופי טורי):

a=3;
b=4;
דוגמה
ההצבה לתוך המשתנה a מתבצעת לפני ההצבה למשתנה b.

ההצבה השנייה היא מסוג blocking ובאמצעות סימן ההצבה <= . בזמן הסימולציה כל ההצבות הללו מתבצעות "בבת אחת", כלומר תוכנת הסימולציה שומרת את כל הערכים הרלוונטיים לפני ביצוע כל ההצבות, ומבצעת את ההצבות (עדיין בטור, בכל זאת, זאת תוכנה) ללא תלות האחת בשנייה.

בשלב מסויים בקוד נציב ברגיסטר
a=2'b11;
מאוחר יותר נבצע את ההצבות
a<=2'b00;
b<=a;
דוגמה
בסיום הביצוע של הדוגמה, המשתנה b יכיל את הערך הקודם של a, כלומר 11 ולא 00. זאת מכיוון שההצבה לתוך שני המשתנים a,b ממודלת כהצבה במקביל. אם היינו משתמשים בסימן שיוויון בהצבות אלו הערך במשתנה b היה הערך החדש של a כלומר 00.

טכניקה נוספת המשמשת כדי להוציא פקודות לפועל באופן מקבילי היא שימוש בהצהרה fork/join.

אופרטורים
מפורטים בטבלה הבאה
אופרטור
סימן
שלילה לוגית
!
שלילה (bit wise)
~
reduction and
&
reduction or
|
reduction nand
&~
reduction nor
|~
xor ^
xnor ^~ או ~^
שרשור {}
הכפלה {{}}
כפל *
חילוק
/
מודולו %
הזזה לוגית שמאלה
>>
הזזה לוגית ימינה
<<
שוויון לוגי
==
אי שוויון לוגי
=!
case equality
===
case inequality
==!
logical and
&&
logical or
||
bit-wise and
&
bit-wise or
|
bit-wise xor
^
bit-wise xnor
^~ או ~^
משפט תנאי (תחביר כמו בשפת C)
: ?

הערות
אופרטורים חשבוניים שלא הכנסתי לטבלה: +, -, <, >, =< וכו'. לאופרטורים האונריים +, - יש עדיפות על פני האופרטורים הבינאריים +, -.
לא מומלץ להשתמש באופרטורים מתמטיים (%, /, *) כאשר התיאור נכתב לשם סינטוז, עדיף להשתמש ברכיבים שמיועדים לממש את הפעולות האלה- לשם יעילות.


הצהרות שונות

תנאי if
תחביר דומה לתחביר בשפת c.

if (<condition>)
    <statement>;
else if ............
else ..............
תחביר
if (A1==2'b00) 
Y1=2'b11;
else Y1=2'b00;
דוגמה
הסוגריים בתנאי הם חובה.


תנאי case
מאפשר לבצע פעולה שונה עבור כל ערך אפשרי של משתנה:

case (<variable>)                             
<value1>: <statement>;           
..                                          
<valueN>: <statement>;           
default: <statement>;  //optional
endcase                                         
תחביר
case (ctrl)               
1'b0: out=in1;
1'b1: out=in2;
endcase               
דוגמה: מרבב

כעקרון, תנאי case מחפש התאמה מלאה בין המשתנה הנבדק לבין הערך באופציות השונות לפני שהוא בוחר איזה אופציה לבצע, כולל ערכי X ו- Z (לא ידוע, ואימפדנס גבוה). שני וריאנטים של case מחפשים התאמה פחות קשיחה, casez מתייחסת לכל Z כ- ?, כלומר לא רלוונטי\משפיע. casex מתייחסת גם ל- Z וגם ל- X כלא רלוונטים להשוואה. התחביר של שני הוריאנטים הללו הוא זהה לתחביר של case. הערה: שימוש בשני אלו יכול לגרום לבאגים בקלות אם לא נעשה בהם שימוש זהיר ומושכל.


לולאת forever
הפקודות המופיעות לאחר הצהרה זאת מתבצעות "לנצח" (בסימולציה: עד סיום הסימולציה, למשל כאשר הסימולטור מגיע למשימת המערכת finish$)

forever <statement>;
תחביר
clk=1'b1;                
forever #5 clk=~clk;
דוגמה: מידול שעון
מידול שעון הוא הדוגמה הקלאסית לשימוש בלולאת forever, בדר"כ פקודות אלו יוכנסו לבלוק initial כדי שביצוען יחל בתחילת הסימולציה. הסימון #5 מורה על השהיה של 5 יחידות זמן, הסבר מורחב אפשר למצוא בפרק על מידול השהיות.

בלוק begin/end
מילות קוד אלו יוצרות בינהן בלוק שההתייחסות אליו היא כהצהרה בודדת, בדומה לתפקיד של הסוגריים המסולסלים {} בתוכנית בשפת C.


בלוק fork/join
משמש כתחליף לסימן ההצבה <= (הצבה מסוג blocking). כל הפקודות או הבלוקים בין פקודת fork לפקודת join מוצאים לפועל "סימולטנית" (בסימולציה, כל הערכים הרלוונטים להצבה נשמרים לפני כל ההצבות ורק לאחר מכן ההצבה מתבצעת- מתוך הערכים השמורים)

fork       
    <statement>;
    <statement>;
..           
join        
תחביר
בשלב מסויים בקוד נציב
A=1'b1;
מאוחר יותר נבצע את ההצבות
fork     
A=1'b0;
B=A;   
join     
דוגמה
בסיום הדוגמה ערכו של B יהיה 1 ולא 0.


מבנים בשפה

שערים
משמשים לתכנון ברמת השערים. במקום להשתמש באופרטורים ניתן להצהיר על שער לוגי, לתת לו שם ולהתייחס אליו ספציפית בתוך המערך הכולל של הרכיבים. היחס לשערים אלו הוא כרכיב רשת (net). סוגי השערים האפשריים: and, nand, or, nor, xor, xnor, buf, not (ועוד סוגים שונים של חוצצים). ההצהרה מתבצעת בפורמט הבא:

<gate_type> <gate_name>(<output>, <input1>, ... , <inputN>);
תחביר

xor xor1(sum,A,B,Cin);

דוגמה שער ברכיב
Full adder

השם הראשון ברשימת הפורטים, בהגדרה, הוא שם המוצא של השער. על החוטים שנכנסים ויוצאים מהשער יש להצהיר מראש למעט יוצא דופן אחד, במקרה של "חוט מרומז", שבו חוט מחבר שני רכיבים מוצהרים (בלבד). במקרה כזה, מספיק להשתמש באותו שם בשני הרכיבים ואין חובה להצהיר עליו מראש:

not (temp, in);
some_device_or_gate s_d_o_1(out, temp);
דוגמה
בדוגמה זאת, אין צורך להצהיר מראש על האות temp באופן מפורש.


בלוק always
הפקודות בבלוק always מתבצעות בכל פעם שחל שינוי באחד האותות המוצהרים ברשימת הרגישות (sesitivity list) של הבלוק. רשימת הרגישות מתחילה בסימן @ (at), כפי שניתן לראות בטבלה הבאה:

always @(<signal1> or <signal2> or ... or <signalN>)
begin                                                                    
    <statement>;                                                              
    <statement>;                                                              
..                                                                         
end                                                                      
תחביר
always @(a or b or cin)
begin                         
...                             
end                           
דוגמה

הכנסת אות מסויים לרשימת הרגישות גורמת לכך שהבלוק יצא לפועל בכל פעם שאות משתנה (בניגוד למה שהיה אפשר לחשוב בהתייחס לעובדה שבין האותות כותבים את המילה or). ניתן להתייחס רק לשפת האות (edge) על ידי הוספת המילה posedge -לציון עליית האות, או negedge לציון ירידת האות. לדוגמה, בלוק בעל הכותרת (always @(posedge clk יוצא לפועל בעליית השעון.
בלוק always ללא רשימת רגישות יחל את ביצועו בזמן t=0 וימשיך לעולם. ניתן להשיג תוצאה זהה על ידי שימוש בבלוק initial (להלן) ובלולאת forever.


בלוק initial
הפקודות בבלוק initial מתבצעות עם תחילת הסימולציה ("ההפעלה") בלבד, למעשה זהו מקרה פרטי של בלוק always, כאשר האירוע היחיד שמפעיל את הבלוק הוא תחילת הסימולציה. בלוק initial משמש לצוך איתחול ערכים, פתיחת קבצים וכד'. בנוסף, ניתן למדל פעולת שעון על ידו באמצעות ההצהרה forever ושימוש במידול השהיות.

initial          
begin         
<statement>;
<statement>;
..               
end           
תחביר
initial                     
begin                     
clk=1'b1;                
forever #5 clk=~clk;
end                       
דוגמה: מידול שעון


מודול
תוכנית של רכיב שעומד בפני עצמו. כותרת המודול כוללת רשימת הפורטים של הרכיב, לאחר מכן המודול כולל הצהרות על משתנים, ולבסוף את המימוש עצמו תוך שימוש במבני השפה ובבלוקים הבסיסיים. כדי לממש רכיב ניתן להשתמש בתוך המודול במודולים אחרים (כלומר ברכיבים בסיסיים יותר).

module <module name>(<port list>);
<declerations>                
..                                   
<instances>                   
..                                   
end module                                   
תחביר
module clock_m(clk);        

output clk;              
reg clk;                  

initial                     
begin                     
clk=1'b1;                
forever #5 clk=~clk;
end                       

endmodule                      
דוגמה: מידול שעון

הסימון ; בסוף הכותרת הוא לא טעות... למרות שתחביר כזה יכול להיראות משונה למי שרגיל לכתוב בשפת C.
על כל הקלטים של המודולים המקננים במודול הנוכחי להיות מחוברים לאנשהו, אבל לא כל הפלטים חייבים להיות מחוברים.
כמו שאפשר לצפות מחומרה, כל מודול פועל תמיד. שימוש במודול מתוך מודול אחר הוא לא "קריאה למודול" כמו קריאה לפונקציה בתוכנה. דוגמה להצהרה על המודול הקודם (מידול שעון) בתוך מודול בדיקה:

module stimulus;                                 

wire clk;                                              
... //other declerations                          
clock_m clock1(clk);  //clock decleration

... //instances                                       

end module                                          
דוגמה

בהצהרה על מודול מקנן אפשר לסדר את המשתנים בהצהרה לפי הסדר במימוש (מיפוי לפי מיקום, positional mapping) כפי שנעשה בדוגמה הקודמת, או באמצעות מיפוי לפי שם (named mapping), לפי התחביר הבא:

<part_type> <part_name>(.<port_name>(<wire_name>), ... , .<port_name>(<wire_name>)); תחביר

כאשר <port_name> הוא שם הכניסה ברכיב המוצהר (ב- header), ו- <wire_name> הוא שם החוט שמחובר לכניסה (מוצהר במודול הנוכחי לפני הצהרת הרכיב).


מידול השהיות
בקרוב..


משימות מערכת
בעת סימולציה של הקוד, ניתן לבצע משימות מסויימות כדי להקל על ניטור פעולת הרכיב. כל משימות המערכת מתחילות בתו $ כדי להבדיל אותן מהוראות אחרות. לדוגמה, כדי להציג הערה על המסך (הפלט הסטנדרטי) בכל פעם שהמשתנה var משתנה, יש להכניס את השורה הבאה לקוד:

$monitor($time,"%b",var);
דוגמה

המשימה time$ מציגה את זמן הסימולציה שבו המאורע התרחש. התחביר זהה לתחביר ההערות בשפת C (הסימון b% משמש להצגה בינארית).
סיום סימולציה מתבצע באמצעות המשימה finish$ (החזרת השליטה למערכת ההפעלה), או על ידי stop$ (השהיית הסימולציה, כניסה למצב אינטרקטיבי).
רשימה ממצה של של משימות המערכת הקיימות ניתן למצוא בערך של verilog בויקיפדיה.


קישורים פנימיים
איקארוס ורילוג - סימולטור וכלי סינטזה לורילוג (קוד פתוח)

קישורים חיצוניים
File I/O for Verilog models - מדריך לטיפול בקבצים בורילוג שיכול להיות מועיל
asic world - מדריכים, דוגמאות, קישורים וכו' בנושא ורילוג
verilog - הערך בויקיפדיה האנגלית


אל הדף הראשי של פרוייקט תכנון מעבד פשוט

Comments