ΗΥ-225: Οργάνωση Υπολογιστών
Ανοιξη 2001 |
Τμ. Επ. Υπολογιστών Πανεπιστήμιο Κρήτης |
Κάθε εντολή γλώσσας μηχανής αποτελείται από έναν κώδικα πράξης (opcode --operation code), και από τελεστέους (operands) που περιγράφουν πάνω σε τι θα γίνει η πράξη. Οι τελεστέοι των εντολών αριθμητικών πράξεων του MIPS είναι πάντα καταχωρητές (registers) της κεντρικής μονάδας επεξεργασίας (CPU --Central Processing Unit), ή σταθερές ποσότητες, αλλά όχι θέσεις μνήμης. Η CPU του MIPS έχει 32 καταχωρητές των 32 bits καθένας --32 bits είναι το μέγεθος λέξης (word) του MIPS. Πιό σύγχρονα μοντέλα επεξεργαστών, σήμερα, έχουν μέγεθος λέξης 64 bits. Οι καταχωρητές της CPU είναι πολύ γρηγορότεροι από την κεντρική μνήμη των υπολογιστών, διότι, συνήθως, όσο μικρότερο ένα ηλεκτρονικό κύκλωμα τόσο γρηγορότερο (2η αρχή σχεδίασης --σελ. 110 βιβλίου). Οι εντολές αριθμητικών πράξεων του MIPS έχουν πάντα τρείς (3) τελεστέους, γιά λόγους ομοιμορφίας. Η ομοιμορφία μεταφράζεται σε απλότητα του hardware, πράγμα που ευνοεί την ψηλότερη ταχύτητα (1η αρχή σχεδίασης --σελ. 108 βιβλίου).
Γιά να γίνει η γλώσσα μηχανής λίγο πιό φιλική προς τον άνθρωπο, χρησιμοποιούμε ένα συμβολικό όνομα γιά κάθε επιτρεπτό opcode, ένα δεκαδικό ή δεκαεξαδικό αριθμό με μερικά απλά σύμβολα γιά κάθε τελεστέο, και γράφουμε αυτά τα στοιχεία της κάθε εντολής σε μία χωριστή γραμμή. Αυτή είναι η γλώσσα "Assembly", η οποία μπορεί να μεταφραστεί σε γλώσσα μηχανής από ένα σχετικά απλό πρόγραμμα υπολογιστή --τον λεγόμενο "Assembler". Οι γλώσσες ψηλού επιπέδου (HLL) (όπως η C) μεταφράζονται σε Assembly από ένα σημαντικά πολυπλοκότερο πρόγραμμα, τον Compiler.
Η βασική εντολή αριθμητικής πράξης του MIPS είναι η add που κάνει πρόσθεση ακεραίων. Η εντολή Assembly "add $23, $16, $18" διαβάζει τα περιεχόμενα των καταχωρητών υπ'αριθμόν 16 και 18, τα προσθέτει, και γράφει το αποτέλεσμα στον καταχωρητή υπ'αριθμόν 23. Η εντολή addi είναι παρόμοια αλλά ο τρίτος τελεστέος της είναι σταθερός αριθμός αντί καταχωρητή: "addi $23, $16, 157" διαβάζει το περιεχόμενο του καταχωρητή 16, προσθέτει τον αριθμό 157 σε αυτό, και γράφει το αποτέλεσμα στον καταχωρητή 23. Εάν η μεταβλητή i βρίσκεται στον καταχωρητή 18, τότε η εκχώρηση "i=i+1" μεταφράζεται σε "addi $18, $18, 1". Ο καταχωρητής $0 του MIPS περιέχει πάντα την σταθερή ποσότητα μηδέν, ανεξαρτήτως του τι γράφει κανείς σε αυτόν (οι εγγραφές σε αυτόν αγνοούνται από το hardware). Ετσι, η αρχικοποίηση "i=1" μπορεί να γίνει: "addi $18, $0, 1". Εκτός από τα ονόματα $0, $1, ..., $31 γιά τους 32 καταχωρητές του MIPS, χρησιμοποιούνται και τα λίγο πιό αφηρημένα ονόματα $s0, $s1, ..., $t0, $t1, ... ανάλογα με την κατηγορία χρήσης που συνήθως επιφυλάσσει σε καθένα τους ο compiler, όπως θα δούμε αργότερα. Γιά συνθετότερες πράξεις και μία αφαίρεση, δείτε το παράδειγμα στη σελ. 110 του βιβλίου.
Γιά να εκτελεστεί ένα πρόγραμμα, οι εντολές του γράφονται στην κεντρική μνήμη του υπολογιστή, η μία "κάτω" από την άλλη (σε συνεχόμενες διευθύνσεις μνήμης). Ο επεξεργαστής (CPU) διαβάζει μιάν εντολή από την μνήμη, την εκτελεί, και στη συνέχεια διαβάζει και εκτελεί την επόμενη, κ.ο.κ. Η σειριακή αυτή εκτέλεση εντολών διακόπτεται όταν εκτελείται μιά εντολή διακλάδωσης (branch/jump). Η εντολή "beq $16, $17, label" είναι μιά εντολή διακλάδωση υπό συνθήκη (conditional branch): διαβάζει τους καταχωρητές 16 και 17, και τους συγκρίνει. Εάν τους βρεί ίσους (equal) διακλαδίζεται στη θέση label, δηλαδή κάνει τον επεξεργαστή να διαβάσει και εκτελέσει την εντολή από εκείνη τη διεύθυνση σαν επόμενη εντολή. Αλλοιώς, δεν κάνει τίποτα, οπότε επόμενη εντολή θα διαβαστεί και εκτελεστεί η "από κάτω" εντολή. Η εντολή bne κάνει τα ανάποδα, δηλαδή διακλαδίζεται εάν βρεί τους καταχωρητές άνισους (not equal), αλλοιώς συνεχίζει "από κάτω". Η εντολή j (jump) διακλαδίζεται πάντοτε (χωρίς συνθήκη).
# compute s = 1+2+3+...+(n-1), for n>=2 # register $16: n # register $17: s # register $18: i .data # init. data memory with the strings needed: str_n: .asciiz "n = " str_s: .asciiz " s = " str_nl: .asciiz "\n" .text # program memory: .globl main # label "main" must be global; # default trap.handler calls main. .globl loop # global symbols can be specified # symbolically as breakpoints. main: # (1) PRINT A PROMPT: addi $2, $0, 4 # system call code for print_string la $4, str_n # pseudo-instruction: address of string syscall # print the string from str_n # (2) READ n (MUST be n>=2 --not checked!): addi $2, $0, 5 # system call code for read_int syscall # read a line containing an integer add $16, $2, $0 # copy returned int from $2 to n # (3) INITIALIZE s and i: add $17, $0, $0 # s=0; addi $18, $0, 1 # i=1; loop: # (4) LOOP starts here add $17, $17, $18 # s=s+i; addi $18, $18, 1 # i=i+1; bne $18, $16, loop # repeat while (i!=n) # LOOP ENDS HERE # (5) PRINT THE ANSWER: addi $2, $0, 4 # system call code for print_string la $4, str_s # pseudo-instruction: address of string syscall # print the string from str_s addi $2, $0, 1 # system call code for print_int add $4, $17, $0 # copy argument s to $4 syscall # print the integer in $4 (s) addi $2, $0, 4 # system call code for print_string la $4, str_nl # pseudo-instruction: address of string syscall # print a new-line # (6) START ALL OVER AGAIN (infinite loop) j main # unconditionally jump back to mainΟ κώδικας αυτός υπολογίζει το άθροισμα s=1+2+3+...+(n-1), γιά n μεγαλύτερο ή ίσο του 2, όπως είδαμε στο μάθημα. ΠΡΟΣΟΧΗ: αν δοθεί n μικρότερο του 2, ο κώδικας θα μπεί σε (σχεδόν) άπειρο βρόγχο! Η "καρδιά" του κωδικά είναι τα κομάτια (3) --αρχικοποιήσεις-- και (4) --βρόγχος υπολογισμού.
Το κομάτι κάθε γραμμής μετά το # είναι σχόλια. Οι 3 γραμμές μετά το ".data" είναι αρχικοποιήσεις μνήμης δεδομένων. Οι γραμμές μετά το ".text" είναι εκτελέσιμος κώδικας. Οι γραμμές ".globl" λένε στον SPIM να προσθέσει τα labels "main" και "loop" στον πίνακα καθολικών (global) συμβόλων. Το "main" είναι εκεί που ο trap.handler θα καλέσει τον κώδικά μας, και το "loop" είναι η αρχή του βρόγχου μας, και κάνοντάς το global μπορούμε να το δώσουμε και συμβολικά (όχι μόνο αριθμητικά) σαν διεύθυνση breakpoint. Μέσα στον κώδικα, το κομάτι (1) είναι ένα κάλεσμα του λειτουργικού συστήματος (system call) προκειμένου να τυπωθεί το string str_n στην κονσόλα. Το κομάτι (2) είναι ένα ανάλογο κάλεσμα που περιμένει να διαβάσει έναν ακέραιο από την κονσόλα (το πρόγραμμα θα περιμένει εκεί μέχρι να πληκτρολογήστε έναν ακέραιο και ένα RETURN). Ο ακέραιος που επιστρέφει το κάλεσμα (στον καταχωρητή $2) αρχικοποιεί τη μεταβλητή μας n). Τα κομάτια (3) και (4) είναι ο κυρίως υπολογισμός. Το κομάτι (5) είναι τρία καλέσματα συστήματος γιά να τυπωθούν το string str_s, η απάντηση s, και το string str_nl. Τέλος, η εντολή jump στο (6) μας επιστρέφει πάντα πίσω στο main, ώστε το πρόγραμμα να ξανατρέχει συνεχώς μέχρι να τερματίσετε τον SPIM.
Up to the Home Page of CS-225 |
© copyright University of Crete, Greece.
Last updated: 06 and 11 Feb. 2001, by M. Katevenis. |