Εκτός από τις εξαιρέσεις παράνομης εντολής (illegal opcode)
--που έχει και ο επεξεργαστής του βιβλίου
(παράγραφος 5.6, σελίδες 358-364 Ελλ. εκδ.)
και που θα υλοποιήσουμε και εμείς--
και εκτός από τις εξαιρέσεις αριθμητικής υπερχείλισης
--που δεν θα υλοποιήσουμε εμείς--
σε αυτή τη σειρά ασκήσεων θα υλοποιήσετε και
Εξαιρέσεις Προσπελάσεων Μνήμης:
Άσκηση 13.2:
Προσθήκη Εξαιρέσεων (Exceptions)
Προσθέστε στο datapath και στον έλεγχο
όσα χρειάζονται γιά να υλοποιεί ο επεξεργαστής σας
Εξαιρέσεις Παράνομης Εντολής
και Εξαιρέσεις Μνήμης.
Η ιδέα είναι η ίδια με ό,τι περιγράφει το βιβλίο
στην παράγραφο 5.6 (σελίδες 358-364 Ελλ. εκδ.),
αλλά εμείς θα έχουμε διαφορετικές αιτίες εξαιρέσεων,
και μία μόνο πρόσθετη κατάσταση ελέγχου,
την "exception_st",
γιά να τις υλοποιήσουμε.
Όπως και στο βιβλίο, πάντως, θα προσθέσετε
δύο νέους (ακμοπυροδότητους) καταχωρητές:
"epc" (Exception PC), των 32 bits,
γιά να κρατά τη διεύθυνση της "κακιάς" εντολής (συν 4), και
"cause" (αιτία), επίσης των 32 bits,
γιά να κρατά έναν κώδικα που υποδεικνύει την αιτία της εξαίρεσης.
Περιγράψτε τον "epc" σαν RegLd (όπως και τον pc),
ενώ τον "cause" μπορείτε να τον περιγράψτε σαν reg
--όπως και τον currState.
Οι αιτίες εξαιρέσεων και οι αντίστοιχες δράσεις θα είναι:
-
Παράνομη Εντολή (Illegal Instruction):
Στη διάρκεια του 2ου κύκλου κάθε εντολής,
δηλαδή κατά την κατάσταση decode_rr,
εάν ο έλεγχος διαπιστώσει ότι ο συνδυασμός op-funct
δεν αντιστοιχεί σε καμία από τις εντολές
που ο επεξεργαστής μας ξέρει να εκτελεί
(αυτές των ασκήσεων 11 και 12.1),
φορτώνει τον αριθμό μηδέν (0) στον καταχωρητή cause,
και μεταβαίνει, σαν επόμενη κατάσταση, στην κατάσταση exception_st.
-
Εξαιρέσεις Μνήμης (Memory Exceptions):
Όπως είπαμε παραπάνω,
στη διάρκεια των κύκλων (καταστάσεων) προσπέλασης μνήμης,
i_fetch, mem_rd, και mem_wr,
το κύκλωμα ελέγχου θα παρακολουθεί
τη διεύθυνση ma που δίδεται στη μνήμη,
και εάν αυτή δεν είναι σωστή
φορτώνει στον καταχωρητή cause τον αριθμό που δίδεται παρακάτω
(στο δεκαδικό),
και μεταβαίνει, σαν επόμενη κατάσταση, στην κατάσταση exception_st.
-
Παραβίαση ευθυγράμμισης κατά τη διάρκεια i_fetch.
-
Σφάλμα σελίδας κατά τη διάρκεια i_fetch.
-
Παραβίαση ευθυγράμμισης και σφάλμα σελίδας
κατά τη διάρκεια i_fetch.
-
Παραβίαση ευθυγράμμισης κατά τη διάρκεια mem_rd.
-
Σφάλμα σελίδας κατά τη διάρκεια mem_rd.
-
Παραβίαση ευθυγράμμισης και σφάλμα σελίδας
κατά τη διάρκεια mem_rd.
-
Παραβίαση ευθυγράμμισης κατά τη διάρκεια mem_wr.
-
Σφάλμα σελίδας κατά τη διάρκεια mem_wr.
-
Παραβίαση ευθυγράμμισης και σφάλμα σελίδας
κατά τη διάρκεια mem_wr.
-
exception_st:
Στην κατάσταση αυτή πρέπει
πρώτον να αποθηκεύσουμε τη διεύθυνση της "κακιάς" εντολής (συν 4)
στον καταχωρητή epc,
και δεύτερον να φορτώσουμε στον pc
τη διεύθυνση όπου ξεκινά ο interrupt handler.
Η διεύθυνση της "κακιάς" εντολής (συν 4) είναι
το (ήδη αυξημένο κατά 4) περιεχόμενο του pc.
Η διεύθυνση όπου ξεκινά ο interrupt handler,
στο μεν κανονικό MIPS είναι η 32'h80000080,
στη δε δική μας περίπτωση θα την κάνουμε να είναι η 32'd2048,
προκειμένου αυτή να πέφτει μέσα στην υλοποιημένη μνήμη (στη μέση της).
Άσκηση 13.3: Πλήρες Debugging
Ελέγξτε εξονυχιστικά τον επεξεργαστή που σχεδιάσατε,
και διορθώστε τα λάθη μέχρι ότου αυτός εκτελεί επιτυχώς
τα μεγάλα προγράμματα που θα βρείτε στην περιοχή
"~hy225/verilog/test/test13/".
Υπολογίστε με τη βοήθεια του SignalScan
πόση είναι η ελάχιστη δυνατή περίοδος του ρολογιού σας,
και αλλάξτε το top-level ώστε να τρέχει ο επεξεργαστής σας
με αυτό το γρηγορότερο δυνατό ρολόϊ.
Πόσα MHz καταφέρατε να "πιάσετε";
Παραδώστε, σχεδόν όπως και στις προηγούμενες ασκήσεις,
τον κώδικά σας,
"datapath13.v" και "control13.v",
το νέο test bench και μνήμη,
"test13.v" και "memory13.hex",
και ένα χαρακτηριστικό στιγμιότυπο,
"signals13.jpg",
από το Signalscan με ένα από τα πλέον πολύπλοκα tests,
πακεταρισμένα σε ένα αρχείο,
που όμως αυτή τη φορά να είναι τύπου zip και όχι tar όπως πριν
(προκειμένου να αποφεύγεται καλύτερα ο κίνδυνος καταστροφής
του πρώτου αρχείου πηγής αν ξεχάσει κανείς το όρισμα προορισμού),
"ask13.zip", μέσω:
zip ask13.zip datapath13.v control13.v test13.v memory13.hex signals13.jpg
~hy225/bin/submit 13
Προαιρετική Άσκηση 13.4:
Απλή Ομοχειρία (Προ-Ανάγνωση επόμενης Εντολής)
Όσοι από σας ενδιαφέρεστε ιδιαίτερα γιά το hardware και έχετε χρόνο,
αλλάξτε το datapath και την FSM και το κύκλωμα ελέγχου
προκειμένου να εισάγετε στον επεξεργαστή σας
μιάν απλή μορφή ομοχειρίας (pipelining) δύο (μόνο) επιπέδων:
κατά τη διάρκεια εκτέλεσης κάθε εντολής
θα γίνεται και η ανάγνωση της επόμενης εντολής.
Αυτή η απλή μορφή pipelining ονομάζεται συνήθως "fetch-execute overlap".
Στην παλαιά (non-pipelined) μορφή του επεξεργαστή,
κάθε εντολή "παραδίδει" στην επόμενή της
--εκτός από τα περιεχόμενα των καταχωρητών γενικού σκοπού $0-$31
και τα περιεχόμενα της μνήμης--
την τιμή (διεύθυνση) που περιέχεται στον PC,
και η οποία καθορίζει από πού θα διαβαστεί η επόμενη εντολή.
Στη νέα (pipelined) μορφή του επεξεργαστή,
κάθε εντολή θα "παραδίδει" στην επόμενή της
--εκτός από τους $0-$31 και τη μνήμη--
τόσο τη διεύθυνση της επόμενης εντολής συν 4,
σαν περιεχόμενο του PC,
όσο και την ίδια την επόμενη εντολή,
σαν περιεχόμενο του IR.
Έτσι,
η εκτέλεση της κάθε εντολής θα ξεκινά με
έτοιμο, φορτωμένο τον καταχωρητή IR,
και επομένως θα γλιτώνουμε τον ένα κύκλο ανάγνωσης της εντολής,
δηλαδή δεν θα υπάρχει η κατάσταση i_fetch.
Αυτό θα επιτευχθεί ώς εξής:
-
decode_rr:
Όλες οι εντολές ξεκινούν με την κατάσταση αυτή.
Στη διάρκειά της, εκτός από όσα ήδη γίνονταν,
θα γίνονται και τα εξής:
- Διαβάζουμε από M[pc] και φυλάμε αυτό που διαβάσαμε
στον (νέο) καταχωρητή irNxt.
Αφού ο καταχωρητής pc περιέχει
τη διεύθυνση της παρούσας εντολής συν 4,
αυτό που διαβάσαμε από τη μνήμη είναι η "από κάτω" εντολή.
Σε περίπτωση που η παρούσα εντολή αποδειχθεί ότι δεν είναι
εντολή επιτυχημένης μεταφοράς ελέγχου,
αυτό που διαβάσαμε θα είναι η επόμενη εντολή (next IR).
- Υπολογίζουμε την τιμή pc+4,
και την φυλάμε σ' έναν (νέο) καταχωρητή pcNxt.
Δεδομένου ότι η κεντρική ALU είναι απασχολημένη
με τον υπολογισμό του pc+Immx4,
χρειαζόμαστε έναν δεύτερο αθροιστή (ALU).
Σε περίπτωση που η παρούσα εντολή αποδειχθεί ότι δεν είναι
εντολή επιτυχημένης μεταφοράς ελέγχου,
αυτό που υπολογίσαμε θα είναι ο PC (συν 4) της επόμενης εντολής.
-
jump:
Στην κατάσταση αυτή
διαβάζουμε από M[jmpAddr] και φυλάμε αυτό που διαβάσαμε στον ir
(είναι η επόμενη εντολή).
Επίσης, υπολογίζουμε την τιμή jmpAddr+4
και την φυλάμε στον pc
(είναι η διεύθυνση της επόμενης εντολής συν 4).
-
branch:
Είτε προσθέτετε μιά νέα κατάσταση μετά από την κατάσταση αυτή,
και εκεί πηγαίνετε όταν η διακλάδωση επιτύχει
και κάνετε τα ανάλογα όπως και παραπάνω στην jump,
είτε μέσα στην κατάσταση branch
διαβάζετε από M[ALUout] (εντολή προορισμού)
και υπολογίζετε το ALUout+4,
και στο τέλος του κύκλου
φορτώνετε τον ir από M[ALUout] (επιτυχημένη διακλάδωση)
ή από irNxt (αποτυχημένη διακλάδωση),
και τον pc ανάλογα από ALUout+4 ή pcNxt.
-
Τελευταίος κύκλος των υπολοίπων εντολών:
επιπλέον του ό,τι κάνατε σε κάθε τέτοιον τελευταίο κύκλο,
φορτώνετε τον ir από τον irNxt και τον pc από τον pcNxt,
ώστε να τα βρεί έτοιμα η κατάσταση decode_rr της επόμενης εντολής.