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

Γνωριμία με την Assembly και τον SPIM

Προθεσμία έως Δευτέρα 12 Φεβρουαρίου (βδομάδα 2)

Περιληπτικά Εισαγωγικά γιά την Assembly του MIPS

Το hardware των υπολογιστών καταλαβαίνει και εκτελεί εντολές από ένα ρεπερτόριο πολύ μικρότερο και απλούστερο από τις γλώσσες υψηλού επιπέδου (HLL --High Level Languages). Οι εντολές που δέχεται και εκτελεί το hardware είναι αναγκαστικά κωδικοποιημένες σαν δυαδικά σύμβολα, και λέγονται "Γλώσσα Μηχανής" (Machine Language). Κάθε οικογένεια επεξεργαστών που είναι μεταξύ τους "binary compatible" έχει την ίδια γλώσσα μηχανής, που είναι διαφορετική από τη γλώσσα μηχανής άλλων οικογενειών. Ενα δημοφιλές σήμερα στυλ γλωσσών μηχανής ("αρχιτεκτονικών") είναι αυτές που έχουν σχετικά λίγες και απλές μόνο εντολές, και που γι' αυτό περιγράφονται σαν "Υπολογιστές Ελαττωμένου Ρεπερτόριου Εντολών" (RISC --Reduced Instruction Set Computers). Ενα υποσύνολο μιάς τέτοιας γλώσσας μηχανής θα χρησιμοποιήσουμε σαν παράδειγμα σε αυτό το μάθημα --της γλώσσας μηχανής του επεξεργαστή MIPS. Αλλες γλώσσες μηχανής στυλ RISC είναι αυτές των SPARC, Alpha, και PowerPC. Η σειρά x86 και Pentium της Intel έχει πιό πολύπλοκη γλώσσα μηχανής.

Κάθε εντολή γλώσσας μηχανής αποτελείται από έναν κώδικα πράξης (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) διακλαδίζεται πάντοτε (χωρίς συνθήκη).

Ο Προσομοιωτής SPIM

Προγράμματα γραμμένα σε γλώσσα Assembly του MIPS μπορεί να τα δοκιμάσει κανείς και να παρακολουθήσει πώς τρέχουν χρησιμοποιόντας τον "προσομοιωτή" SPIM, γραμμένο από τον James Larus στο Πανεπιστήμιο Wisconsin-Madison, http://www.cs.wisc.edu/~larus/spim.html. Γιά να τρέξετε τον προσομοιωτή SPIM:

Κώδικας Γνωριμίας

Αντικείμενο της παρούσας άσκησης είναι να γνωριστείτε με τη χρήση του SPIM (και με τη γλώσσα Assembly του MIPS). Γιά το σκοπό αυτό, μελετήστε και αντιγράψτε τον παρακάτω κώδικα --ή διάφορες παραλλαγές του που προτιμάτε-- σε ένα αρχείο, και τρέξτε τον στον SPIM.

		# 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.

Ασκήσεις Τρεξίματος στον SPIM

Τρόπος Παράδοσης

Θα παραδώσετε ηλεκτρονικά ένα στιγμιότυπο της οθόνης καθώς τρέχετε το πρόγραμμα "xspim" και αυτό βρίσκεται σ' ένα "ενδιαφέρον" ενδιάμεσο breakpoint. Το στιγμιότυπο θα το πάρετε και θα το παραδώσετε ως εξής:


Up to the Home Page of CS-225
 
© copyright University of Crete, Greece.
Last updated: 06 and 11 Feb. 2001, by M. Katevenis.