Menu

יצירת מופעים של מודולים ב-Verilog: חיבור תת מודולים

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

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

מודולים בתוך מודולים

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

כבר ראיתם את הצורה, השתמשנו בה ב-המודול הראשון שלכם:

and_gate dut(.a(a), .b(b), .y(y));

השורה הזו יוצרת מופע יחיד של and_gate, קוראת לו dut, ומחברת את הפורטים שלו לאותות מקומיים. בואו נפרק כל חלק.

הצורה של יצירת מופע

module_name instance_name (port_connections);
  • module_name חייב להתאים לשם מהצהרת module כלשהי בפרויקט. Verilog רגישה לאותיות גדולות וקטנות.
  • instance_name הוא תווית שאתם בוחרים, בדרך כלל שם שמתאר את התפקיד של המופע. תשתמשו בו בנתיבים היררכיים ובתצוגות waveform.
  • port_connections מחברים את הפורטים של המופע לאותות מקומיים. יש שתי דרכים לכתוב את זה.

חיבורי פורטים לפי שם (השתמשו בהם)

הצורה לפי שם נראית כך:

my_module instance_name(
    .clk    (clk),
    .reset  (reset_n),
    .data_in(in_bus),
    .data_out(out_bus),
    .valid  (out_valid)
);

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

שתי הערות מעשיות:

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

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

חיבורי פורטים לפי מיקום (הימנעו מהם)

הצורה התמציתית יותר מונה את האותות לפי סדר רשימת הפורטים:

my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);

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

איפה זה עדיין מקובל: מודולי עזר זעירים שבהם סדר הפורטים הוא חלק מה-API. שער עם שתי כניסות בסדר לפי מיקום. בקר זיכרון עם 30 פורטים מחפש צרות.

דוגמה היררכית מלאה

זו היררכיה אמיתית בשלוש רמות: test → full_adder → שני מופעים של half_adder. לכל מופע יש עותק משלו של השערים שבתוך half_adder; כלי הסינתזה יפיק מעגל אחד לכל יצירת מופע.

כמה מופעים של אותו מודול

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

adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));

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

לולאות generate: שכפול חומרה חוזרת

לכתוב ארבעה מופעים ביד זה בסדר. לכתוב 64 זה מייגע. בלוק ה-generate נותן ל-elaborator להקליד בשבילכם:

שלושה פריטי תחביר חדשים:

  • genvar i מצהיר על משתנה לולאה שאפשר להשתמש בו ב-generate. הוא לא אות בזמן ריצה: הוא קיים רק בזמן ה-elaboration.
  • generate ... endgenerate עוטף את הלולאה. יש כלים שמקבלים לולאות generate בלי מילת המפתח generate המפורשת, אבל כתיבתה הופכת את הכוונה לברורה.
  • begin : invert_loop נותן תווית לתחום ה-generate. התווית הופכת לחלק מהשם ההיררכי של כל מופע שנוצר (dut.invert_loop[0].u_inv, dut.invert_loop[1].u_inv וכו').

הסינתיסייזר פורש את הלולאה ומייצר WIDTH עותקים של bit_inverter. כל עותק הוא חומרה עצמאית.

דריסת פרמטרים ביצירת מופע

אם למודול יש פרמטרים, אפשר לדרוס אותם עם #(.PARAM(value)) בין שם המודול לשם המופע:

counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));

שני המופעים משתמשים באותו קוד מקור של counter, אבל ברוחבים שונים. כיסינו את התחביר ב-פרמטרים; הוא משתלב בצורה נקייה ביצירת מופעים.

שמות היררכיים

ברגע שיש היררכיה, לכל אות יש נתיב היררכי:

test.dut.ha0.sum

קוראים את זה כך: במודול test, בתוך המופע dut, בתוך המופע ha0, האות שנקרא sum. תראו את הנתיבים האלה במציגי waveform, בהודעות שגיאה, ומדי פעם בקריאת $display שמציצה לעומק תת מודול מתוך testbench:

$display("internal carry1 = %b", dut.carry1);

הפניות היררכיות כאלה הן רק ל-testbenches ולדיבוג: RTL בר סינתזה לא נוגע במודולים אחרים.

טעויות נפוצות

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

אי התאמה ברוחב פורט. חיבור אות של 4 ביטים לפורט של 8 ביטים מרחיב באפסים בשקט; הכיוון ההפוך קוטם בשקט. רוב הכלים מתריעים; אם אתם לא רואים אזהרות, חפשו טוב יותר.

שכחת ה-# של הפרמטר. counter (.WIDTH(8)) c(.clk(clk)) נראה כמו דריסה, אבל הוא לא: המפענח מנסה להתייחס ל-(.WIDTH(8)) כחיבור פורט ונכשל. הנכון: counter #(.WIDTH(8)) c(.clk(clk)).

שימוש חוזר בשם מופע. לשני מופעים אסור שיהיה אותו שם באותו תחום. הודעת השגיאה בדרך כלל ברורה; הפיתוי להעתיק ולהדביק הוא מה שמפיל אתכם.

מה הלאה

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

שאלות נפוצות

איך יוצרים מופע של מודול ב-Verilog?

כותבים את שם המודול, אחריו שם למופע, ואחריו רשימה בסוגריים של חיבורי פורטים: my_module instance_name(.port(signal), ...);. הסגנון הנפוץ ביותר משתמש בחיבורים לפי שם (.port(signal)), שמתאימים לפי שם הפורט בלי קשר לסדר. הסגנון התמציתי יותר לפי מיקום (my_module instance(signal1, signal2)) תלוי בסדר רשימת הפורטים ומסוכן לתחזוקה.

מה ההבדל בין חיבורי פורטים לפי שם ולפי מיקום?

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

אפשר ליצור כמה מופעים של אותו מודול Verilog?

כן, זו כל המטרה. כל מופע הוא חומרה עצמאית עם מצב משלה. אם יש לכם מודול adder, אפשר ליצור ממנו 64 מופעים ביחידת SIMD, כל אחד עם כניסות שונות. לולאת generate היא התחביר הקנוני כשהמופעים דומים וממוספרים.

מה זה בלוק generate ב-Verilog?

generate ... endgenerate הוא מבנה של זמן קומפילציה שמשכפל חומרה חוזרת. לולאת for בתוך generate יוצרת N מופעים של מה שנמצא בגוף. generate רץ בזמן ה-elaboration, לפני שהסימולציה מתחילה: זו לא לולאה בזמן ריצה, אלא מחולל קוד בשביל הסינתיסייזר.

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

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

להתחיל