ΗΥ-225: Οργάνωση Υπολογιστών
Άνοιξη 2020 |
Τμ. Επ. Υπολογιστών © Πανεπιστήμιο Κρήτης |
[Up - Table of Contents] [Prev - 4. Instruction Formats] |
[printer version - PDF] [6. Procedure Call - Next] |
Βιβλίο: Διαβάστε την §2.7: σελ. 145-151 στο Ελληνικό, ή σελ. 92-97 στο Αγγλικό. Μπορείτε επίσης να δείτε, κάπως εγκυκλοπαιδικά, τις σελ. 115-120 του Αγγλικού βιβλίου· στο Ελληνικό βιβλίο, οι αντίστοιχες σελίδες ήταν οι 171-175, αλλά αυτές δεν σας βοηθάνε ιδιαίτερα, διότι αλλάζουν σημαντικά από τον MIPS στον RISC-V.
Το πιό δημοφιλές addressing mode, δηλαδή τρόπος διευθυνσιοδότησης ή τρόπος προσδιορισμού της διεύθυνσης προορισμού των διακλαδώσεων, είναι το "PC-Relative addressing mode, δηλαδή ο προορισμός της διακλάδωσης δηλώνεται μέσα στην εντολή σε σχέση με την εντολή διακλάδωσης αυτή καθεαυτή, δηλαδή σαν σχετική απόσταση (μετακίνηση) από εδώ που βρισκόμαστε τώρα προς τα εκεί όπου θέλουμε να πάμε. Ο RISC-V χρησιμοποιεί και αυτός PC-Relative mode γιά τις εντολές CTI. Το PC-relative branch addressing mode mode προσφέρει δύο σημαντικά πλεονεκτήματα. Πρώτον, επειδή η απόσταση αυτή είναι συνήθως μικρή, αυτή μπορεί να χωρέσει σε λιγότερα bits μέσα στην εντολή· και είναι συνήθως μικρή επειδή οι διακλαδώσεις είναι εντός των if-then-else και εντός των βρόχων, που και τα δύο είναι εντός της ίδιας διαδικασίας (procedure), και όπου όλα αυτά είναι συνήθως σχετικά μικρά. Δεύτερον, όταν ο Linker συνενώνει αρχεία που έχουν γίνει compiled χωριστά γιά να φτιάξει ένα ενιαίο εκτελέσιμο (ή όταν κάνουμε dynamic linking at runtime), τότε η καθε διαδικασία καταλήγει να ξεκινά από μιά "καινούργια" διεύθυνση που δεν την ξέραμε νωρίτερα, άρα ο Linker έχει σχετικές διορθώσεις διευθύνσεων μέσα στον κώδικα που πρέπει να κάνει. Οι διορθώσεις αυτές είναι λιγότερες όπου χρησιμοποιείται PC-relative addressing mode, διότι όταν μετακινείται ολόκληρη μιά διαδικασία σε νέες διευθύνσεις, οι σχετικές αποστάσεις των εντολών μέσα στη διαδικασία δεν αλλάζουν.
Στη γλώσσα Assembly, η διεύθυνση προορισμού της διακλάδωσης δηλώνεται απλά με μια ετικέτα (label), και αναλαμβάνει ο Assembler να υπολογίσει και να βάλει τη σωστή δυαδική τιμή. Στη γλώσσα μηχανής του RISC-V, οι εντολές διακλάδωσης ακολουθούν το B-format, που όπως είπαμε είναι παραλλαγή του I-format και αφιερώνει, όπως κι εκείνο, 12 bits γιά μιά σταθερή ποσότητα, Imm12, που χρησιμοποιείται γιά να προσδιοριστεί η διεύθυνση προορισμού, ως εξής:
όπου PC_br είναι η διεύθυνση της ίδιας της εντολής διακλάδωσης, Imm12 είναι η σταθερή ποσότητα των 12 bits μέσα στην εντολή, θεωρούμενη ως προσημασμένος αριθμός σε συμπλήρωμα ως προς 2 (δηλαδή sign-extended), και PC_new είναι η διεύθυνση της εντολής προορισμού σε περίπτωση επιτυχίας της διακλάδωσης. Ο πολλαπλασιασμός του Imm12 επί 2 γίνεται γιά να εκμεταλλευτούμε το γεγονός ότι η διεύθυνση όλων των εντολών του RISC-V είναι ακέραιο πολλαπλάσιο του 2 (συχνά, που δεν έχουμε και μικρές (16-μπιτες) εντολές, η διευθύνσεις όλων των εντολών είναι και ακέραια πολλαπλάσια του 4, αλλά αυτό δεν είναι πάντα εγγυημένο, κι έτσι ο RISC-V δεν στηρίζεται σε αυτό). Με τον τρόπο αυτό, διπλασιάζουμε το "βεληνεκές" των διακλαδώσεων –με άλλα λόγια, ο αριθμός Imm12 μετράει πλήθος "μικρών" (16-μπιτων) εντολών μπροστά (θετικός) ή πίσω (αρνητικός), αντί να μετρά πλήθος Bytes μπροστά ή πίσω.
Η συνθήκη διακλάδωσης αφορά πάντοτε τη σύγκριση των δύο καταχωρητών πηγής, rs1 και rs2, που περιέχει το B-format. Οι εντολές διακλάδωσης που υπάρχουν στον βασικό RISC-V, το συντακτικό τους σε Assembly, και οι συγκρίσεις που κάνουν είναι οι εξής:
Υπάρχει ένα περίφημο κόλπο γιά να ελέγξει κανείς την διπλή ανίσωση 0 ≤ i < N, που είναι ο έλεγχος ορίων γιά το index ενός πίνακα μεγέθους N, χρησιμοποιώντας μία μόνο εντολή διακλάδωσης. Έστω ότι το array index i είναι προσημασμένος (signed) ακέραιος και βρίσκεται στον καταχωρητή x20, και το μέγεθος N ειναι θετικός ακέραιος και βρίσκεται στον x11. Τότε η εντολή: bgeu x20, x11, indexOutOfBounds πραγματοποιεί και τις δύο συγκρίσεις ταυτοχρόνως, διότι ερμηνεύει και συγκρίνει τον (προσημασμένο) ακέραιο i (x20) σαν να ήταν unsigned: Όταν το κάνουμε αυτό, σε αναπαράσταση συμπληρώματος ως προς 2, τότε, ως γνωστό, όλοι οι αρνητικοί αριθμοί μοιάζουν σαν πολύ μεγάλοι θετικοί αριθμοί – μεγαλύτεροι από τον μεγαλύτερο προσημασμένο θετικό· έτσι, εάν το index i είναι <0, τότε στη απρόσημη (unsigned) σύγκριση το i θα μοιάζει σαν πολύ μεγάλος θετικός αριθμός, σαφώς μεγαλύτερος από το N, άρα θα εμφανίζεται να παραβιάζει τον περιορισμό μας ξανά από την "επάνω" πλευρά, όπως και όταν i≥N.
(β) Επίσης, οι εντολές διακλάδωσης του RISC-V συγκρίνουν πάντα και μόνον δύο καταχωρητές μεταξύ τους –δεν υπάρχουν εντολές διακλάδωσης όπως π.χ. "beqi rs1, Immediate, Label" που να συγκρίνουν το περιεχόμενο ενός καταχωρητή με μία σταθερή ποσότητα. Γιατί άραγε;
(γ)
Δεδομένων λοιπόν όλων των παραπάνω που δεν υπάρχουν,
πώς θα μεταφράζατε σε Assembly του RISC-V τον ψευδοκώδικα
"branch if i≤13"
(ενδεχομένως με τη χρήση κάποιου ενδιάμεσου προσωρινού καταχωρητή);
Δώστε τις απαντήσεις σας, με τις εξηγήσεις τους, γραπτώς.
Η κάτω πλευρά του σχήματος παριστά τη μνήμη εντολών, με τις διευθύνσεις να αυξάνουν από πάνω προς τα κάτω. Αμέσως μετά την εκτέλεση των εντολών PREV, ο επεξεργαστής πάντα αρχίζει να υπολογίζει τη συνθήκη COND, της οποίας οι εντολές είναι τοποθετημένες "κολλητά" μετά τις εντολές PREV. Αμέσως μετά από αυτό τον υπολογισμό της συνθήκης, η εκτέλεση πρέπει να συνεχίσει είτε στο μπλοκ THEN είτε στο μπλοκ ELSE· αυτό επιτυγχάνεται με μιάν εντολή διακλάδωσης, η οποία πρέπει να οδηγήσει τον επεξεργαστή στο μπλοκ ELSE (που είναι σε απόσταση) εάν η συνθήκη είναι ψευδής, αλλιώς θα συνεχίσει στο μπλοκ THEN (που είναι τοποθετημένο "κολλητά από κάτω"). Εάν εκτελεστεί το μπλοκ THEN, τότε μετά το τέλος του πρέπει να παρακάμψει ο επεξεργαστής το μπλοκ ELSE, άρα χρειαζόμαστε μιάν εντολή άλματος (χωρίς συνθήκη) που να μας οδηγήσει κατευθείαν στο μπλοκ CONT, δηλαδή αμέσως μετά την έξοδο από το if-then-else. Αντίθετα, εάν εκτελεστεί το μπλοκ ELSE, τότε μετά το τέλος του βγαίνουμε από το if-then-else και συνεχίζουμε κανονικά στο μπλοκ CONT, που είναι τοποθετημένο "κολλητά" από κάτω, χωρίς να απαιτείται προς τούτο καμιά διακλάδωση ή άλμα. Στο βιβλίο υπάρχει το εξής παράδειγμα (με μία προσθήκη, εδώ, κώδικα "CONT" (μηδενισμός της μεταβλητής h) γιά περισσότερη σαφήνεια):
όπου οι μεταβλητές f, g, h, i, j θεωρείται ότι βρίσκονται στους καταχωρητές x19, x20, x21, x22, και x23, αντίστοιχα. Τότε αυτό μεταφράζεται σε Assembly του RISC-V ως εξής (τα ονόματα των labels είναι εμπνευσμένα από την αρίθμηση της παραγράφου, §5.3) (η (ψεύδο-)εντολή "j" (jump) υλοποιεί το άλμα που χρειαζόμαστε, και θα μιλήσουμε γιά αυτήν παρακάτω, στην §5.7):
...{PREV}... # instructions before entering into "if" bne x22, x23, else53 # if ( i!=j ) goto ELSE add x19, x20, x21 # f = g+h; {THEN} j cont53 # skip over "ELSE" else53: sub x19, x20, x21 # f = g-h; {ELSE} cont53: add x21, x0, x0 # h=0; {CONT}
Στο βιβλίο υπάρχει το παρακάτω παράδειγμα, ελαφρώς παραλλαγμένο εδώ. Ψάχνουμε σε έναν πίνακα ακεραίων double: long long int table[SIZE] μέχρι να βρούμε εκεί μίαν επιθυμητή τιμή value, η οποία υποτίθεται ότι ξέρουμε ότι υπάρχει στον πίνακα. Η μεταβλητή i βρίσκεται, όπως και πριν, στον καταχωρητή x22, η τιμή value που αναζητούμε βρίσκεται στον x24, και η διεύθυνση βάσης του πίνακα (που ως συνήθως δεν χωρά στη 12-μπιτη σταθερά Imm12 της εντολής ld) βρίσκεται στον καταχωρητή x25. Αφού τα στοιχεία του πίνακα είναι double words, μεγέθους 8 Bytes καθένα, το στοιχείο table[i] θα βρίσκεται στη διεύθυνση: x25 + 8·i.
Θα χρησιμοποιήσουμε τον καταχωρητή x10 γιά τα ενδιάμεσα, προσωρινά αποτελέσματά μας. Η εντολή slli (shift left logical immediate) προκαλεί αριστερή ολίσθηση των bits του πρώτου τελεστέου πηγής (εδώ του x22) κατά το πλήθος θέσεων (bits) που ορίζει ο δεύτερος (σταθερός - immediate) τελεστέος πηγής (εδώ: 3 θέσεις), γεμίζοντας τις κενούμενες θέσεις με μηδενικά ("logical"), και γράφει το αποτέλεσμα στον καταχωρητή προορισμού (εδώ: x10)· όπως ξέρουμε (§6.1 Ψηφιακής Σχεδίασης), μιά τέτοια αριστερή ολίσθηση ισοδυναμεί με πολλαπλασιασμό επί 2 είς την δύναμη του πλήθους των θέσεων ολίσθησης –άρα εδώ πολλαπλασιασμός επί 23 = 8. Έτσι, ο παραπάνω κώδικας μεταφράζεται σε:
add x22, x0, x0 # i=0; loop54: slli x10, x22, 3 # tmp = 8 * i (start COND evaluation) add x10, x25, x10 # tmp = διεύθυνση του table[i] ld x10, 0(x10) # tmp = table[i] beq x10, x24, cont54 # if table[i] == value goto cont54 (loop exit) addi x22, x22, 1 # i = i+1 (loop BODY) j loop54 # repeat the loop cont54: .... # contine with the rest of the program
Ξαναγράψτε το βρόχο σε Assembly, ούτως ώστε
μόνο ένα branch ή jump να εκτελείται σε κάθε ανακύκλωση.
Κατά την είσοδο ή την έξοδο από το βρόχο
επιτρέπεται να εκτελούνται δύο εντολές μεταφοράς ελέγχου
–αυτό που μας ενδιαφέρει είναι να γλυτώνουμε τη μια από αυτές
κατά τις υπόλοιπες επαναλήψεις του βρόχου,
που αποτελούν και την πλειοψηφία των φορών που αυτός εκτελείται.
• Έστω ότι το σώμα (BODY) αυτού του βρόχου
επαναλαμβάνεται (εκτελείται) 10 φορές.
Τότε, πόσες εντολές συνολικά εκτελούνταν με τον παλαιό κώδικα,
και πόσες συνολικά με τον νέο;
Ας θεωρήσουμε τώρα μιά λίγο πιό σύνθετη συνθήκη διακλάδωσης, με λογικό ΚΑΙ μέσα της. Θεωρήστε ένα παράδειγμα ανάλογο με το προηγούμενο, όπου ψάχνουμε να βρούμε κάποιο "value", αλλά τώρα το ψάχνουμε σε linked list αντί σε array· επιπλέον, τώρα ενδέχεται και να μην υπάρχει το στοιχείο αυτό στη λίστα, άρα πρέπει να προβλέψουμε και την περίπτωση που η λίστα τελειώνει πριν το βρούμε. Στον παρακάτω κώδικα, έστω ότι η τιμή value που αναζητούμε βρίσκεται και πάλι στον καταχωρητή x24, και ότι ο pointer p βρίσκεται στον καταχωρητή x25 (στη θέση της διεύθυνσης βάσης του προηγούμενου πίνακα). Ο p δείχνει σε structures με δύο στοιχεία καθένα, μεγέθους 8 Bytes καθένα επειδή βρισκόμαστε σε 64-μπιτο RISC-V: το πρώτο στοιχείο είναι μία τιμή "key" σε offset 0 σε σχέση με την αρχή του structure, και το δεύτερο στοιχείο είναι ένας pointer "next" στο επόμενο structure της λίστας, σε offset 8 σε σχέση με την αρχή του structure.
Εδώ, ως γνωστόν, τα semantics της C μας βοηθάνε να μην προσπαθήσει ο επεξεργαστής να διαβάσει το p->key όταν ο pointer p τύχει να είναι NULL: η C προδιαγράφει "short circuit evaluation" του λογικού ΚΑΙ, δηλαδή εάν είναι ψευδές το πρώτο σκέλος, p!=NULL, τότε παρακάμπτεται και δεν υπολογίζεται το δεύτερο σκέλος, p->key != value. Γιά να υλοποιηθεί αυτό, απαιτούνται δύο διακλαδώσεις υπό συνθήκη σε αυτό το βρόχο:
loop56: beq x25, x0, cont56 # if p==NULL then exit the loop ld x10, 0(x25) # tmp = p->key beq x10, x24, cont56 # if p->key == value then exit the loop ld x25, 8(x25) # p = p->next (loop BODY) j loop56 # repeat the loop cont56: .... # contine with the rest of the program
Έτσι, ο RISC-V καταλήγει να μην χρειάζεται (και δεν έχει)
ειδική εντολή γιά το απλό άλμα (jump),
αλλά υλοποιεί τέτοια απλά άλματα
σαν ειδικές περιπτώσεις μιάς γενικότερης εντολής,
της jump-and-link (jal), η οποία,
όπως θα πούμε στο επόμενο κεφάλαιο (6η σειρά διαλέξεων),
χρησιμοποιείται γιά κάλεσμα διαδικασιών,
και έχει έναν καταχωρητή προορισμού.
Η απλή jump κάνει την ίδια δουλειά με εκείνην,
αλλά δεν χρειάζεται να γράψει τίποτα στον καταχωρητή προορισμού,
άρα απλώς χρησιμοποεί τον x0 σε εκείνη τη θέση
(αφού ο x0 δεν αλλάζει ποτέ τιμή, και μένει πάντα 0,
όσο και αν "γράφουμε" σε αυτόν).
Έτσι, τόσο η "jal rd, label",
όσο και η ψευδοεντολή "j label" (απλή jump)
που είναι ισοδύναμη με "jal x0, label"
χρησιμοποιούν και οι δύο το J-format
(§4.3),
άρα προσδιορίζουν τη διεύθυνση προορισμού τους
μέσω της 20-μπιτης σταθερής ποσότητας Imm20.
Ο τρόπος που χρησιμοποιείται αυτό το πεδίο γιά να αλλάξει τον PC
είναι πανομοιότυπος με εκείνον γιά τις διακλαδώσεις
–απλώς εδώ το Immediate έχει 20 bits αντί 12 εκεί:
• PC_new ← PC_j + (2*(signed)Imm20)
όπου PC_j είναι η διεύθυνση της ίδιας της εντολής άλματος,
Imm20 είναι η σταθερή ποσότητα των 20 bits μέσα στην εντολή,
θεωρούμενη ως προσημασμένος αριθμός σε συμπλήρωμα ως προς 2
(δηλαδή sign-extended),
και PC_new είναι η διεύθυνση της εντολής όπου πηδάμε.
Όπως και με τις διακλαδώσεις,
ο πολλαπλασιασμός επί 2 αξιοποιεί το γεγονός ότι ο PC έχει τιμή
πάντοτε ακέραιο πολλαπλάσιο του 2,
κι έτσι διπλασιάζει το βεληνεκές των αλμάτων σε
±219 = 512 K half-words (μικρές, 16-μπιτες εντολές) = ±1 MByte.
Indirect (indexed) Jump – jr rs1 (ψευδοεντολή):
Εκτός από άλματα σε σταθερές, πάντα τις ίδιες διευθύνσεις,
οι υπολογιστές χρειάζονται και άλματα
σε μεταβλητούς (runtime variable) προορισμούς,
γιά μία σειρά από σκοπούς, όπως αναλύουμε εδώ.
Στον RISC-V, τον ρόλο αυτό τον παίζει η ψευδοεντολή
jr (jump-register)·
όπως και με την απλή jump, η jump-register συντίθεται σαν ειδική περίπτωση
της γενικότερης jump-and-link-register που θα δούμε στο επόμενο κεφάλαιο.
Η jr rs1 κάνει την εξής απλή αλλά εξαιρετικά "ισχυρή" λειτουργία:
• PC_new ← rs1
με άλλα λόγια κάνει ώστε η επόμενη εντολή
να είναι η εντολή στην οιαδήποτε αυθαίρετη διεύθυνση μνήμης
είχαμε υπολογίσει προηγουμένως και είχαμε τοποθετήσει στον καταχωρητή rs1.
Η εντολή αυτή μας επιτρέπει
να μεταφέρουμε τον έλεγχο (την εκτέλεση του προγράμματος)
σε αυθαίρετη θέση μνήμης, η οποία μπορεί και να
ποικίλει κατά την εκτέλεση του προγράμματος (run-time variable)
και πιθανόν να εξαρτάται και από τα δεδομένα (data dependent).
Χρησιμοποιείται γιά:
•
Επιστροφή από διαδικασία, όπως θα πούμε στο επόμενο κεφάλαιο.
•
Μεταφορά του ελέγχου οσοδήποτε μακριά,
αφού μπορούμε με προηγούμενες εντολές
να φορτώσουμε την οποιαδήποτε διεύθυνση θέλουμε στον rs1.
•
γιά μετάφραση του switch statement,
όπως περιγράφουμε επιγραμματικά εδώ:
Γιά το switch statement, όπως περιγράφεται στο τέλος της §2.7 του βιβλίου, η βασική ιδέα είναι η εξής: ο κώδικας της κάθε περίπτωσης (case) γράφεται σε κάποια περιοχή μνήμης, και ο compiler θυμάται σε ποιά διεύθυνση αρχίζει αυτός ο κώδικας, γιά την καθεμία περίπτωση. Στη συνέχεια, ο compiler κατασκευάζει έναν πίνακα (array), τον "jump table", ο οποίος περιέχει σε κάθε θέση του αυτές τις διευθύνσεις όπου αρχίζει ο κώδικας της κάθε περίπτωσης, με τη σειρά, γιά όλες τις (αριθμητικές) τιμές της κάθε περίπτωσης. Όταν είναι να εκτελεστεί το switch statement, παίρνουμε την τιμή της μεταβλητής του switch (αυτήν της οποίας την κάθε περίπτωση τιμών εξετάζουμε), και χρησιμοποιούμε αυτή την τιμή σαν index στον jump table, γιά να επιλέξουμε ένα από τα περιεχόμενα του jump table –αυτό που αντιστοιχεί στην περίπτωση της τωρινής τιμής της μεταβλητής του switch. Το στοιχείο του πίνακα που διαλέξαμε είναι η διεύθυνση του κώδικα που πρέπει να εκτελεστεί γιά την περίπτωση της μεταβλητής που τώρα μας ήλθε. Αυτό ακριβώς το στοιχείο του jump table το φέρνουμε σε έναν καταχωρητή R, και στη συνέχεια εκτελούμε την εντολή jr R, η οποία κάνει ώστε η επόμενη εντολή που θα εκτελεστεί να είναι εκεί που δείχνει ο R, δηλαδή εκεί που μας είπε ο jump table να πάμε γιά την περίπτωση της τιμής που τώρα είχε η μεταβλητή.
Στη συνέχεια παρατηρήστε ότι η εντολή: sltu rd, x0, rs2 ελέγχει εάν ο rs2 είναι μεγαλύτερος του μηδενός, αλλά τον συγκρίνει ως unsigned, δηλαδή σαν πάντα ≥0, άρα ουσιαστικά ελέγχει εάν ο rs2 είναι διάφορος του μηδενός. Επίσης η εντολή: sltiu rd, rs1, 1 ελέγχει εάν ο rs1 είναι μικρότερος του 1, αλλά τον συγκρίνει ως unsigned, δηλαδή σαν πάντα ≥0, άρα ουσιαστικά ελέγχει εάν ο rs1 είναι ίσος με μηδέν.
Με βάση τις τελευταίες δύο παρατηρήσεις, χρησιμοποιήστε μιάν εντολή αφαίρεσης (sub) ή πρόσθεσης του -CONST, και στη συνέχεια μιάν από τις δύο αυτές συγκρίσεις ισότητας/ανισότητας προς μηδέν, γιά να συνθέσετε και τις εξής τέσσερεις εκχωρήσεις. Σαν προσωρινό καταχωρητή παρατηρήστε ότι μπορείτε να χρησιμοποιήσετε τον ίδιο τον καταχωρητή προορισμού, και ότι αυτό θα ίσχυε ακόμα και εάν τύχαινε ο καταχωρητής αυτός να ήταν ο ίδιος με έναν από τους καταχωρητές πηγής. Δώστε τις απαντήσεις σας γραπτά.
Τρόπος Παράδοσης:
Παραδώστε μαζί την προηγούμενη άσκηση 4 και αυτήν εδώ τη σειρά 5,
on-line, σε μορφή PDF (μόνον)
(μπορεί να είναι κείμενο μηχανογραφημένο ή/και "σκαναρισμένο" χειρόγραφο,
αλλά μόνον σε μορφή PDF).
Παραδώστε μέσω turnin ex045@hy225 [directoryName]
ένα αρχείο ονόματι ex045.pdf
που θα περιέχει τις απαντήσεις σας σε όλες τις ασκήσεις, 4 και 5.
(Σημείωση σε περίπτωση δυσλειτουργίας του turnin:
εάν αυτό σας λέει
"/home/misc/courses/hy225/TURNIN/ex...: No such file or directory"
ή κάτι ανάλογο, ενδέχεται ο υπολογιστής όπου τρέχετε
να μην μπορεί να δεί τον server που στην πραγματικότητα κρατά
αυτό το directory του μαθήματος (ή το δικό σας home directory),
λόγω δυλειτουργίας του δικτύου που ελπίζει κανείς να είναι προσωρινή.
Πολλές φορές, εκτελώντας την εντολή λίγο αργότερα,
ή από άλλον υπολογιστή μπορεί να λύσει το πρόβλημα
–χαρτης των διαθέσιμων μηχανημάτων υπάρχει
στην ιστοσελίδα του Τμήματος, κάτω από την ενότητα:
Υπηρεσίες→Ψηφιακές Υπηρεσίες).