ΗΥ-120: Ψηφιακή Σχεδίαση
Φθινόπωρο 2005 |
Τμ. Επ. Υπολογιστών © Πανεπιστήμιο Κρήτης |
[Up - Table of Contents] [Prev - 11. Processor Datapath] |
[printer version - PDF] |
Στο σχήμα βλέπουμε ένα παράδειγμα προγράμματος που υπολογίζει το άθροισμα n+(n-1)+(n-2)+...+3+2+1 και το γράφει στη θέση 1A της μνήμης δεδομένων. Τον αριθμό n τον διαβάζει η πρώτη εντολή (διευθ. 20) από το πληκτρολόγιο: input 18· στο σχήμα υποτίθεται ότι ο αριθμός αυτός είναι 8. Ο υπολογισμός γίνεται χρησιμοποιώντας κυρίως τις θέσεις 18 και 19 της μνήμης δεδομένων (κάτω μέρος σχήματος), οι οποίες έχουν αρχική τιμή το "n" και το 0 (μεταβλητή "s") αντίστοιχα. Οι τρείς αρχικές εντολές του "βρόχου", στις θέσεις 21, 22, και 23, διαβάζουν την τρέχουσα τιμή της μεταβλητής s, της προσθέτουν την τρέχουσα τιμή της μεταβλητής n, και γράφουν το αποτέλεσμα πίσω στην s (s=s+n). Στο παράδειγμα, την πρώτη φορά που εκτελούνται αυτές οι εντολές, αυξάνουν το s από 0 σε 8. Οι τρείς επόμενες εντολές (θέσεις 24, 25, και 26) ελάττώνουν τη μεταβλητή n κατά 1· στο παράδειγμα, την πρώτη φορά που εκτελούνται, αλλάζουν το n από 8 σε 7.
Στη συνέχεια εκτελείται η εντολή bne 21 από τη θέση 27· η εντολή αυτή σημαίνει εάν ο συσσωρευτής δεν ισούται με μηδέν, διακλαδώσου (πήγαινε) στην εντολή 21 (branch if ACC not equal to zero - brach not equal - bne). Επειδή εκείνη την ώρα ο συσσωρευτής περιέχει το n=7, που είναι διάφορο του μηδενός, η συνθήκη της διακλάδωσης είναι αληθής και η διακλάδωση επιτυγχάνει (πραγματοποιείται). Έτσι, επόμενες εντολές εκτελούνται οι εντολές 21, 22, και 23, αυξάνοντας το s από 8 σε F (=15 στο δεκαδικό), και μετά οι 24, 25, και 26, μειώνοντας το n από 7 σε 6. Μετά, ξαναεκτελείται η bne 21· επειδή ο συσσωρευτής περιέχει το 6, η διακλάδωση επιτυγχάνει και πάλι. Έτσι, οι εντολές 21 έως και 27 θα ξαναεκτελεστούν κάμποσες φορές ακόμα, αυξάνοντας διαδοχικά το s κατά 6, 5, ..., 2, και 1, και μειώνοντας το n διαδοχικά σε 5, 4, ..., 1, και 0. Την τελευταία φορά, στο συσσωρευτή θα έχει μείνει n=0. Τότε, η διακλάδωση bne 21 θα αποτύχει, διότι ο συσσωρευτής δεν είναι πλέον διάφορος του μηδενός· έτσι, η επόμενη εντολή δεν θα διαβαστεί από τη θέση 21 όπως πρίν, αλλά από τη θέση 28, δηλαδή από την "από κάτω" θέση, όπως κάνουν και όλες οι άλλες εντολές που δεν είναι διακλαδώσεις. Τώρα, οι εντολές 28 και 29 θα αντιγράψουν το τελικό αποτέλεσμα 8+7+6+...+2+1 από τη θέση 19 (s) στη θέση 1A (S) και ο στόχος του προγράμματος θα έχει επιτευχθεί.
Όπως είδαμε στην §6.3, το πρόσημο ενός αριθμού κωδικοποιημένου σε συμπλήρωμα ως προς 2 είναι το περισσότερο σημαντικό (MS) bit του (το αριστερότερο bit): όταν αυτό είναι 1 τότε ο αριθμός είναι αρνητικός, ενώ όταν το MS bit είναι 0 ο αριθμός είναι θετικός ή μηδέν. Η ανίχνευση του εάν ο αριθμός είναι μηδέν ή διάφορος του μηδενός απαιτεί μιά πύλη NOR με τόσες εισόδους όσα τα bits του αριθμού. Αφού η έξοδος μιάς πύλης NOR είναι 1 όταν και μόνον όταν όλες οι είσοδοί της είναι μηδέν, συνδέοντας κάθε bit του συσσωρευτή σε μία είσοδο της NOR έχουμε την έξοδό της να ανάβει όταν και μόνον όταν όλα τα bits του συσσωρευτή είναι μηδέν, δηλαδή όταν ο ACC περιέχει τον αριθμό μηδέν. Ονομάζουμε accSign το MS bit του ACC, ονομάζουμε accZero την έξοδο της πύλης NOR 8 εισόδων, και παρέχουμε στο κύκλωμα ελέγχου αυτά τα δύο σήματα σαν εισόδους του. Τώρα, ο πίνακας αληθείας του κυκλώματος ελέγχου που είδαμε στη §11.10 πρέπει να συμπληρωθεί όπως παρακάτω. Η εντολή jumpx που φαίνεται ακριβώς κάτω από την jump είναι νέα και θα συζητηθεί στην επόμενη §12.3
opcode: accZero: Λειτουργία: alu_md acc2bus dm_wr pc_md accSign: dm_rd acc_ld io_in im_rd 0000 (add) x x ACC:=ACC+DM[A] 1 000 1 0 0 0 0 0 0001 (sub) x x ACC:=ACC-DM[A] 1 001 1 0 0 0 0 0 0010 (and) x x ACC:=ACCandDM[A] 1 010 1 0 0 0 0 0 0011 (nor) x x ACC:=ACCnorDM[A] 1 011 1 0 0 0 0 0 0100 (inp) x x DM[A]:=IO_BUS 0 xxx 0 0 1 1 0 0 0101 (ld) x x ACC:=DM[A] 1 1xx 1 0 0 0 0 0 0110 (st) x x DM[A]:=ACC 0 xxx 0 1 0 1 0 0 0111 (jmp) x x PC := A 0 xxx 0 0 0 0 1 1 1111 (jmpx) x x PC := DM[A] 1 xxx 0 0 0 0 0 1 1000 (beq) 0 x PC := PC+1 0 xxx 0 0 0 0 x 0 1000 1 x PC := A 0 xxx 0 0 0 0 1 1 1001 (bne) 0 x PC := A 0 xxx 0 0 0 0 1 1 1001 1 x PC := PC+1 0 xxx 0 0 0 0 x 0 1010 (blt) x 0 PC := PC+1 0 xxx 0 0 0 0 x 0 1010 x 1 PC := A 0 xxx 0 0 0 0 1 1 1011 (bge) x 0 PC := A 0 xxx 0 0 0 0 1 1 1011 x 1 PC := PC+1 0 xxx 0 0 0 0 x 0 Πάντα γιά όλες αυτές τις εντολές: pc_ld = 1 ; zero2bus = 0 ; addr_md = 0 ; tmp_ld = 0 ; io_out = 0 ;
Γιά να μπορεί ο υπολογιστής μας να εκτελεί διαδικασίες (procedures), πρέπει να έχουμε έναν τρόπο όταν τελειώνει η εκτέλεση της διαδικασίας να "πηγαίνει" το πρόγραμμα πίσω σε αυτόν που την κάλεσε, όποιος κι αν ήταν αυτός. Το θέμα είναι ότι την ίδια διαδικασία μπορεί να την καλέσουμε από πολλά και διαφορετικά μέρη ενός προγράμματος· πώς θα μπορέσει αυτή να "επιστρέψει" άλλοτε στο ένα και άλλοτε στο άλλο από αυτά, εάν η τελευταία της εντολή είναι ένα απλό άλμα που μας πηγαίνει πάντα σε ένα, σταθερό σημείο; Φυσικά, όποιος καλεί τη διαδικασία μπορεί να γράφει κάπου (δηλαδή στη μνήμη δεδομένων, όπου και μόνο μπορούν να γράφουν τα προγράμματα την ώρα που τρέχουν) τη διεύθυνσή του, δηλαδή τη διεύθυνση της εντολής όπου επιθυμεί να επιστρέψει η διαδικασία. Όμως το ζητούμενο είναι να έχουμε μιάν ειδική εντολή άλματος η οποία να μπορεί να μας στείλει σε μεταβλητή διεύθυνση προορισμού, δηλαδή σε προορισμούς που να ποικίλλουν την ώρα που τρέχει το πρόγραμμα. Αν την έχουμε αυτήν, τότε κάθε διαδικασία μπορεί να τελειώνει με μιά τέτοια εντολή, η οποία θα βρίσκει την επόμενή της εντολή διαβάζοντας τη διεύθυνση αυτής της επόμενης εντολής από την ειδική εκείνη θέση της μνήμης δεδομένων όπου αυτός που κάλεσε την διαδικασία έγραψε την εκάστοτε επιθυμητή διεύθυνση επιστροφής.
Την ειδική αυτή εντολή άλματος την ονομάζουμε jmpx ADDR (jump indexed - άλμα με δείκτη, ή άλλοτε jump indirect - έμμεσο άλμα), όπως αυτή φαίνονταν στο παραπάνω πίνακα αμέσως κάτω από την απλή εντολή άλματος jmp ADDR. Σαν σημασία και σαν υλοποίηση, η νέα εντολή είναι ένα κράμα του απλού άλματος και της εντολή φορτώματος (load) του συσσωρευτή. Σαν το απλό άλμα, το έμμεσο άλμα γράφει μιά νέα τιμή στον PC, διαφορετική από PC+1 (και γιά το σκοπό αυτό κάνει pc_md=1)· σαν την εντολή load όμως, αυτό που φορτώνει στον PC (αντί στον ACC) είναι το DM[ADDR], αντί του απλού ADDR (και γι' αυτό ανάβει το dm_rd αντί του im_rd): η νέα τιμή του PC είναι η (εν δυνάμει μεταβλητή) τιμή DM[ADDR] που διαβάζουμε από την μνήμη δεδομένων (στη θέση που μας λέει η εντολή, μέσω του ADDR), αντί της σταθερής τιμής ADDR που διαβάζουμε από την μνήμη εντολών.
Ένα άλλο παράδειγμα χρήσης της νέας εντολής έμμεσου άλματος έχουμε στις πρώτες δύο θέσεις της μνήμης εντολών του υπολογιστή μας, όπως τις είδαμε στην §11.11: ο υπολογιστής μας αρχίζει πάντα να εκτελεί εντολές από τη διεύθυνση 00, όπου βρίσκει σαν δύο πρώτες εντολές τις "input 10; jmpx 10" (στο εργαστήριο 11.11 εκτελούσατε την jmpx σαν απλή jmp, αλλά εδώ τα πράγματα αλλάζουν). Η πρώτη εντολή, input 10, παραλαμβάνει έναν αριθμό από την περιφερειακή συσκευή (πληκτρολόγιο), και τον γράφει στη θέση 10 της μνήμης δεδομένων (DM[10]). Η δεύτερη εντολή, jmpx 10, εκτελεί ένα (έμμεσο!) άλμα --όχι στη διεύθυνση 10, αλλά στη διεύθυνση που διαβάζει από την παραπάνω θέση 10 της μνήμης δεδομένων, DM[10]. Έτσι τελικά, σαν τρίτη εντολή εκτελείται η εντολή από τη διεύθυνση που ο χρήστης έδωσε από το πληκτρολόγιο, δηλαδή μιά εντολή που μπορεί να αλλάζει κάθε φορά που εκτελείται το πρόγραμμα! Στο εργαστήριο, όταν θέλετε να εκτελέσετε το πρόγραμμα της §12.1, πληκτρολογήστε "0020" (δεκαεξαδικό) όταν ανάβει ο υπολογιστής: η πρώτη εντολή "input 10" θα προκαλέσει "DM[10] := 20", και η δεύτερη εντολή "jmpx 10" θα προκαλέσει "PC := DM[10] = 20")· έτσι, σαν τρίτη εντολή θα εκτελεστεί η εντολή 20, που είναι η πρώτη εντολή του προγράμματος §12.1. Αν πάλι θέλετε να εκτελέσετε το πρόγραμμα της παρακάτω §12.5, που αρχίζει στη διεύθυνση 30 (δεκαεξαδικό) της μνήμης εντολών, πληκτρολογήστε "0030" όταν ανάβει ο υπολογιστής: η "input 10" θα κάνει "DM[10] := 30", και η "jmpx 10" θα κάνει "PC := DM[10] = 30".
Στο εργαστήριο, εάν μεν προλαβαίνετε να κατασκευάσετε το κύκλωμα της §12.7, τότε προσπεράστε αυτό εδώ το κύκλωμα (που είναι υποσύνολο εκείνου). Αλλοιώς, κατασκευάστε το παραπάνω κύκλωμα ελέγχου και δοκιμάστε το με τα προγράμματα από τις διευθύνσεις 10 (§11.11) και 20 (§12.1). Πηγαίνετε στο πρόγραμμα που επιθυμείτε να εκτελέσετε με την μέθοδο που εξηγήσαμε στο τέλος της §12.3.
Στο παράδειγμα δεξιά, υπάρχει ένας πίνακας (array) από αριθμούς, A0, A1, A2, A3,... αποθηκευμένος στις διευθύνσεις μνήμης (δεδομένων) 80, 81, 82, 83,.... Παρατηρήστε ότι το στοιχείο Ai βρίσκεται στη διεύθυνση (80+i). Ο αριθμός A = 80 λέγεται διεύθυνση βάσης του πίνακα. Ας πούμε ότι θέλουμε να διπλασιάσουμε τα n πρώτα στοιχεία αυτού του πίνακα, A0, ..., An-1, όπου n είναι ένας αριθμός που δίδεται σαν είσοδος. Αυτό θα το κάνουμε με το βρόχο που φαίνεται γραμμένος σε γλώσσα C στο σχήμα πάνω αριστερά· το πρόγραμμα αυτό βρίσκεται γραμμένο στις θέσεις 30 - 3C (μνήμη εντολών) του υπολογιστή στο εργαστήριο, και φαίνεται δεξιά σε γλώσσα Assembly. Η εντολή input 1C (θέση 30) διαβάζει την τιμή n και την γράφει στη θέση 1C της μνήμης δεδομένων. Οι δύο επόμενες εντολές αρχικοποιούν τη μεταβλητή i του προγράμματος, που κρατιέται στη διεύθυνση 1D, χρησιμοποιώντας την τιμή 0 από τη θέση 00. Την μεταβλητή i θα χρησιμοποιήσουμε σαν μετρητή του βρόχου μας, γιά να επεξεργαζόμαστε κάθε φορά το στοιχείο Ai του πίνακα.
Ο βρόχος αρχίζει στη διεύθυνση 33, ελέγχοντας αν το i είναι μικρότερο του n· αν δεν είναι ο βρόχος δεν εκτελείται άλλο (γιά n αρνητικό ή μηδέν, δεν θα εκτελεστεί ούτε μιά φορά). Η σύγκριση γίνεται μέσω της ισοδύναμης έκφρασης i-n<0: αφού υπολογιστεί το i-n, φεύγουμε από το βρόχο όταν η έκφραση αυτή είναι ψευδής, δηλαδή όταν i-n είναι μεγαλύτερο ή ίσο με μηδέν (εντολή bge)· η έξοδος από το βρόχο, εδώ, είναι επιστροφή στην αρχή του προγράμματος γιά να ζητηθεί νέα τιμή n. Όταν μπούμε στο βρόχο, αρχικά υπολογίζουμε την διεύθυνση p του στοιχείου Ai του πίνακα, διότι μόνον αν ξέρουμε τη διεύθυνσή του μπορούμε να το βρούμε και να το επεξεργαστούμε· η διεύθυνση p του δεδομένου Ai λέγεται pointer (δείκτης) σε αυτό το στοιχείο. Όπως παρατηρήσαμε παραπάνω, η διεύθυνση αυτή είναι p=(80+i), και την υπολογίζουμε με τις εντολές 36-38, οι οποίες και την γράφουν στη θέση 1F της μνήμης δεδομένων. Κατά την πρώτη επανάληψη του βρόχου, i=0 και p=80 δείχνει στο πρώτο στοιχείο, A0· στην δεύτερη επανάληψη, i=1 και p=81 δείχνει στο A1· την τρίτη φορά, όπως στο σχήμα, i=2 και p=82 δείχνει το A2.
Οι τρείς νέες εντολές του υπολογιστή μας φαίνονται στις θέσεις 39-3B του προγράμματος. Ενώ η απλή εντολή ld ADDR κάνει ACC:=DM[ADDR] (δηλαδή φορτώνει τον καταχωρητή από τη θέση ADDR), η νέα εντολή, load indexed, ldx ADDR, κάνει ACC:=DM[DM[ADDR]], δηλαδή φορτώνει τον καταχωρητή από τη θέση της μνήμης δεδομένων την οποία ορίζει η θέση ADDR --όπως λέμε, έχουμε μιαν έμμεση πρόσβαση (indirect access). Στο παράδειγμά μας, ενώ η απλή εντολή ld 1F θα έφερνε στον συσσωρευτή τα δεδομένα της θέσης 1F, δηλαδή τον αριθμό 82, η νέα εντολή ldx 1F διαβάζει από τη θέση 1F τον αριθμό 82, και χρησιμοποιεί αυτό τον αριθμό σα διεύθυνση του πραγματικού δεδομένου γιά να φέρει τελικά στο συσσωρευτή τον αριθμό A2 από τη θέση 82. Εάν η εντολή ήταν μιά απλή load, τότε κάθε φορά που θα επαναλαμβάνονταν ο βρόχος, πάντα η εντολή αυτή θα διάβαζε την ίδια θέση μνήμης, 1F. Τώρα που η εντολή είναι load indexed, σε κάθε επανάληψη του βρόχου η θέση μνήμης που τελικά διαβάζουμε αλλάζει: στην αρχή ήταν η 80, μετά η 81, ύστερα η 82, κ.ο.κ. έτσι πετυχαίνουμε να επεξεργαζόμαστε διαφορετικό στοιχείο της δομής δεδομένων κάθε φορά!
Κατ' αναλογία προς την ldx 1F, η add indexed addx 1F διαβάζει από τη θέση 1F τον αριθμό 82, και χρησιμοποιεί αυτό τον αριθμό σα διεύθυνση του πραγματικού δεδομένου γιά να προσθέσει τελικά στο συσσωρευτή τον αριθμό A2 από τη θέση 82. Αφού ο συσσωρευτής περιείχε ήδη τον αριθμό A2, το άθροισμα που προκύπτει ισούται με το επιθυμητό διπλάσιο, 2*A2. Κατ' αναλογία προς τις ldx και addx, η store indexed stx 1F διαβάζει από τη θέση 1F τον αριθμό 82, και χρησιμοποιεί αυτό τον αριθμό σα διεύθυνση της πραγματικής θέσης όπου θα αποθηκεύσει το περιεχόμενο του συσσωρευτή· έτσι, στο παράδειγμά μας, η stx 1F θα γράψει τελικά στη θέση 82 (στην προηγούμενη ανακύκλωση είχε γράψει στη θέση 81, και στην προ-προηγούμενη στη θέση 80). Άρα, τελικά, το αρχικό A2 στη θέση 82 αντικαθίσταται από το διπλάσιό του. Ο βρόχος ολοκληρώνεται αυξάνοντας το i κατά 1, και επιστρέφοντας στην εντολή 33 γιά να συγκρίνουμε τη νέα τιμή του i με το n, κ.ο.κ.
Όπως είδαμε, οι νέες εντολές μεταφέρουν μεταξύ μνήμης και συσσωρευτή όχι το περιεχόμενο μιάς σταθερής --πάντα της ίδιας-- θέσης (διεύθυνσης) μνήμης, αλλά το περιεχόμενο μιάς μεταβλητής θέσης μνήμης --μιάς θέσης που τη διεύθυνσή της να μπορεί να την υπολογίζει και να την αλλάζει κατά βούληση το ίδιο το πρόγραμμα την ώρα που τρέχει! Γιά να κάνουμε και αφαιρέσεις και λογικές πράξεις με στοιχεία δομών δεδομένων, θα μας βόλευαν και οι αντίστοιχες εντολές subx, andx, norx. Πάντως, μπορούμε να κάνουμε και χωρίς αυτές (ακομή και χωρίς την addx), αρκεί να έχουμε τις ldx και stx: Αντιγράφουμε το επιθυμητό στοιχείο της δομής δεδομένων σε μιά σταθερή θέση tmp (π.χ. ldx p; st tmp), κάνουμε τις πράξεις που θέλουμε πάνω σε τέτοιες σταθερές προσωρινές θέσεις (π.χ. ld tmp2; sub tmp), και στο τέλος στέλνουμε το αποτέλεσμα στη θέση της δομής δεδομένων που θέλουμε (π.χ. stx p).
Γιά τον ίδιο αυτό λόγο, οι εντολές indexed δεν μπορούν να εκτελεστούν σε ένα μόνο κύκλο ρολογιού, όπως οι υπόλοιπες εντολές: χρειάζεται μιά ενδιάμεση ακμή ρολογιού η οποία να τερματίσει την πρώτη ανάγνωση από τη μνήμη δεδομένων και να ξεκινήσει την δεύτερη. Έτσι, και το κύκλωμα ελέγχου δεν μπορεί πιά να είναι συνδυαστικό: χρειάζεται και 1 bit κατάστασης που να μας πληροφορεί αν τώρα βρισκόμαστε στον πρώτο ή στο δεύτερο κύκλο της εκτέλεσης. Στο πρώτο διάγραμμα, πιό κάτω, φαίνονται οι πράξεις και μεταφορές καταχωρητών που πρέπει να γίνουν στο datapath κατά τον πρώτο κύκλο εκτέλεσης των τριών νέων εντολών. Πρέπει να γίνει ανάγνωση από τη μνήμη δεδομένων (ενεργοποίηση dm_rd), αλλά το προϊόν αυτής της ανάγνωσης είναι ένα ενδιάμεσο αποτέλεσμα --μία νέα διεύθυνση-- και όχι το τελικό αποτέλεσμα της εντολής. Γιά το λόγο αυτό η έξοδος της μνήμης δεδομένων, από το BUS, οδηγείται να αποθηκευτεί στον προσωρινό καταχωρητή TMP, μέσω ενεργοποίησης του tmp_ld. Το addr_md πρέπει να έχει την κανονική του τιμή (0), διότι η πρώτη αυτή ανάγνωση από την μνήμη δεδομένων γίνεται από την θέση που υποδεικνύει το πεδίο ADDR της εντολής. Πρέπει να κρατηθούν σβηστά τα pc_ld και acc_ld διότι δεν πρέπει να πειραχτούν οι αντίστοιχοι καταχωρητές: ο PC δεν πρέπει να αλλάξει διότι δεν τέλειωσε η εκτέλεση της εντολής ακόμα (μόλις αλλάξει ο PC αλλάζει και η εντολή στην έξοδο της μνήμης εντολών), και ο ACC δεν πρέπει να αλλάξει διότι δεν είμαστε ακόμη έτοιμοι να κάνουμε την επιθυμητή πράξη πάνω του (π.χ. πρόσθεση νέων δεδομένων σε αυτόν (addx), ή αποθήκευσή του (stx)).
Όπως είπαμε παραπάνω, αφού μερικές εντολές του τελικού υπολογιστή μας χρειάζονται δύο κύκλους ρολογιού γιά την εκτέλεσή τους, το κύκλωμα ελέγχου δεν μπορεί πιά να είναι συνδυαστικό: χρειάζεται και 1 bit κατάστασης που να μας πληροφορεί αν τώρα βρισκόμαστε στον πρώτο ή στο δεύτερο κύκλο της εκτέλεσης. Η FSM του κυκλώματος ελέγχου που προκύπτει θα έχει το διάγραμμα καταστάσεων που φαίνεται δίπλα. Έστω S το bit κατάστασης της FSM. Όταν S=0 θα σημαίνει ότι βρισκόμαστε στον πρώτο ή και μοναδικό κύκλο εκτέλεσης της εντολής. Μετά από έναν κύκλο S=0, μόνον οι εντολές ldx, addx, stx πηγαίνουν στην κατάσταση S=1, δηλαδή σε δεύτερο κύκλο εκτέλεσης --όλες οι άλλες παραμένουν στην κατάσταση S=0, δηλαδή στον πρώτο κύκλο εκτέλεσης, φυσικά της επόμενης εντολής. Οι εντολές ldx, addx, stx, μετά την κατάσταση S=1, πηγαίνουν πάντα στην S=0, δηλαδή στον πρώτο κύκλο εκτέλεσης --φυσικά της επόμενης εντολής.
Κανονικά, χρειαζόμαστε ένα σήμα αρχικοποιήσης (reset) της FSM, που να την φέρνει στην κατάσταση S=0. Γιά λόγους απλοποίησης δεν θα χρησιμοποιήσουμε τέτοιο σήμα, βασιζόμενοι στο ότι αν η FSM εκκινήσει σε S=1, τότε πάντοτε τον επόμενο κύκλο θα μεταβεί σε S=0, σύμφωνα με το διάγραμμα μετάβασης καταστάσεών της. Επίσης, πάλι γιά απλοποίηση, θα κάνουμε τις εντολές που εκτελούνται σε ένα κύκλο να αγνοούν την τιμή του bit κατάστασης, S. Έτσι, εάν η εντολή που εκτελείται τον πρώτο κύκλο ρολογιού δεν είναι ldx, addx, stx, αυτή θα εκτελεστεί σωστά ακόμα κι αν το bit κατάστασης S=1 κατά τον πρώτο εκείνο κύκλο, λόγω έλλειψης reset. Σε μας αυτό εξασφαλίζεται, γιατί η εντολή στη θέση 00 είναι η input 10. Φυσικά, το σήμα reset δεν μπορούμε να το γλυτώσουμε εντελώς, διότι πρέπει κάποιος να αρχικοποιήσει τον PC=0· γιά απλοποίηση των κυκλωμάτων σας, αυτό το κάνει μόνη της η πλακέτα του υπλογιστή, με το δικό της κουμπί reset (αριστερά κάτω) ή μόλις ανάβει η τροφοδοσία.
Βάσει των παραπάνω, ο πίνακας αληθείας της §12.2 επεκτείνεται με τις 3 νέες εντολές όπως φαίνεται παρακάτω. Αυτό είναι και το πλήρες κύκλωμα ελέγχου του υπολογιστή μας. Η είσοδος S είναι το bit κατάστασης· η έξοδος nS είναι η επόμενη κατάσταση. Το σήμα pc_md γιά τις εντολές διακλάδωσης εμφανίζεται σαν απλός αστερίσκος (*), παραπέμποντας στον πλήρη ορισμό του στην παραπάνω §12.2 ή στήν ισοδύναμη εξίσωση στην §12.4· γιά τον ίδιο λόγο, οι είσοδοι accZero, accSign έχουν παραληφθεί από τον πίνακα. Όσον αφορά το σήμα tmp_ld, παρ' ότι έχουμε μόνον ανάγκη αυτό να ανάβει στην κατάσταση S=0 των 3 νέων εντολών, εντούτοις δεν βλάπτει να ανάβει πάντα, δεδομένου ότι το περιεχόμενο του TMP το καταναλώνουμε πάντα μέσα στον πρώτο κύκλο μετά το φόρτωμά του, άρα δεν έχουμε ανγκη αυτό να διατηρείται γιά μακρότερο. Παρατηρήστε ότι το σήμα pc_ld είναι το συμπλήρωμα του nS (φορτώνουμε πάντα τον PC αμέσως πριν μεταβούμε στην κατάσταση 0 γιά να εκτελέσουμε μιάν επόμενη εντολή).
opcode: Λειτουργία: addr_md alu_md acc2bus dm_wr pc_md S: nS: dm_rd acc_ld io_in im_rd pc_ld 0000 (add) x ACC:=ACC+DM[A] 0 0 1 000 1 0 0 0 0 0 1 0001 (sub) x ACC:=ACC-DM[A] 0 0 1 001 1 0 0 0 0 0 1 0010 (and) x ACC:=ACCandDM[A] 0 0 1 010 1 0 0 0 0 0 1 0011 (nor) x ACC:=ACCnorDM[A] 0 0 1 011 1 0 0 0 0 0 1 0100 (inp) x DM[A]:=IO_BUS 0 0 0 xxx 0 0 1 1 0 0 1 0101 (ld) x ACC:=DM[A] 0 0 1 1xx 1 0 0 0 0 0 1 0110 (st) x DM[A]:=ACC 0 0 0 xxx 0 1 0 1 0 0 1 0111 (jmp) x PC := A 0 0 0 xxx 0 0 0 0 1 1 1 1000 (beq) x PC := A or PC+1 0 0 0 xxx 0 0 0 0 1 * 1 1001 (bne) x PC := A or PC+1 0 0 0 xxx 0 0 0 0 1 * 1 1010 (blt) x PC := A or PC+1 0 0 0 xxx 0 0 0 0 1 * 1 1011 (bge) x PC := A or PC+1 0 0 0 xxx 0 0 0 0 1 * 1 1100 (addx) 0 TMP:=DM[A] 1 0 1 xxx 0 0 0 0 0 x 0 1100 1 ACC:=ACC+DM[TMP] 0 1 1 000 1 0 0 0 0 0 1 1101 (ldx) 0 TMP:=DM[A] 1 0 1 xxx 0 0 0 0 0 x 0 1101 1 ACC:= DM[TMP] 0 1 1 1xx 1 0 0 0 0 0 1 1110 (stx) 0 TMP:=DM[A] 1 0 1 xxx 0 0 0 0 0 x 0 1110 1 DM[TMP]:=ACC 0 1 0 xxx 0 1 0 1 0 0 1 1111 (jmpx) x PC := DM[A] 0 0 1 xxx 0 0 0 0 0 1 1 Πάντα γιά όλες αυτές τις εντολές: zero2bus = 0 ; tmp_ld = 1 ; io_out = 0 ;
Στο εργαστήριο, κατασκευάστε το κύκλωμα ελέγχου του απλού υπολογιστή, χρησιμοποιώντας την παραπάνω PAL καθώς και δικά σας chips γιά το (ένα) flip-flop κατάστασης, γιά έναν αντιστροφέα γιά το bit κατάστασης, και γιά μερικές πύλες AND και OR που θα χρειαστείτε. Θα χρειαστείτε ένα κοινό ρολόϊ που να τροφοδοτεί τόσο το δικό σας flip-flop κατάστασης όσο και το datapath του απλού υπολογιστή. Χρησιμοποιήστε το διακόπτη A, από την πλακέτα εισόδων/εξόδων, δεξιά, γιά το σκοπό αυτό. Την έξοδο αυτού του διακόπτη, από την καλωδιοταινία, δώστε την τόσο στο δικό σας chip με το flip-flop κατάστασης όσο και στο datapath του υπολογιστή. Γιά τον τελευταίο αυτό σκοπό, τροφοδοτήστε το σήμα από τον διακόπτη A στο μικρό (ανώνυμο!) ακροδέκτη του datapath που βρίσκεται δεξιά από τους 4 ακροδέκτες του opcode και αριστερά από τα δύο chips που είναι αριστερά από το διακόπτη ρολογιού του datapath. Εσωτερικά, ο απλός υπολογιστής χρησιμοποιεί σαν ρολόϊ του το λογικό OR αυτού του ακροδέκτη και του δικού του διακόπτη, άρα τελικά, πατώντας μόνο τον διακόπτη A και όχι τον διακόπτη ρολογιού του datapath, θα έχετε ένα ισοδύναμο ρολόϊ γιά το datapath που ταυτίζεται με την έξοδο του διακόπτη A.
Ελέγξτε το κύκλωμά σας και τον υπολογιστή εκτελώντας τα παραπάνω προγράμματα στις διευθύνσεις 20 και 30 γιά διάφορες τιμές εισόδου. Ο πίνακας στοιχείων A0, A1, ..., A127 στις διευθύνσεις 80, 81, ..., FF της μνήμης δεδομένων αρχικοποιείται γιά σας σε κάθε reset στις τιμές: 01, FF, 02, FE, 03, FD, 04, FC, ..., 3F, C1, 40, C0, κατά σειρά.
Εκτός από την ταχύτητα ενός κυκλώματος,
το άλλο σημαντικό χαρακτηριστικό που μας απασχολεί
είναι η κατανάλωση ενέργειας ανά μονάδα χρόνου (ηλεκτρική ισχύς):
πόσο γρήγορα ξοδεύει την μπαταρία,
ή πόσο πολύ ζεσταίνεται (πόσους ανεμιστήρες ή κλιματιστικά χρειάζεται).
Η μεγαλύτερη συνηθισμένη πηγή κατανάλωσης ισχύος των chips CMOS
είναι η ενέργεια φόρτισης και εκφόρτισης
των παρασιτικών χωρητικοτήτων που αυτά έχουν μέσα τους.
Κάθε φορά που η λογική τιμή ενός ηλεκτρικού κόμβου
ανεβαίνει από το 0 στο 1 και μετά ξαναπέφτει στο 0,
χάνεται (μετατρέπεται σε θερμότητα) ποσότητα ενέργειας
ίση πρός C·V2,
όπου C η παρασιτική χωρητικότητα του κόμβου
και V η τάση τροφοδοσίας.
Αυτός είναι ο κύριος λόγος γιά τον οποίο οι νεότερες γενιές chips
έχουν χαμηλότερη τάση τροφοδοσίας (π.χ. 2.5 V, 1.8 V, 1.2 V)
από τις παλαιότερες.
Γιά δοσμένη τάση τροφοδοσίας,
όσο περισσότεροι, και μεγαλύτερης χωρητικότητας, ηλεκτρικοι κόμβοι
ανεβοκατεβαίνουν (αναβοσβήνουν) μέσα σε ένα chip,
και όσο περισσότερες φορές ανά δευτερόλεπτο το κάνουν αυτό,
τόσο μεγαλύτερη ισχύ καταναλώνει το chip αυτό.
Άρα, γιά μικρή κατανάλωση, πρέπει ένα κύκλωμα
να είναι μικρό (λίγες πύλες, μικρή χωρητικότητα),
ή να δουλεύει αργά (λιγότερα ανεβοκατεβάσματα ανά δευτερόλεπτο),
ή να δουλεύει γιά λίγο και μετά να κάθεται (να μην αλλάζει κατάσταση),
ή τις περισσότερες φορές
να εργάζεται ένα μικρό μόνο μέρος του κυκλώματος
και το υπόλοιπο να κάθεται.
[Up - Table of Contents] [Prev - 11. Processor Datapath] |
[printer version - PDF] |
Up to the Home Page of CS-120
|
© copyright
University of Crete, Greece.
last updated: 19 Dec. 2005, by M. Katevenis. |