Menu

Verilog testbench למתחילים: איך בודקים מודול

איך כותבים testbench ב-Verilog: יצירת שעון, רצף reset, גירוי, תצפית, והשלד הסטנדרטי שמניע כל סימולציה שתריצו.

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

מה testbench הוא בעצם

testbench הוא פשוט מודול Verilog. ההבדל ממודול בר סינתזה הוא במה שיש בתוכו:

  • אין לו פורטים: הוא הראש של היררכיית הסימולציה.
  • הוא יוצר מופע של התכנון הנבדק.
  • הוא מזין את הכניסות של ה-DUT מבלוקים של initial ו-always.
  • הוא צופה בפלטים של ה-DUT עם $display, $monitor, או על ידי שמירת VCD.
  • הוא מסיים את הסימולציה עם $finish.

זו כל העבודה. אין מילת מפתח מיוחדת של "testbench": מזהים testbench לפי מה שהוא עושה.

השלד הסטנדרטי

זו כל התבנית. חמישה חלקים, קל להעתיק, קל להתאים.

ל-DUTs צירופיים (כמו המחבר) לא צריך שעון. ל-DUTs סדרתיים כן, וזו השכבה הבאה.

יצירת שעון ל-DUTs סדרתיים

כשל-DUT יש כניסת clk, ה-testbench צריך לייצר שעון. השורה הסטנדרטית:

reg clk = 0;
always #5 clk = ~clk;

היא הופכת את clk כל 5 יחידות זמן. כל היפוך הוא חצי ממחזור השעון, ולכן המחזור המלא הוא 10 יחידות (מחזור אחד = גבוה ל-5 ונמוך ל-5). אם ה-timescale שלכם הוא 1ns / 1ps (ברירת המחדל ברוב הסימולטורים), זה שעון של 100 MHz.

בחרו את חצי המחזור לפי מה שרוצים למדל. לשעון של 10 MHz באותו timescale, השתמשו ב-#50. לשעון של 200 MHz, ב-#2.5 (או שנו את ה-timescale).

רצף reset

תכנונים סינכרוניים צריכים reset שמוחזק גבוה כמה מחזורים אחרי זמן 0, ואז משוחרר. הצורה המקובלת:

reg reset = 1;     // start asserted

initial begin
    // Hold reset for a few clocks.
    #20 reset = 0;
end

זה משאיר את reset גבוה עד זמן 20, ואז מוריד אותו. עד ש-reset יורד, השעון כבר עבר כמה מחזורים, כל flip-flop לכד את ערך ה-reset, והתכנון במצב ידוע.

ל-reset פעיל בנמוך (reset_n במקום reset), הופכים את הערך ההתחלתי:

reg reset_n = 0;   // active-low: 0 means asserted

initial begin
    #20 reset_n = 1;
end

testbench סדרתי מלא

שילוב של שעון, reset וגירוי:

זה testbench מלא למונה של 4 ביטים. לחצו Run ותראו את המונה יוצא מ-reset, סופר למעלה כשהוא מאופשר, נעצר כש-enable יורד, ממשיך כשהוא עולה, ואז נעצר.

שלושה דברים שכדאי לשים לב אליהם במבנה:

  1. שלושה מוקדי פעילות נפרדים: מחולל השעון (always אחד), הגירוי (initial אחד), והמעקב (always נוסף). לכל אחד תפקיד אחד.
  2. ההשהיות # בבלוק הגירוי נמדדות ביחידות זמן, לא במחזורי שעון. אם מחזור השעון הוא 10 יחידות, אז #10 הוא בדיוק מחזור אחד. יש testbenches שמשתמשים במקום זאת ב-@(posedge clk), שמקדם מחזור אחד בלי קשר לאורך המחזור.
  3. המעקב משתמש ב-$display מתוך בלוק always @(posedge clk). זה מדפיס בכל מחזור. לפלט מתוחכם יותר, עברו ל-$monitor (מוסבר בהמשך).

גירוי לפי מחזורים

לפעמים "חכו N מחזורים" נקרא טוב יותר מ"חכו N יחידות זמן":

initial begin
    @(posedge clk);     // wait for next clock edge
    reset = 0;

    repeat (5) @(posedge clk);   // wait 5 more cycles
    enable = 1;

    repeat (8) @(posedge clk);
    enable = 0;

    repeat (10) @(posedge clk);
    $finish;
end

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

ב-testbenches ראשונים, השהיות # פשוטות יותר. ב-testbenches של ייצור שעשויים לרוץ בכמה מהירויות שעון, הסגנון לפי מחזורים בטוח יותר.

בדיקות שבודקות את עצמן

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

initial begin
    #1;
    a = 10; b = 20;
    #1;
    if (sum !== 30) begin
        $display("FAIL: 10 + 20 = %0d (expected 30)", sum);
        $finish;
    end
    a = 250; b = 5;
    #1;
    if (sum !== 255) begin
        $display("FAIL: 250 + 5 = %0d (expected 255)", sum);
        $finish;
    end

    $display("PASS");
    $finish;
end

השתמשו ב-!== (אופרטור ה-case inequality) ליתר ביטחון: הוא לא מחזיר x כשבאחד האופרנדים יש ביטים לא ידועים. התבנית: מזינים כניסות, מחכים שהדברים יתייצבו, משווים לציפייה, ומדווחים הצלחה או כישלון.

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

מה הלאה

עכשיו יש לכם צורת testbench שמספיקה לכל מודול. המסמכים הבאים עוסקים בכלים שנכנסים לתוך ה-testbench: Display ו-Monitor לפלט טקסט עשיר יותר, Dumpfile ו-VCD לדיבוג גרפי עם waveforms, ו-Timescale והשהיות לשליטה מדויקת על הקשר בין זמן הסימולציה לזמן אמיתי.

שאלות נפוצות

מה זה testbench ב-Verilog?

testbench הוא מודול Verilog, בדרך כלל בלי פורטים, שכל תפקידו להפעיל את התכנון הנבדק (DUT). הוא יוצר מופע של ה-DUT, מייצר שעון, מזין גירוי דרך בלוקים של initial ו-always, צופה בפלטים עם $display/$monitor, שומר waveform של VCD אם רוצים, ומסיים את הסימולציה עם $finish.

איך מייצרים שעון ב-testbench של Verilog?

התבנית הסטנדרטית היא שורה אחת: always #5 clk = ~clk; (עם reg clk = 0; שהוצהר קודם). היא הופכת את clk כל 5 יחידות זמן של סימולציה, ונותנת מחזור של 10 יחידות (שעון של 100 MHz אם ה-timescale בננו שניות). ה-#5 הוא חצי מחזור: חצי מהזמן clk גבוה, וחצי נמוך.

מה זה DUT ב-Verilog?

DUT הוא ראשי תיבות של Design Under Test, המודול שה-testbench מפעיל. המוסכמה היא לקרוא למופע של ה-DUT בשם dut או u_dut ב-testbench: my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));. השם הוא רק תווית; מה שחשוב הוא שנוצר ממנו מופע, שהפורטים שלו מחוברים לאותות של ה-testbench, ושה-testbench מזין את האותות האלה.

כמה זמן סימולציית Verilog צריכה לרוץ?

מספיק זמן כדי להפעיל את כל מה שרוצים לבדוק, ואז $finish. רוב ה-testbenches קובעים מגבלת זמן מפורשת (#1000 $finish) כדי שהסימולציה לא תיתקע בהמתנה לאירוע שלא מגיע. בתוך החלון הזה מזינים את הגירוי, נותנים ל-DUT להתייצב, ובאופן אידיאלי כוללים כמה משפטי if של בדיקה עצמית שמדפיסים FAIL אם הפלט לא תואם את הציפיות.

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

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

להתחיל