ΗΥ-225: Οργάνωση Υπολογιστών
Άνοιξη 2024 |
Τμ. Επ. Υπολογιστών © Πανεπιστήμιο Κρήτης |
[Up: Table of Contents] [Prev - 1. Assembly Introduction] |
[printer version - PDF] [3. Memory Accesses - Next] |
Βιβλίο:
Προσημασμένοι αριθμοί: Διαβάστε την §2.4
(σελίδες 127-134 στο βιβλίο MIPS εκδ.2010, pp. 74-81 στο βιβλίο RISC-V),
και ξαναθυμηθείτε το
Εργαστήριο 6
της Ψηφιακής Σχεδίασης.
2.1 Ολίγα περί Εντολών Διακλάδωσης υπό Συνθήκη
Όπως λέγαμε και στην §1.3,
για να εκτελεστεί ένα πρόγραμμα,
οι εντολές του γράφονται στην κεντρική μνήμη
η μία "κάτω" από την άλλη,
δηλαδή σε συνεχόμενες θέσεις (διευθύνσεις) μνήμης.
Μετά την ανάγνωση και εκτέλεση μιας εντολής,
ο επεξεργαστής αυξάνει τον PC
κατά το μέγεθος της εντολής που εκτελέστηκε
(κατά 4 στον βασικό RISC-V, αφού οι εντολές του έχουν μέγεθος 4 Bytes),
οπότε αυτός (ο PC) δείχνει στην επόμενη (την "από κάτω") εντολή.
Η σειριακή αυτή εκτέλεση εντολών διακόπτεται
όταν εκτελείται μιά εντολή
μεταφοράς ελέγχου (CTI - control transfer instruction).
Είδαμε ήδη μία τέτοια, την (ψεύδο)εντολή άλματος
j label ("jump" to label),
που κάνει ώστε η επόμενη εντολή που θα εκτελεστεί
να είναι η εντολή στη διεύθυνση μνήμης label,
αντί να είναι η "από κάτω" εντολή.
Με άλλα λόγια, η (ψεύδο)εντολή j label
φορτώνει τη διεύθυνση label στον καταχωρητή PC.
Χρησιμοποιώντας αυτήν στην άσκηση 1 φτιάξαμε έναν "άπειρο βρόχο",
δηλαδή κάναμε τον υπό προσομοίωση υπολογιστή
να εκτελεί συνεχώς το ίδιο "μπλόκ" εντολών.
Γιά να φτιάξουμε έναν κανονικό (όχι άπειρο) βρόχο
χρειαζόμαστε μια εντολή
διακλάδωσης υπό συνθήκη (conditional branch),
δηλαδή μια εντολή που μερικές φορές προκαλεί διακλάδωση
και μερικές φορές όχι,
ανάλογα με το αν ισχύει ή δεν ισχύει κάποια κατάλληλη συνθήκη.
Η βασική τέτοια εντολή είναι η
beq (branch if equal):
Η εντολή "beq x26, x27, label"
διαβάζει τους καταχωρητές 26 και 27, και τους συγκρίνει.
Εάν τους βρει ίσους (equal) διακλαδίζεται στη θέση label,
δηλαδή κάνει τον επεξεργαστή να διαβάσει και εκτελέσει την εντολή
από εκείνη τη διεύθυνση σαν επόμενη εντολή.
Αλλιώς, δεν κάνει τίποτα το ξεχωριστό,
οπότε σαν επόμενη εντολή θα διαβαστεί και εκτελεστεί η "από κάτω" εντολή.
Η εντολή bne (branch if not equal) κάνει τα ανάποδα,
δηλαδή διακλαδίζεται εάν βρει τους καταχωρητές άνισους (not equal),
αλλιώς συνεχίζει "από κάτω".
Τρόπος Παράδοσης:
Γιά να παραδώσετε τις ασκήσεις σας γενικά και αυτήν εδώ ειδικά,
συνδεθείτε σε ένα μηχάνημα Linux του Τμήματος.
Ετοιμάστε ένα directory με το(α) αρχείο(α) που σας ζητάει η άσκηση.
Ας υποθέσουμε ότι το όνομα του directory είναι
[somepath]/mydir.
Μετακινηθείτε στο directory [somepath], και εκτελέστε την εντολή:
2.2 Κώδικας Βρόχου και Εισόδου/Εξόδου Κονσόλας
Για να επικοινωνούν τα προγράμματα με τον έξω κόσμο,
ο RARS προσομοιώνει μερικές υποτυπώδεις υπηρεσίες λειτουργικού συστήματος
για είσοδο/έξοδο (I/O) στην "κονσόλα" (ένα απλό τερματικό ASCII).
Οι κλήσεις στο λειτουργικό σύστημα λέγονταν παραδοσιακά "system call",
αλλά ο RISC-V υιοθετεί το γενικότερο όνομα "environment call (ecall)"
διότι μπορούν να χρησιμοποιηθούν και γιά την κλήση
άλλων περιβαλλόντων ελέγχου (π.χ. hypervisor κλπ).
Δεν είναι ανάγκη προς στιγμήν να καταλάβετε όλες τις λεπτομέρειες
του πώς γίνονται αυτές οι κλήσεις
–αρκεί να μιμηθείτε το παρακάτω παράδειγμα
και να καταλάβετε τις εξηγήσεις που δίνονται κάτω από αυτό.
Μελετήστε και αντιγράψτε
σε ένα αρχείο (π.χ. "ex02.asm")
τον παρακάτω κώδικα
–ή διάφορες παραλλαγές του που προτιμάτε–
και τρέξτε τον στον RARS:
# compute s = 1+2+3+...+(n-1), for n>=2
# register x26: n
# register x27: s
# register x28: i
.data # init. data memory with the strings needed:
str_n: .asciz "n = "
str_s: .asciz " s = "
str_nl: .asciz "\n"
.text # program memory:
main: # (1) PRINT A PROMPT:
addi x17, x0, 4 # environment call code for print_string
la x10, str_n # pseudo-instruction: address of string
ecall # print the string from str_n
# (2) READ n (MUST be n>=2 --not checked!):
addi x17, x0, 5 # environment call code for read_int
ecall # read a line containing an integer
add x26, x10, x0 # copy returned int from x10 to n
# (3) INITIALIZE s and i:
add x27, x0, x0 # s=0;
addi x28, x0, 1 # i=1;
loop: # (4) LOOP starts here
add x27, x27, x28 # s=s+i;
addi x28, x28, 1 # i=i+1;
bne x28, x26, loop # repeat while (i!=n)
# LOOP ENDS HERE
# (5) PRINT THE RESULT:
addi x17, x0, 4 # environment call code for print_string
la x10, str_s # pseudo-instruction: address of string
ecall # print the string from str_s
addi x17, x0, 1 # environment call code for print_int
add x10, x27, x0 # copy argument s to x10
ecall # print the integer in x10 (s)
addi x17, x0, 4 # environment call code for print_string
la x10, str_nl # pseudo-instruction: address of string
ecall # 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) --βρόγχος υπολογισμού.
Προσέξτε τις παρακάτω εξηγήσεις:
Άσκηση 2.3: Τρέξιμο στον RARS
Εάν δεν σας δουλεύει ο RARS
και πρέπει να χρησιμοποιήστε τον Venus:
Εάν δεν καταφέρατε να εγκαταστήσετε την Java και τον RARS,
και θέλετε να κάνετε αυτή την άσκηση με τον
www.kvakil.me/venus/
που λέγαμε στην §1.5,
θα πρέπει να κάνετε τρείς αλλαγές στο πρόγραμμα σας:
Πρώτον, η οδηγία ".asciz" του RARS
γράφεται με δύο "i" στον Venus: ".asciiz".
Δεύτερον, ο κωδικός του ecall περνιέται μέσω του καταχωρητή x10 στον Venus
(αντί μέσω του x17 στον RARS),
και το όρισμα (argument) του ecall περνιέται μέσω του x11 στον Venus
(αντί μέσω του x10 στον RARS)
–δείτε τις οδηγίες στο
github.com/kvakil/venus/wiki (item: Environmental Calls).
Και τρίτον, ο Venus δεν έχει ecall γιά ανάγνωση εισόδου (user input),
άρα στη θέση του read_int ecall θα πρέπει να γράψετε μιάν εντολή
π.χ. addi x26, x0, 77
που να αρχικοποιεί π.χ. n=77,
και κάθε φορά που θέλετε να τρέξετε το πρόγραμμά σας με άλλη τιμή του n
να αλλάζετε με τον Editor το "77" μέσα στο πρόγραμμα
στη νέα τιμή που θέλετε.
Θα παραδώσετε ηλεκτρονικά ένα στιγμιότυπο της οθόνης
καθώς τρέχετε το πρόγραμμα "RARS"
και αυτό βρίσκεται σ' ένα "ενδιαφέρον" ενδιάμεσο breakpoint
της επιλογής σας (όχι στην αρχή και όχι στο τέλος του προγράμματος).
Το στιγμιότυπο μπορείτε να το πάρετε π.χ. όπως παρακάτω,
θα το ονομάσετε ex02.jpg, και
θα το παραδώσετε ως εξής:
turnin ex02@hy225 mydir
Η διαδικασία turnin θα σας ζητήσει να επιβεβαιώσετε
την αποστολή των αρχείων.
Περισσότερες πληροφορίες και αναλυτικές οδηγίες
για τη διαδικασία turnin είναι διαθέσιμες στην ιστοσελίδα
https://www.csd.uoc.gr/index.jsp?custom=use_the_turnin
ή εκτελώντας man turnin
σε κάποιο από τα μηχανήματα Linux του Τμήματος.
Γιά επαλήθευση της υποβολής μπορείτε να εκτελέσετε:
verify-turnin ex02@hy225
2.4
Προσημασμένοι Αριθμοί, Επέκταση Προσήμου
Οι σημερινοί επεξεργαστές παριστάνουν τους προσημασμένους (signed) αριθμούς
σε κωδικοποίηση συμπληρώματος ως-προς-2,
όπως είχαμε δεί στο μάθημα της Ψηφιακής Σχεδίασης (ΗΥ-120, ενότητα
6.3).
Σε αυτή την αναπαράσταση, η μετατροπή προσημασμένου (signed) αριθμού
από λιγότερα σε περισσότερα bits γίνεται με την εξής τεχνική,
που ονομάζεται "επέκταση προσήμου" (sign extension)
και αποδεικνύεται μαθηματικά ως εξής:
Έστω ο προσημασμένος ακέραιος As,k με k bits, τον οποίο θέλουμε να μετατρέψουμε στον ίδιο αριθμό As,n με n bits: As,n = As,k όπου n>k. Εάν τα bits του As,k τα ερμηνεύσουμε σαν μη προσημασμένο (unsigned) ακέραιο, τότε θα μοιάζουν με (θα δηλώνουν) έναν αριθμό που ας το ονομάσουμε Au,k και ομοίως εάν τα bits του As,n τα ερμηνεύσουμε σαν unsigned τότε θα μοιάζουν με τον Au,n . Εάν ο As,k = As,n είναι μη αρνητικός (δηλ. θετικός ή μηδέν), τότε, κατά τον ορισμό της κωδικοποίησης συμπληρώματος ως-προς-2, (α) το αριστερό bit τους θα είναι μηδέν, και (β) οι απρόσημες ερμηνίες τους θα είναι: Au,k = As,k και Au,n = As,n , και αφού As,n = As,k τότε θα είναι και: Au,n = Au,k. Αυτοί οι δύο τελευταίοι, αφού είνα unsigned ακέραιοι, με n και k bits αντίστοιχα, και είναι ίσοι μεταξύ τους, προκύπτει ότι ο Au,n που έχει περισσότερα bits θα είναι ίδιος με τον Au,k αλλά με n-k μηδενικά προστεθημένα αριστερά από τον Au,k .
Αλλιώς, εάν οι As,n = As,k είναι αρνητικοί, τότε, κατά τον ορισμό μας, (α) το αριστερό bit τους θα είναι ένα, και (β) οι απρόσημες ερμηνίες τους θα είναι: Au,k = As,k + 2k και Au,n = As,n + 2n . Δεδομένου ότι: As,n = As,k προκύπτει ότι θα είναι και: Au,n - 2n = Au,k - 2k . Επομένως, η αναπαράσταση που ψάχνουμε είναι η: Au,n = Au,k - 2k + 2n = Au,k + (2n - 2k) = (2n-k - 1) · 2k + Au,k . Σε αυτήν την τελευταία έκφραση, ο μεν αριστερός προσθετέος, (2n-k - 1) · 2k, αποτελείται από (n-k) το πλήθος άσσους (που είναι ο αριθμός 2n-k - 1) ολισθημένους αριστερά κατά k θέσεις bits (που είναι ο πολλαπλασιασμός επί 2k), ο δε δεξιός προσθετέος, Au,k , είναι τα αρχικά k bits του αρχικού αριθμού που μας δόθηκε. Δεδομένου ότι ο πρώτος προσθετέος έχει όλο μηδενικά στις k δεξιές θέσεις, ο δε δεύτερος προσθετέος έχει όλο μηδενικά στις (n-k) αριστερές θέσεις, το άθροισμά τους θα είναι απλώς η "συγκόλληση" (concatenation) των δύο αυτών ποσοτήτων. Επομένως, η αναπαράσταση με n bits του αρχικού αριθμού που μας δόθηκε θα αποτελείται από τα αρχικά k bits, δεξιά, μαζί με (n-k) άσσους κολλημένους ακριβώς αριστερά τους.
Συνολικά λοιπόν, γιά να μετατρέψουμε ένα προσημασμένο (signed) ακέραιο από k (λιγότερα) bits σε n (περισσότερα) bits, δεν έχουμε παρά να κάνουμε το εξής: Τοποθετούμε αριστερά από τα bits που μας δόθηκαν (n-k) επιπλέον bits τα οποία είναι όλα τους αντίγραφα του αριστερού (most significant) bit του αριθμού που μας δόθηκε, δηλαδή αντίγραφα του bit που υποδεικνύει το πρόσημο του δοθέντος αριθμού (0 γιά θετικούς ή το μηδεν, 1 γιά αρνητικούς). Η πράξη αυτή ονομάζεται επομένως, προφανώς, επέκταση προσήμου (sign extension).