ΗΥ-225: Οργάνωση Υπολογιστών
Ανοιξη 2001
Τμ. Επ. Υπολογιστών
Πανεπιστήμιο Κρήτης
Σειρά Ασκήσεων 7:

Η FSM Ελέγχου του Επεξεργαστή

Προθεσμία έως Τετάρτη 25 Απριλίου (βδομάδα 10)

Σε αυτή την άσκηση θα περιγράψετε και θα προσομοιώσετε σε Verilog το τμήμα ελέγχου του επεξεργαστή του μαθήματος για την υλοποίηση πολλαπλών κύκλων ρολογίου. Πρόκειται, γιά την Μηχανή Πεπερασμένων Καταστάσεων (FSM - Finite State Machine) που είδαμε στο μάθημα και που δίδεται επακριβώς στο

Σχήμα 5.42 (σελίδα 396) του βιβλίου
και επεξηγείται στις σελίδες 389-399 του βιβλίου καθώς και παρακάτω, μαζί με τα σήματα εξόδου που αυτή γεννά. Η μηχανή αυτή υλοποιεί τις εντολές add, sub, and, or, slt, lw, sw, beq, και j. Τις υπόλοιπες εντολές θα τις υλοποιήσουμε στην άσκηση 8.

FSM Ελέγχου και Χρονισμός των Σημάτων Εισόδου/Εξόδου της

Η γενική μορφή μιάς FSM φαίνεται στο παρακάτω σχήμα, μαζί με διάφορες περιπτώσεις χρονισμού των σημάτων και πολυπλοκότητας των συνδυαστικών συναρτήσεων που τα γεννούν. Η "καρδιά της FSM είναι ο καταχωρητής κατάστασης --πάνω δεξιά στο σχήμα-- που περιέχει (θυμάται) την παρούσα κατάσταση, "currState", του συστήματος. Ενα μπλοκ Συνδυαστικής Λογικής (combinational logic) --κάτω από αυτόν, στο σχήμα-- υπολογίζει (αποφασίζει) ποιά θα είναι η επόμενη κατάσταση, "nxtState" (αμέσως μετά την επερχόμενη ακμή του ρολογιού), σαν συνάρτηση της παρούσας κατάστασης και των εισόδων του κυκλώματος ελέγχου. Οι έξοδοι του κυκλώματος ελέγχου, δηλαδή τα σήματα ελέγχου του datapath, είναι επίσης συναρτήσεις της παρούσας κατάστασης, και μπορεί να είναι συναρτήσεις και των εισόδων του κυκλώματος ελέγχου.
Exercise 7: control FSM structure
Τα σήματα εισόδου στο κύκλωμα ελέγχου αποκτούν έγκυρες τιμές (σταθεροποιούνται) σε διάφορες στιγμές μέσα στον κύκλο (περίοδο) του ρολογιού --άλλα πολύ νωρίς, άλλα αργότερα, κι άλλα πολύ αργά. Παράδειγμα εισόδων του ελέγχου που σταθεροποιούνται πολύ νωρίς μέσα στον κύκλο ρολογιού είναι τα op και funct, που είναι κατευθείαν έξοδοι του καταχωρητή ir και άρα σταθεροποιούνται σχεδόν αμέσως μόλις αρχίζει ο κύκλος --μόλις περάσει η καθυστέρηση εξόδου του καταχωρητή ir, μόνο, μετά την ακμή ρολογιού. Τέτοια πρώιμα σήματα, στο σχήμα παραπάνω, τα συμβολίζουμε σαν "earlyIn, και τα έχουμε σχεδιάσει σχετικά ψηλά ώστε να μας θυμίζουν ότι είναι διαθέσιμα από νωρίς (αν φανταστούμε το χρόνο να κυλά από πάνω προς τα κάτω).

Παράδειγμα εισόδου του ελέγχου που σταθεροποιείται πολύ αργά μέσα στον κύκλο ρολογιού είναι το σήμα zero, που είναι έξοδος του συγκριτή, που ακολουθεί την ALU, που ακολουθεί τους πολυπλέκτες, που ακολουθούν τους καταχωρητές A και B. Τέτοια αργοπορημένα σήματα, στο σχήμα παραπάνω, τα συμβολίζουμε σαν "lateIn, και τα έχουμε σχεδιάσει σχετικά χαμηλά ώστε να μας θυμίζουν ότι γίνονται διαθέσιμα πολύ αργά. Τα αργοπορημένα σήματα εισόδου του ελέγχου μπορούν να συμμετάσχουν μόνο σε πολύ απλούς, δηλαδή γρήγορους, υπολογισμούς --αλλοιώς οι έξοδοι αυτών των υπολογισμών δεν θα προλάβαιναν να σταθεροποιηθούν (να αποκτήσουν έγκυρες τιμές) πριν τελειώσει ο κύκλος ρολογιού. Στο παραπάνω σχήμα αυτό το δείχνουμε γιά μνημονικούς λόγους με "κοντύτερα" (στον κατακότυφο άξονα) μπλόκ συνδυαστικής λογικής.

Σε μιά FSM "τύπου Moore", οι έξοδοι της FSM είναι συναρτήσεις μόνο της παρούσας κατάστασης, --όχι και των εισόδων της FSM (οι είσοδοι επηρεάζουν μόνο την επόμενη κατάσταση). Τέτοιες εξόδους τις βλέπουμε συμβολικά σαν "MooreOut στο σχήμα. Το πλεονέκτημά τους είναι ότι απλοποιούν τη σχεδίαση, ενώ το μειονέκτημά τους είναι ότι ένα τέτοιο κύκλωμα ελέγχου αδυνατεί να αντιδράσει σε εξωτερικά "ερεθίσματα" (σήματα, εισόδους) στον ίδιο κύκλο ρολογιού όπου αυτά εμφανίζονται, και μπορεί μόνο να αντιδράσει σε αυτά στον επόμενο κύκλο ρολογιού (και μάλιστα χρειάζεται και διαφορετικές καταστάσεις γιά την κάθε διαφορετική του αντίδραση).

Σε μιά FSM "τύπου Mealy", οι έξοδοι της FSM είναι συναρτήσεις και της παρούσας κατάστασης, και των εισόδων της FSM. Τέτοιες εξόδους τις βλέπουμε συμβολικά σαν "MealyOut" στο σχήμα. Τα πλεονεκτήματά τους είναι ότι επιτρέπουν οικονομία στο πλήθος καταστάσεων (βλ. πράξεις ALU παρακάτω), καθώς και ταχεία αντίδραση σε εξωτερικά ερεθίσματα. Ομως, το τελευταίο απαιτεί και προσεκτικότερη σχεδίαση: όσα σήματα ελέγχου τύπου Mealy εξαρτώνται από αργοπορημένα σήματα εισόδου, θα είναι και αυτά με τη σειρά τους πολύ αργοπορημένα (βλ. pcld παρακάτω). Στο παραπάνω σχήμα αυτά τα δείχνουμε γιά μνημονικούς λόγους σαν "lateOut".

Το σχήμα 5.42 (σελίδα 396) του βιβλίου, δηλαδή η FSM αυτής της άσκησης, είναι σε γενικές γραμμές μιά FSM τύπου Moore, αλλά με ορισμένες εξαιρέσεις, οι οποίες φαίνονται στο σχήμα 5.33 (σελ. 383) του βιβλίου:

Προετοιμασία Σημάτων Ελέγχου από τον προηγούμενο Κύκλο

[Η παράγραφος αυτή αφορά προαιρετικές βελτιστοποιήσεις χρονισμού, που δίδουν ταχύτερο ρολόϊ (βραχύτερο κύκλο ρολογιού), άρα ταχύτερο επεξεργαστή, αλλά που δεν είναι απαραίτητο να τις εφαρμόσετε αν δυσκολεύεστε να τις κατλάβετε].

Τα σήματα ελέγχου τύπου Moore (MooreOut), καθώς και τα τύπου Mealy (MeallyOut) που εξαρτώνται μόνο από πρώιμα σήματα εισόδου (earlyIn), μπορούν να στεθεροποιούνται σχετικά νωρίς στη διάρκεια του κύκλου ρολογιού, όταν η λογική συνάρτηση που τα γεννάει είναι αρκούντως απλή (γρήγορη). Ετσι, οι λειτουργίες ή πράξεις στο datapath που εξαρτώνται από τέτοια σήματα ελέγχου δεν θα καθυστερούν πολύ να αρχίσουν (αναγκαστικά, αυτές δεν μπορούν να αρχίσουν πριν να σταθεροποιηθούν τα σήματα που τις ελέγχουν ή τις ενεργοποιούν). Παρ' όλα αυτά, η --έστω και μικρή-- καθυστέρηση της λογικής που γεννάει αυτά τα σήματα αποβαίνει σε βάρος του όλου κύκλου ρολογιού. Ουσιαστικά, στην αρχή του κάθε κύκλου ρολογιού, μόλις εμφανιστεί η νέα κατάσταση στην έξοδο του καταχωρητή currState (και τα earlyIn π.χ. σε εξόδους καταχωρητών όπως του ir), και μέχρι να τελειώσει η λειτουργία της λογικής που γεννά τα σήματα ελέγχου, κανένας άλλος μέσα στον επεξεργαστή δεν μπορεί να αρχίσει να δουλεύει --όλοι κάθονται και περιμένουν να τους πεί ο έλεγχος τι να κάνουν....

Το ελάττωμα αυτό μπορεί να διορθωθεί με τη μέθοδο που φαίνεται αριστερά στο παραπάνω σχήμα. Μπορώ να προετοιμάσω (προ-ϋπολογίσω) από τον προηγούμενο κύκλο ρολογιού την τιμή nxtOut που πρέπει να πάρουν ορισμένα σήματα ελέγχου στον επόμενο κύκλο ρολογιού. Αν το κάνω αυτό, και χρησιμοποιήσω και τον καταχωρητή που φαίνεται πάνω-αριστερά στο σχήμα, τότε ευθύς μόλις αρχίσει ο επόμενος κύκλος ρολογιού --μόλις περάσει ίσα-ίσα η καθυστέρηση εξόδου του καταχωρητή-- τα σήματα ελέγχου "earlyOut" θα πάρουν έγκυρες και σταθερές τιμές, επιτρέποντας έτσι στο datapath να αρχίσει αμέσως να δουλεύει. (Ουσιαστικά πρόκειται γιά μιά μορφή ομοχειρίας (pipelining) --τεχνική που όπως θα δούμε σύντομα μπορεί να εφαρμοστεί λίαν εποφελώς και στο datapath). Προφανώς, γιά να μπορέσω να προ-ϋπολογίσω από τον προηγούμενο κύκλο ρολογιού την τιμή που πρέπει να πάρουν ορισμένα σήματα ελέγχου στον επόμενο κύκλο ρολογιού, πρέπει να έχω όλα τα δεδομένα που μου χρειάζονται προς τούτο, άρα τα σήματα αυτά δεν μπορεί να εξαρτώνται από πληροφορίες που γίνονται γνωστές κατά τον "δικό" τους κύκλο ρολογιού και μόνο --πρέπει να εξαρτώνται μόνο από πληροφορίες που ήταν ήδη γνωστές από τον προηγούμενο κύκλο ρολογιού (άρα, βασικά, να ήταν τύπου Moore αν δεν τα προ-ϋπολογίζαμε, ή να μπορούσαν να γίνουν τύπου Moore με προσθήκη καταστάσεων).

Οταν θέλουμε να πετύχουμε γρήγορο ρολόϊ, την μέθοδο αυτή είναι κρίσιμο να την εφαρμόσουμε τουλάχιστο γιά εκείνα τα σήματα ελέγχου που κανονίζουν τις εργασίες που το datapath αρχίζει να τις εκτελεί στην αρχή-αρχή του κύκλου ρολογιού. Στο δικό μας datapath, τέτοια σήματα είναι τα:

Σε όλα αυτά είναι εφαρμόσιμη η μέθοδος, διότι όλα αυτά ξέρουμε από τον προηγούμενο κύκλο ρολογιού τι τιμή πρέπει να πάρουν.

Ασκηση 7.1: Περιγραφή Καταστάσεων σε "Συνθέσιμη Verilog"

Περιγράψτε το κύκλωμα ελέγχου του επεξεργαστή σας σ' ένα καινούριο module, "control", που θα γράψτε σ' ένα αρχείο "control7.v". To module αυτό θα παίρνει σαν είσοδο το ρολόϊ, και βέβαια θα έχει σαν εισόδους ή εξόδους όλα τα σήματα επικοινωνίας με το datapath που είχαν καθοριστεί στην 6η σειρά ασκήσεων.

Η περιγραφή της FSM θα γίνει με το στυλ "RTL" (Register Transfer Level) της Verilog, που είναι ο συνηθισμένος "συνθέσιμος" τρόπος που γράφουμε στις γλώσσες HDL. Περιγράφουμε το κύκλωμά μας με τις κατάλληλες εκείνες εντολές της γλώσσας που τις καταλαβαίνουν τα εργαλεία σύνθεσης, χωρίς να υπολογίζουμε εμείς τις καθυστερήσεις του κυκλώματός μας. Ξέρουμε (κυρίως από εμπειρία) πόση λογική "χωράει" σε ένα κύκλο ρολογιού, και απλώς την περιγράφουμε. Επειτα, τα εργαλεία σύνθεσης αναλύουν το κύκλωμα, το μεταφράζουν σε λογικές πύλες και flip-flops, το βελτιστοποιούν, και υπολογίζουν επακριβώς τις καθυστερήσεις (βήματα που δεν θα γίνουν στα πλαίσια αυτού του μαθήματος...). Για την FSM της άσκησης, θεωρούμε ότι όλη η λογική της χωράει σε έναν κύκλο ρολογιού.

Ξεκινείστε ορίζοντας το ποιές είναι οι καταστάσεις της FSM και πώς καθορίζεται η επόμενη κατάσταση με βάση την παρούσα κατάσταση και την τιμή των εισόδων. Ακολουθείστε το παρακάτω μικρό παράδειγμα με τις εξηγήσεις που δίδονται.

    parameter [3:0]          // symbolic constants for state encoding
      i_fetch    = 4'b0001,  // "one hot" encoding style
      i_decode   = 4'b0010,
      mem_access = 4'b0100,
      alu_exec   = 4'b1000;

    wire [3:0] state;        // current State (currState): output of rState
    reg  [3:0] nxtState;     // *combinational* (although "reg"); see below
    Reg #4 rState (state, nxtState, 1'b1, clk);  // state register; lden==1

                  // define nxtState  as a function of state, op, funct:
    always @(state or op or funct) begin  // whenever any input changes:
      case (state)
	i_fetch: 
	  nxtState = i_decode;
	i_decode:
	  if ((op == 6'b100011) || (op == 6'b101011)) // Load or store
	    nxtState = mem_access;
	  else  if ( ... )
	    nxtState = ...
	  else 
	    nxtState = alu_exec;
	mem_acess:
	  nxtState = ...
	alu_exec:
	  nxtState = ...
	default: 
	  nxtState = i_fetch;
      endcase
    end 

Ορισμός και Κωδικοποίηση Καταστάσεων:
Αρχικά ορίζουμε ποιές είναι οι καταστάσεις της FSM, δίνοντας τους συμβολικά ονόματα (i_fetch, i_decode, mem_access, alu_exec,...) και μία προτεινόμενη, πιθανή κωδικοποίηση (0001, 0010, 0100, 1000,...). Αυτό το κάνουμε με την εντολή "parameter" της Verilog, που ορίζει μιά σταθερά που κάποιος άλλος μπορεί να την αλλάξει αργότερα, αντι της "define" που ορίζει σταθερές-σταθερές --έτσι, το εργαλείο σύνθεσης θα μπορέσει να βελτιστοποιήσει, πιθανόν, την κωδικοποίηση των καταστάσεων. Στο παραπάνω παράδειγμα διαλέξαμε κωδικοποίηση του στυλ "one hot", δηλαδή ο καταχωρητής κατάστασης έχει τόσα bits όσες και οι καταστάσεις συνολικά, κάθε bit αντιστοιχεί σε μία συγκεκριμένη κατάσταση, και κάθε κατάσταση κωδικοποιείται με όλα τα bits μηδέν εκτός από το "δικό της" bit που είναι "αναμένο" (ζεστό, hot) --αυτό το στυλ κωδικοποίησης ταιριάζει σε FSM με όχι πολύ μεγάλο πλήθος καταστάσεων, και συνήθως δίνει ψηλή ταχύτητα λειτουργίας της FSM. Συμπληρώστε τον κατάλογο με όλες τις καταστάσεις που θα χρειαστείτε, και διορθώστε την κωδικοποίησή τους (αν ακολουθείστε το στυλ one hot, προσέξτε πόσα bits χρειάζεστε).

Καταχωρητής Κατάστασης:
Ορίζουμε τον καταχωρητή κατάστασης "rState" (προσοχή στο πλάτος του), που θα περιέχει την παρούσα κατάσταση ("state" εδώ, "currState" στο αρχικό σχήμα). Ο καταχωρητής αυτός φορτώνεται σε κάθε ακμή ρολογιού (load enable πάντα 1) με την νέα κατάσταση, nxtState, που υπολογίσαμε στον προηγούμενο κύκλο ρολογιού.

Υπολογισμός Επόμενης Κατάστασης:
Ο υπολογισμός της επόμενης κατάστασης, nxtState, γίνεται με συνδυαστική λογική βάσει της παρούσας κατάστασης, state, και των εισόδων της FSM, όπως φαίνονταν στο σχήμα δεξιά. Επειδή η συνδυαστική αυτή λογική είναι αρκετά μεγάλη, με πολλές υποπεριπτώσεις, μας βολεύει η περιγραφή να γίνει με τις εντολές case και if της Verilog, αναθέτοντας (assign) διάφορες τιμές στη nxtState στους διάφορους κλάδους της case και των if-then-else. Γιά να δουλέψει αυτό, σε Verilog, πρέπει το nxtState να δηλωθεί σαν "reg" και όχι σαν "wire".

[Στα wire επιτρέπεται μόνο σύνδεση με modules ή continuous assignment. Στα continuous assignments επιτρέπονται μόνο αριθμητικές/λογικές πράξεις και πράξεις επιλογής όπως "(a==b) ? x : y", αλλά όχι case ή if-then-else. Τα case και if-then-else επιτρέπονται μόνο μέσα σε behavioral blocks, δηλαδή μέσα σε "initial" και "always". Αναθέσεις (assignments) μέσα σε behavioral blocks επιτρέπονται μόνο σε κόμβους τύπου reg, και όχι σε wire. Οι κόμβοι τύπου reg έχουν "μνήμη": όσο και όταν δεν τους ανατίθεται μιά νέα τιμή, αυτοί διατηρούν την παλαιά τους τιμή. Οταν λοιπόν, γιά λόγους ευκολίας μας, θέλουμε να περιγράψουμε συνδυαστική λογική μέσω behavioral blocks, χρειάζεται προσοχή να αναθέτουμε πάντα νέα τιμή στην έξοδο όποτε αλλάζει κάποια είσοδος --διότι αλλοιώς διατηρείται η παλαιά τιμή που σημαίνει μνήμη-- και η νέα τιμή να είναι συνάρτηση μόνο των εισόδων και όχι κάτι άλλου.]
Παρ' ότι, λοιπόν, το nxtState δηλώνεται σαν reg αντί wire, εμείς θα ορίσουμε την συμπεριφορά του έτσι ώστε να προκύπτει ότι πρόκειται γιά συνδυαστική λογική. Αυτό το πετυχαίνουμε με τους εξής τρόπους: (α) Η παρένθεση του "always" statement περιλαμβάνει όλες τις εισόδους από τις οποίες εξαρτάται το nxtState (και χωρίς περιορισμό σε ορισμένες μόνο ακμές τους --posedge ή negedge). Αυτό σημαίνει ότι το μπλοκ αυτό του κώδικα (always block) πρέπει να ξαναεκτελείται πάντα όποτε αλλάζει η τιμή οιασδήποτε από αυτές τις εισόδους. (β) Αναθετουμε τιμή στο nxtState σε κάθε σκέλος των case και if-then-else statements, ώστε να μην υπάρχει περίπτωση το nxtState να "θυμάται" την παλαιά τιμή. (γ) Η τιμή που αναθέτουμε στο nxtState είναι συνάρτηση μόνο των εισόδων που εμφανίζονται στην παρένθεση του always, διότι μόνον αυτών η αλλαγή προκαλεί επανεκτέλεση του always block και άρα ανάθεση νέας τιμής στο nxtState. Εάν προσθέστε εισόδους που επηρεάζουν το nxtState, μην ξεχάστε να τις προσθέσετε και στην παρένθεση του always. Συμπληρώστε τη λογική, ώστε να περιγραφούν πλήρως όλες οι μεταβάσεις καταστάσεων της FSM ελέγχου.

Ασκηση 7.2: Περιγραφή Εξόδων της FSM σε Συνθέσιμη Verilog

Τώρα που η FSM κινείται σωστά μεταξύ των καταστάσεων που πρέπει, το επόμενο βήμα είναι να περιγράψουμε τις εξόδους της FSM, δηλαδή τα σήματα ελέγχου γιά το datapath. Οπως συζητήσαμε νωρίτερα εν εκτάσει, αυτά μπορεί να έχουν διάφορους χρονισμούς, όπως τα "MooreOut", "MealyOut", "lateOut", και "earlyOut" του σχήματος.

Σήματα τύπου MooreOut, MealyOut
Τα σήματα τύπου MooreOut είναι συναρτήσεις μόνο της state. Τα σήματα τύπου MealyOut είναι συναρτήσεις τόσο της state όσο και εισόδων του ελέγχου. Και στις δύο περιπτώσεις θα κάνουμε την απλοποιητική παραδοχή ότι η λογική που τα γεννά έχει καθυστέρηση (μόνο) 0.3 ns (αυτό αντιστοιχεί σε πολύ απλή λογική --ένα-δύο επίπεδα πυλών μόνο, με μικρό μόνο fan-in). Ακολουθείστε το εξής παράδειγμα γιά τα σήματα αυτά:

    reg irld;               // "reg" for convenience of behavioral descr.
    always @(state) begin   // irld is a comb. function of state, only
	if (state == i_fetch) 
	    irld = #0.3  1'b1;  // load IR at end of i_fetch
	else
	    irld = #0.3  1'b0;  // never else load IR
    end  
Οπως και με το nxtState πιό πάνω, έδώ ορίζουμε ένα σήμα που προκύπτει από συνδυαστική λογική και μόνο. Παρ' όλα αυτά, επειδή το περιγράφουμε με το behavioral block "always", το ορίζουμε σαν reg. Ομως, ακολουθούμε όλες τις παραπάνω οδηγίες του nxtState, κι έτσι αυτό που περιγράφουμε είναι τελικά συνδυαστική λογική. Το κομάτι "#0.3" μετά το "=" της κάθε ανάθεσης σημαίνει "η νέα τιμή να εφαρμοστεί στο irld με καθυστέρηση 0.3 ns" (σε σχέση με το πότε εκτελείται το always block, το οποίο εκτελείται τη "στιγμή" που αλλάζουν τιμή τα σήματα που εμφανίζονται στο "@(...)"). Μην ξεχνάτε να βάζετε πάντα αυτή την καθυστέρηση, διότι τότε το κύκλωμά σας θα μοιάζει γρηγορότερο απ' όσο στην πραγματικότητα θα είναι....

Σήματα τύπου lateOut
Τα σήματα τύπου lateOut γεννιούνται όπως και τα MealyOut --απλώς εξαρτώνται και από εισόδους που σταθεροποιούνται πολύ αργότερα. Οπως συζητήσαμε παραπάνω, έχουμε τουλάχιστο ένα τέτοιο σήμα, το pcld κατά τον 3ο κύκλο των εντολών διακλάδωση υπό συνθήκη. Στον τρίτο κύκλο πρέπει να εκτελέσουμε την αφαίρεση στην ALU, και ανάλογα με την τιμή του αποτελέσματος (0 ή όχι) να κάνουμε ή να μήν κάνουμε branch, δηλ. να φορτώσουμε ή όχι τον PC με την διεύθυνση προορισμού. Ετσι, κατ' αναλογία προς την προηγούμενη περίπτωση, έχουμε το παρακάτω κομμάτι κώδικα (μην ξεχνάτε την καθυστέρηση 0.3 ns, και να βάζετε όλες τις εισόδους της συνδυαστικής λογικής στο "@(...)" του always).

    reg pcld;               // "reg" for convenience of behavioral descr.
    always @(state or zero) begin
	if (state == branch_completion) 
	    if (zero == 1'b1)
		pcld = #0.3  1'b1;   // successful branch: load PC
	    else
		pcld = #0.3  1'b0;   // branch failed: do NOT load PC
	else ...                     // what to do in other states...
    end  

Σήματα τύπου earlyOut
Οπως συζητήσαμε παραπάνω, τέτοια σήματα μπορείτε να χρησιμοποιήσετε, προαιρετικά, γιά να επιταχύνετε τον επεξεργαστή σας, και με προτεραιότητα στα "κρίσιμα" σήματα ελέγχου που ρυθμίζουν την εκτέλεση εργασιών στην αρχή του κύκλου ρολογιού. Την τιμή "nxtOut" αυτών των σημάτων την προετοιμάζουμε από τον προηγούμενο κύκλο ρολογιού, στο τέλος του οποίου την φορτώνουμε σε ένα καταχωρητή, γιά να βγεί αυτή με τη μορφή "earlyOut" έγκυρη ευθύς από την αρχή του κύκλου που πρέπει. Ας χρησιμοποιήσουμε το ALUsrcA σαν παράδειγμα. Αυτό πρέπει να πάρει την τιμή 0 (επιλογή του PC): (α) στον 1ο κύκλο κάθε εντολής (i_fetch) (προκειμένου να γίνει η πρόσθεση PC+4), και (β) στον 2ο κύκλο κάθε εντολής (i_decode) (προκειμένου να γίνει η πρόσθεση PC+immx4), ενώ στους υπόλοιπους κύκλους πρέπει να πάρει την τιμή 1 (επιλογή του A). Την τιμή 0, λοιπόν, πρέπει να την προετοιμάσουμε από τον προηγούμενο κύκλο: (α) τον τελευταίο κύκλο κάθε εντολής (προηγούμενος του 1ου κύκλου της επόμενης), και (β) τον 1ο κύκλο κάθε εντολής (προηγούμενος του 2ου), στους υπόλοιπους δε κύκλους πρέπει να προετοιμάσουμε την τιμή 1. Ετσι προκύπτει το παρακάτω κομμάτι κώδικα γιά το σήμα ALUsrcA (και πάλι, το σήμα nxt_ALUsrcA δηλώνεται σαν "reg" αλλά είναι συνδυαστική συνάρτηση του state μόνο, και σαν τέτοιο περιγράφεται):

    wire ALUsrcA;
    reg  nxt_ALUsrcA;    // "reg" for convenience of behavioral description
    Reg #1 r_ALUsrcA (ALUsrcA, nxt_ALUsrcA, 1'b1, clk);
    always @(state) begin
	if (     (state == branch_completion)
	      || (...otherLastStates...)
	      || (state == i_fetch) )
	    nxt_ALUsrcA = 1'b0;
	else
	    nxt_ALUsrcA = 1'b1;
    end  
Ενας άλλος τρόπος περιγραφής της ίδιας λογικής είναι να προετοιμάσουμε την τιμή των σημάτων earlyOut γιά τον επόμενο κύκλο ρολογιού βασισμένοι όχι στην παρούσα κατάσταση, state, αλλά στην επόμενη κατάσταση, nxtState. Αν προετοιμάζουμε ένα σήμα που πρέπει να πάρει την τιμή x στην κατάσταση y, τότε πρέπει να προετοιμάσουμε την τιμή x όταν nxtState==y. Ετσι, το always block του προηγουμένου παραδείγματος μπορεί, ισοδύναμα, να γίνει:
    always @(nxtState) begin
	if ((nxtState == i_fetch) || (nxtState == i_decode))
	    nxt_ALUsrcA = 1'b0;
	else
	    nxt_ALUsrcA = 1'b1;
    end  
Σε πολλές περιπτώσεις, μιά τέτοια περιγραφή μπορεί να είναι απλούστερη. Εάν επρόκειτο το τελικό κύκλωμα να προκύψει "τυφλά" από αυτή την περιγραφή, τότε το μειονέκτημα αυτής θα ήταν η μεγαλύτερη καθυστέρηση: η καθυστέρηση του κυκλώματος υπολογισμού του nxt_ALUsrcA, εδώ, προστίθεται στην καθυστέρηση υπολογισμού του nxtState, ενώ με την πρώτη μορφή περιγραφής το κύκλωμα υπολογισμού του nxt_ALUsrcA αρχίζει να δουλεύει ευθύς από την αρχή του κύκλου ρολογιού, αφού στηρίζεται στο state και μόνο. Ομως, δεδομένου ότι σήμερα πιά όλα τα συνδυαστικά κυκλώματα περνούν από ένα πρόγραμμα αυτόματης βελτιστοποίησης, η διαφορά αυτή στις καθυστερήσεις δεν έχει σημασία, διότι την αναγωγή από το nxtState στο state μπορεί να την κάνει και ο βελτιστοποιητής, αν αυτή τελικά είναι η κρίσιμη καθυστέρηση που θα καθορίσει τη διάρκεια του κύκλου ρολογιού.

Ασκηση 7.3: Ολοκλήρωση και θέση σε Λειτουργία

Αφού τελειώσετε με τον έλεγχο, αντιγράψτε τα αρχεία της προηγούμενης άσκησης σαν "datapath7.v" και "test7.v". Αλλάξτε το top-level module ώστε να μην δίνετε τιμές με το χέρι στα σήματα ελέγχου, αλλά αυτά να τροφοδοτούνται από το control module που φτιάξατε (αρχείο "control7.v").

Αρχικοποίηση:
(α) Βάλτε στη μνήμη αρκετές εντολές για να ελέγξετε το κύκλωμά σας (π.χ. μία εντολή από κάθε είδος). (β) Αρχικοποιήστε τον μετρητή προγράμματος, pc, με τον ίδιο τρόπο όπως και στην άσκηση 6.2 (κανονικά, αυτό θα το έκανε το σήμα reset που ένα πραγματικό κύκλωμα πρέπει πάντα να έχει). (γ) Αρχικοποιήστε τους καταχωρητές του κυκλώματος ελέγχου, δηλαδή τον state και τους καταχωρητές που οδηγούν τυχόν σήματα τύπου earlyOut, ώστε στον πρώτο κύκλο ρολογιού να εκτελεστεί ένα instruction fetch (κανονικά, και αυτό θα το έκανε το σήμα reset σ' ένα πραγματικό κύκλωμα).

Προσομοιώστε τη λειτουργία του επεξεργαστή και διορθώστε τα λάθη, τόσο στο κύκλωμα ελέγχου όσο και στο datapath που έχετε από την άσκηση 6. Θα πρέπει να τρέχουν σωστά τουλάχιστο μία από κάθε είδος εντολής, με μη τετριμένες τιμές των τελεστέων τους!

Αφού τρέξουν όλα, υπολογίστε με τη βοήθεια του SignalScan πόση είναι η ελάχιστη δυνατή περίοδος του ρολογιού σας, και αλλάξτε το top-level ώστε να τρέχει ο επεξεργαστής σας με αυτό το γρηγορότερο δυνατό ρολόϊ. Πόσα MHz καταφέρατε να "πιάσετε";

Παραδώστε, όπως και στις προηγούμενες ασκήσεις, τον κώδικά σας "control7.v", τις αλλαγές που κάνατε στο datapath, "datapath7.v", το test bench "test7.v" όπως τελικά το διαμορφώσατε, και ένα χαρακτηριστικό στιγμιότυπο από το Signalscan της άσκησης 7.3 με το γρηγορότερο δυνατό ρολόϊ σε μορφή jpeg ή gif, πακεταρισμένα στο αρχείο "ask7.tar.gz", μέσω:

        tar -cvf ask7.tar control7.v datapath7.v test7.v signals7.gif
        gzip ask7.tar
        ~hy225/bin/submit 7


Up to the Home Page of CS-225
 
 
© copyright University of Crete, Greece.
Written by S. Lyberis and M. Katevenis.
Last updated: 2 and 26 Apr. 2001, by M. Katevenis.