Επιβεβαίωση ορθής λειτουργίας απλών ψηφιακών κυκλωμάτων Δημήτρης Κωνσταντίνου, Γιώργος Δημητρακόπουλος Εφόσον έχουμε περιγράψει το κύκλωμά μας σε System Verilog θα πρέπει να βεβαιωθούμε πως λειτουργεί σωστά. Αυτό το πετυχαίνουμε προσομοιώνοντας το κύκλωμά μας με την βοήθεια ενός βοηθητικού προγράμματος (testbench), το οποίο οδηγεί τα σήματα εισόδου του κυκλώματος που εξετάζουμε και μας επιτρέπει να παρακολουθούμε τα σήματα εξόδου καθώς και τις τιμές της εσωτερικής κατάστασης του κυκλώματος. Για να γίνει ξεκάθαρη η διαδικασία που φαίνεται στο Σχήμα 1, θα δούμε πως δημιουργούμε ένα testbench για έναν απλό counter και πως θα κάνουμε τη προσομοίωση με την βοήθεια του Modelsim. Σχήμα 1. Διαδικασία προσομοίωσης κυκλώματος και ελέγχου ορθής λειτουργίας. Το κύκλωμα Στο Σχήμα 2 φαίνεται το κύκλωμα του counter που επιθυμούμε να δημιουργήσουμε και στον Κώδικας 1 είναι η υλοποίηση του counter. module counter #(parameter int WIDTH = 8) //Input-Output List (input logic clk, input logic rst_n, input logic cnt_en, output logic[width-1:0] val_out); //Internal Signal(flip-flops) logic[width-1:0] cnt; //Functionality always_ff @(posedge clk, negedge rst_n) begin if(!rst_n) cnt <= 0; else begin if (cnt_en) cnt <= cnt + 1; assign val_out = cnt; module Κώδικας 1. Περιγραφή του counter σε System Verilog. Σχήμα 2. Το επιθυμητό κύκλωμα που περιγράφει ο Κώδικας 1.
Τα βασικά χαρακτηριστικά του κυκλώματος είναι: Αύξηση της τιμής του counter κατά 1, όταν το cnt_en=1 Διατήρηση της τιμής του counter, όταν το cnt_en=0 Ασύγχρονο Reset Testbench Αφού έχουμε τον κώδικα του counter, το επόμενο βήμα είναι να δημιουργήσουμε ένα testbench. Για το λόγο αυτό δημιουργούμε ένα αρχείο counter_tb.sv (ή ότι όνομα θέλουμε), το οποίο δεν θα έχει σήματα εισόδου ή εξόδου αλλά θα περιγράφει εσωτερικά σήματα τα οποία θα οδηγούν το κύκλωμά μας, όπως φαίνεται στο Σχήμα 3. Στον Κώδικας 2 αρχικά ορίζουμε το module του testbench, μετά την παράμετρο του counter για το εύρος των bit, και έπειτα τα σήματα που θα χρειαστούμε. Για να δημιουργήσουμε το ρολόι χρησιμοποιούμε ένα always block, το οποίο θα τρέχει συνέχεια τον κώδικα που περικλείει. Έτσι στον χρόνο 0, το ρολόι θα γίνει 1 και θα περιμένει για 5ns (#5ns;) μετά θα γίνει 0 και θα περιμένει άλλα 5ns και έπειτα θα ξανά-ξεκινήσει από την αρχή. Μετά το ρολόι ορίζουμε έναν counter και συνδέουμε τα σήματα του testbench με αυτά του counter. Τέλος, στον Κώδικας 2 φαίνεται το σημείο στο οποίο θα δημιουργήσουμε το σενάριο που θέλουμε να εξετάσουμε. Πολύ σημαντικό είναι να έχουμε στο μυαλό μας ότι τα παραπάνω blocks (always, module, initial) εκτελούνται παράλληλα. module counter_tb; parameter WIDTH = 2; logic clk, rst_n; logic cnt_enable; logic [WIDTH-1:0] counter_out; always begin //generate clock clk = 1; #5ns; clk = 0; #5ns; counter #(.WIDTH (WIDTH)) cntr_name (.clk (clk),.rst_n (rst_n),.cnt_en (cnt_enable),.val_out (counter_out)); initial begin //Test case code here module Κώδικας 2. Αρχικός κώδικας Testbench, δήλωση του counter και δημιουργία ρολογιού counter_tb cnt_enable rst_n clk tes tbench counter cnt_en rst_n clk top_level val_out counter_out Σχήμα 3. Σχηματικά η σύνδεση των σημάτων και η ιεραρχία του testbench και του counter.
Ο λόγος που κάνουμε προσομοίωση είναι για να δούμε αν το κύκλωμά μας λειτουργεί όπως περιμένουμε, επομένως το σενάριο θα πρέπει να δημιουργεί κανονικές συνθήκες λειτουργίας, αλλά και ακραίες περιπτώσεις ώστε να ανακαλύψουμε πιθανά λάθη. Ενδεικτικά για την περίπτωση μας, το σενάριο φαίνεται στον Κώδικας 3 και είναι το εξής: 1) Reset για να αρχικοποιήσουμε τον counter στον κύκλο#1 (10~20ns). 2) Περιμένουμε έναν κύκλο. 3) Κάνουμε το cnt_enable=1 και περιμένουμε 6 κύκλους, ώστε να κάνει overflow ο 2bit counter. 4) Κάνουμε το cnt_enable=0 και περιμένουμε 2 κύκλους, περιμένοντας σταθερό counter_out. 5) Κάνουμε reset στην αρνητική ακμή του ρολογιού, ώστε να γίνει το ασύγχρονο Reset και ταυτόχρονα cnt_enable=1 ώστε να δούμε και την προτεραιότητα που έχει το Reset. initial begin //Time 0 $display("sarting Testbench"); cnt_enable <= 0; //Set starting values @(posedge clk); //First posedge occurred(10ns) rst_n <= 0; @(posedge clk); //Second posedge occurred(20ns) @(posedge clk); //And so on... $strobe("@%0t: counter_out-> %b", $time, counter_out); cnt_enable <= 1; repeat(6) begin @(posedge clk); $strobe("@%0t: counter_out-> %b", $time, counter_out); cnt_enable <= 0; repeat(2) @(posedge clk); @(negedge clk); //At negative edge! rst_n <= 0; cnt_enable <= 1; @(posedge clk); repeat(3) @(posedge clk); $display("finished"); Κώδικας 3. Το initial block του Κώδικας 2, το οποίο περιγράφει τις τιμές των σημάτων εισόδου του counter σε κάθε κύκλο. Με το @(posedge clk)περιμένουμε να συμβεί θετική ακμή του ρολογιού, και ουσιαστικά να συγχρονίζουμε τα σήματα του testbench (rst_n, cnt_enable) με το ρολόι που χρησιμοποιεί και o counter. Ως σημείο αναφοράς χρησιμοποιούμε το clk (είτε posedge, είτε negedge). Την $strobe, την χρησιμοποιούμε για να τυπώσουμε τις τιμές των σημάτων στο Modelsim (όπως θα δούμε παρακάτω). Προσομοίωση Για να μπορέσουμε να προσομοιώσουμε την λειτουργία του testbench και του counter θα χρησιμοποιήσουμε το modelsim. Η παραπάνω διαδικασία μπορεί να παρασταθεί με το διάγραμμα του Σχήμα 4.
Σχήμα 4. Διαδικασία σύνθεσης και ελέγχου των κυκλωμάτων Aπό τα προηγούμενα βήματα έχουμε δύο αρχεία με τον κώδικα του testbench(counter_tb.sv) και του counter (counter.sv) και (στη περίπτωσή μας) βρίσκονται στον φάκελο c:/workspace/counter. Επομένως την πρώτη φορά ανοίγουμε το Modelsim και γράφουμε με την σειρά της εξής εντολές στο transcript(σχήμα 5): # Εντολές Περιγραφή 1 cd c:/workspace/counter Μετάβαση στον φάκελο του project 1α vlib work Δημιουργία βιβλιοθήκης στον ενεργό φάκελο 2 vlog counter.sv counter_tb.sv Compile τα αρχεία (χωρισμένα με κενό) 3 vsim novopt counter_tb Το testbench που θα γίνει simulation (όνομα module) Η εντολή vlog θα εμφανίσει στο transcript τα errors που θα συναντήσει κατά το compilation. Φυσικά τα λύνουμε, πριν προχωρήσουμε στην vsim novopt, κατά την οποία μπορεί και πάλι να εμφανιστούν errors τα οποία προέρχονται από τον κώδικα τα οποία διορθώνουμε πριν συνεχίσουμε. (Σημείωση: κοιτάμε και τα warnings!) Αφού Κάνουμε vlog και vsim με επιτυχία, ανοίγουμε το παράθυρο Wave (αν δεν είναι ήδη ανοιχτό) από το menu View->Wave. Με απλό Drag&Drop εισάγουμε στο παράθυρο Wave τα σήματα που θέλουμε να παρατηρήσουμε(clk, rst_n, cnt_enable, counter_out), όπως φαίνεται στο Σχήμα 5. Σχήμα 5. Αριστερά το παράθυρο των κυματομορφών και δεξιά το κύριο παράθυρο του Modelsim.
Εφόσον κάνουμε τα παραπάνω βήματα, η προσομοίωση ξεκινά με την εντολή: # Εντολές Περιγραφή 4 run 150ns Εκτέλεση simulation για ορισμένο χρόνο (150ns στην περίπτωσή μας) Αν όλα έχουν γίνει σωστά, θα πρέπει να εμφανιστούν οι κυματομορφές του Σχήμα 6, στις οποίες φαίνονται οι τιμές των σημάτων στον χρόνο, αλλά και η έξοδος του counter(counter_out). Επίσης στο transcript(σχήμα 5) εμφανίζονται τα αποτελέσματα των $strobe τα οποία μπορούν να βοηθήσουν στον έλεγχο της λειτουργίας. Σχήμα 6. Κυματομορφές των σημάτων εισόδου και εξόδου του counter για το σενάριο του παραδείγματος. Όπως φαίνεται στο Σχήμα 6, ο counter μηδενίζει κατά το reset, αυξάνεται με το cnt_enable, μένει σταθερό όταν cnt_enable=0, και κάνει ασύγχρονο Reset, σημάδι πως το κύκλωμά μας είναι σωστό. Σε περίπτωση που η έξοδος δεν είναι η επιθυμητή, γυρίζουμε στον κώδικα, τον διορθώνουμε και μπορούμε να ξανακάνουμε προσομοίωση όσες φορές θέλουμε, με τις παρακάτω εντολές (αφού έχουν γίνει οι προηγούμενες εντολές μία φορά): Εντολές Περιγραφή vlog counter.sv counter_tb.sv Ξανακάνουμε Compile τα διορθωμένα αρχεία restart -f Επαναφορά της προσομοίωσης στον χρόνο 0 run 150ns Εκτέλεση προσομοίωσης (Σημείωση: Όταν κάποιο σήμα είναι κόκκινο(x) σημαίνει πως δεν είναι γνωστή η τιμή του, και το ποιο πιθανό είναι ότι δεν οδηγείται από κάπου. Για τα flip-flops αυτό είναι λογικό πριν γίνει το πρώτο Reset.) Σε κάθε επόμενη εκτέλεση του Μodelsim(εκκίνηση του προγράμματος), τα βήματα και οι εντολές παραμένουν τα ίδια εκτός της εντολής vlib work (1α). Η εντολή cd θα εκτελείται μία φορά, όταν ανοίγουμε το Modelsim, ώστε να μεταβούμε στον φάκελο του project.h εντολή vlib (1α) εκτελείται μία φορά σε κάθε project (όχι κάθε φορά που ανοίγουμε το Modelsim). Όλες οι υπόλοιπες εκτελούνται κανονικά και με την σειρά τους.