Menu

לוגיקה מסונכרנת לשעון ב-Verilog: flip-flops, אוגרים ו-pipelines

איך בונים אוגרים, מונים, shift registers ו-pipelines מבלוקים של always שמסונכרנים לשעון: התבנית המרכזית של כל תכנון דיגיטלי סינכרוני.

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

אבן הבניין: D flip-flop

כל תכנון סינכרוני חוזר לרכיב חומרה זעיר אחד: ה-D flip-flop. יש לו כניסת שעון, כניסת נתונים ויציאת נתונים. בכל קצה עולה של השעון הוא לוכד את הערך של D ומחזיק אותו עד הקצה הבא. זה הכול.

ב-Verilog:

always @(posedge clk) begin
    q <= d;
end

שלוש שורות, flip-flop אחד. ה-<= הוא השמה לא חוסמת, וזו בדיוק ההתנהגות של flip-flops אמיתיים (מוסבר ב-השמה חוסמת מול לא חוסמת). הרגישות ל-posedge clk היא מה שהופך את הבלוק לסדרתי.

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

אוגר עם reset

תכנונים אמיתיים תמיד צריכים reset, דרך להביא את המערכת למצב ידוע בהדלקה או לפי דרישה:

זה reset סינכרוני: תנאי ה-reset נבדק בקצה שעון כמו כל כניסה אחרת. הסינתיסייזר מייצר flip-flop עם mux של 2 ל-1 בכניסת הנתונים שלו: כש-reset גבוה, ה-mux מזין אפס; אחרת הוא מזין את d.

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

אוגר עם enable

לעיתים קרובות רוצים אוגר שמתעדכן רק כשמשהו אומר לו. משתמשים ב-if בתוך הבלוק המסונכרן לשעון, וסומכים על ההתנהגות המובלעת של "שמירת הערך הקודם" כשה-else חסר:

זה ה-"load-enabled register" הקנוני. הוא מופיע בכל מקום: אוגרי תצורה, שלבי pipeline שמתקדמים רק כשהשלב הבא מוכן, מונים שנעצרים וממשיכים. השמטת ה-else האחרון מכוונת ובטוחה בתוך בלוק מסונכרן לשעון: flip-flop כבר זוכר את הערך הקודם שלו, ולכן "לא לעשות כלום" פירושו "להחזיק".

בבלוק צירופי אותו קוד היה מסיק latch. כללים שונים, אותו תחביר.

מונה

מונה הוא אוגר שהערך הבא שלו הוא הערך הנוכחי ועוד אחד:

המונה גדל בכל מחזור שעון כש-enable גבוה. אחרי 16 מחזורים הוא חוזר ל-0 (כי הצהרנו על 4 ביטים ו-15 + 1 גולש ל-0). הגלישה הזו טבועה באריתמטיקה של N ביטים, וכך בדיוק מתנהג מונה חומרה אמיתי.

Shift register

הערמת flip-flops נותנת shift register. הטריק הוא לבצע את כל ההזזות בהוראה לא חוסמת אחת:

הגוף הוא out <= {out[WIDTH-2:0], in}: משרשרים את הביטים התחתונים של out הנוכחי עם ה-in החדש, ומבצעים השמה של הכול. מכיוון שזו השמה לא חוסמת, צד ימין קורא את out ה_ישן_ לפני שצד שמאל מתעדכן. התוצאה היא הזזה נקייה של N ביטים במחזור שעון אחד.

זו תבנית ה-shift register בצורתה הקטנה ביותר. היא מתרחבת ל-LFSRs, משדרים טוריים, deserializers, כל תכנון שבו נתונים נעים בשרשרת של flip-flops בכל שעון.

Pipelines

Pipeline הוא שרשרת של אוגרים שמופרדים בלוגיקה צירופית. כל שלב מעבד נתונים מהשלב הקודם ומזין את הבא:

שלושה שלבים, שלושה מחזורי latency, אבל תוצאה חדשה בכל מחזור ברגע שה-pipeline מלא. ה-latency הוא 3 שעונים כי הנתונים עוברים דרך שלושה flip-flops; התפוקה היא פעולה 1 לכל שעון כי שלושת השלבים עובדים בו זמנית על כניסות שונות.

כך תכנונים עתירי ביצועים מגיעים ליעדי התפוקה שלהם: שומרים על שלבים קצרים, מעמיקים את ה-pipeline, ונותנים למקביליות לעשות את העבודה.

reset אסינכרוני (כשצריך אותו)

לפעמים אי אפשר לחכות לקצה שעון כדי להפעיל reset: השבב נכבה, השעון חסום, watchdog חיצוני מושך את הקו. במקרים כאלה:

always @(posedge clk or negedge reset_n) begin
    if (~reset_n) q <= 0;
    else          q <= d;
end

ברשימת הרגישות יש עכשיו גם קצה שעון וגם קצה reset. ה-flip-flop מגיב מיד לכל אחד מהם. reset_n הוא לפי המוסכמה active-low (פעיל כשהוא נמוך), ולכן הבדיקה היא ~reset_n.

ל-reset אסינכרוני יש מחירים: קשה יותר לנתח את התזמון שלו, הוא עלול לגרום ל-metastability אם לא מטפלים בזהירות בשחרור שלו, והוא לא נייד בין כל ארכיטקטורות ה-FPGA. השתמשו ב-reset סינכרוני כברירת מחדל, ובאסינכרוני רק כשהתכנון דורש אותו.

מה הלאה

עכשיו אתם יכולים לבנות כל datapath סינכרוני. המסמך הבא, מכונות מצבים סופיות, מחבר אוגר מסונכרן לשעון עם משפט case כדי ליצור את ניב ה-FSM הסטנדרטי: סוס העבודה של כל בקר, מנוע פרוטוקול ובלוק קבלת החלטות בתכנון דיגיטלי.

שאלות נפוצות

מה זו לוגיקה מסונכרנת לשעון ב-Verilog?

לוגיקה שהעדכונים שלה נשלטים על ידי אות שעון. הניב הסטנדרטי הוא always @(posedge clk) target <= next_value;: בכל קצה עולה של clk, ה-target לוכד את next_value. השורה הזו לבדה מתארת flip-flop בחומרה. כשמערימים הרבה כאלה עם לוגיקה צירופית ביניהם, בונים מונים, shift registers, pipelines, כל דבר סינכרוני.

מה ההבדל בין reset סינכרוני לאסינכרוני ב-Verilog?

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

איך בונים pipeline ב-Verilog?

מערימים כמה שלבים של always @(posedge clk) stageN_reg <= stageN_combinational;: הלוגיקה הצירופית של כל שלב מזינה את האוגר של השלב הבא, וכל האוגרים לוכדים באותו קצה שעון. התוצאה היא pipeline שבו נתונים חדשים נכנסים בכל מחזור ויוצאים N מחזורים אחר כך, עם תפוקה של תוצאה אחת לכל שעון.

מה זה shift register ב-Verilog?

שרשרת של flip-flops שבה הפלט של כל אחד מזין את הכניסה של הבא. כל קצה שעון מזיז כל ביט במיקום אחד. הגרסה הקנונית ב-Verilog משתמשת בהשמות לא חוסמות: out <= {out[N-2:0], in};. השורה הזו יוצרת shift register של N ביטים שמקבל ביט אחד בכל מחזור מ-in.

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

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

להתחיל