Τα τρία είδη επεξεργαστών που μπορούν να προσομοιωθούν είναι τα παρακάτω:
- atomic
- minor
- hpi
όπως προκύπτει από εδώ:
cpu_types = {
"atomic" : ( AtomicSimpleCPU, None, None, None, None),
"minor" : (MinorCPU,
devices.L1I, devices.L1D,
devices.WalkCache,
devices.L2),
"hpi" : ( HPI.HPI,
HPI.HPI_ICache, HPI.HPI_DCache,
HPI.HPI_WalkCache,
HPI.HPI_L2)
}Μπορούμε να επιλέγξουμε το είδος του επεξεργαστή μέσα από το όρισμα --cpu και μάλιστα παρατηρούμε ότι στην δικιά μας εντολή χρησιμοποιήσαμε τον minor επεξεργαστή αφού γράψαμε --cpu=”minor”
# Use a fixed cache line size of 64 bytes
cache_line_size = 64
το οποίο θέτει το μέγεθος της cache σε 64 bytes
# Create a voltage and clock domain for system components
self.voltage_domain = VoltageDomain(voltage="3.3V")
self.clk_domain = SrcClockDomain(clock="1GHz",
voltage_domain=self.voltage_domain)
# Create the off-chip memory bus.
self.membus = SystemXBar()
# Create a cache hierarchy (unless we are simulating a
# functional CPU in atomic memory mode) for the CPU cluster
# and connect it to the shared memory bus.
if self.cpu_cluster.memoryMode() == "timing":
self.cpu_cluster.addL1()
self.cpu_cluster.addL2(self.cpu_cluster.clk_domain)
self.cpu_cluster.connectMemSide(self.membus)
Οι παρακάτω εντολές προσθέτουν τους CPUs στο cluster, αυτό που με προβλημάτισε όμως είναι η αναφορά στα 1.2V, ενώ η τάση του συστήματος παραπάνω έχει οριστεί στα 3.3V.
# Add CPUs to the system. A cluster of CPUs typically have private L1 caches and a shared L2 cache.
self.cpu_cluster = devices.CpuCluster(self,
args.num_cores,
args.cpu_freq, "1.2V",
*cpu_types[args.cpu])Στο παρακάτω κομμάτι κώδικα ορίζονται τα ορίσματα που μπορεί να δώσει κανείς πριν την εκτέλεση της προσομοίωσης, καθώς και οι default τιμές. Επομένως, καθώς εμείς δεν ορίζουμε κάτι πέραν του τύπου της cpu παρατηρούμε ότι:
# Choose the type of the cpu for the simulation from the cpu_types
parser.add_argument("--cpu", type=str, choices=list(cpu_types.keys()),
default="atomic",
help="CPU model to use")
# Set the CPU frequency
parser.add_argument("--cpu-freq", type=str, default="4GHz")
# Set number of cores
parser.add_argument("--num-cores", type=int, default=1,
help="Number of CPU cores")
# Set the type of memory to be used
parser.add_argument("--mem-type", default="DDR3_1600_8x8",
choices=ObjectList.mem_list.get_names(),
help = "type of memory to use")
# Set the number of memory channels
parser.add_argument("--mem-channels", type=int, default=2,
help = "number of memory channels")
# Set the memory ranks per channel (don’t know what that means though)
parser.add_argument("--mem-ranks", type=int, default=None,
help = "number of memory ranks per channel")
# Set the memory size
parser.add_argument("--mem-size", action="store", type=str,
default="2GB",
help="Specify the physical memory size")parser.add_argument("--mem-type", default="DDR3_1600_8x8",
choices=ObjectList.mem_list.get_names(),
help = "type of memory to use")
parser.add_argument("--mem-size", action="store", type=str,
default="2GB",
help="Specify the physical memory size")
sim_freq 1000000000000 # Frequency of simulated ticks
sim_insts 5028 # Number of instructions simulated
sim_ticks 24321000 # Number of ticks simulated
system.cpu_cluster.voltage_domain.voltage 1.200000 # Voltage in Volts
system.voltage_domain.voltage 3.300000 # Voltage in Volts
Από τα παραπάνω συμπεραίνουμε ότι η συχνότητα του επεξεργαστή είναι 1GHz(μιάς και η περίοδος είναι 1000 σε ticks), ο αριθμός των εντολών που προσομοιώθηκαν είναι 5028, τα ticks που προσομοιώθηκαν ειναι 24321000, η τάση του επεξεργαστή είναι 3.3V και το cluster voltage, όπως δηλώνεται και στο starter_se.py είναι 1.2V.
Με σχόλιο αναφερόμαστε στα κομμάτια του config που επιβεβαιώνουν το stats.txt
"system": {
# Το μέγεθος της cache
"cache_line_size": 64,
# Δύο μνήμες
"memories": [
"system.mem_ctrls0.dram",
"system.mem_ctrls1.dram"
],
"clk_domain": {
"type": "SrcClockDomain",
"cxx_class": "SrcClockDomain",
"name": "clk_domain",
"path": "system.clk_domain",
# Χρονισμός ρολογιού
"clock": [
1000
],
"domain_id": -1,
"eventq_index": 0,
"init_perf_level": 0,
"voltage_domain": "system.voltage_domain"
},
"cpu_cluster": {
"cpus": [
{
# Type of CPU
"type": "MinorCPU",
"cxx_class": "MinorCPU",
},
},
"voltage_domain": {
"type": "VoltageDomain",
"cxx_class": "VoltageDomain",
"name": "voltage_domain",
"path": "system.voltage_domain",
"eventq_index": 0,
# Voltage 3.3 V
"voltage": [
3.3
]
},
# L2
"l2": {
# Τύπος Cache
"type": "Cache",
"cxx_class": "Cache",
"name": "l2",
"path": "system.cpu_cluster.l2",
"addr_ranges": [
"0:18446744073709551615"
],
"assoc": 16,
},
}
Committed Instructions: 5028
system.cpu_cluster.cpus.committedInsts 5028 # Number of instructions committed
system.cpu_cluster.cpus.committedOps 5834 # Number of ops (including micro ops) committed
Η διαφορά ανάμεσα σε αυτούς τους δύο αριθμούς έγκειται στο γεγονός ότι η πρώτη τιμή αναφέρεται μόνο στις εντολές που απαιτούνται για να εκτελεστεί το πρόγραμμα σε c, ενώ η δεύτερη περιλαμβάνει και τα instructions που απαιτούνται για την εκκίνηση του προσομοιωτή.
system.cpu_cluster.l2.demand_accesses::.cpu_cluster.cpus.inst 332 # number of demand (read+write) accesses
system.cpu_cluster.l2.demand_accesses::.cpu_cluster.cpus.data 147 # number of demand (read+write) accesses
system.cpu_cluster.l2.demand_accesses::total 479 # number of demand (read+write) accesses
Έγιναν συνολικά 479 accesses στην l2 μνήμη όπως φαίνεται από τα στατιστικά του gem5. Ένας άλλος τρόπος να υπολογιστεί θα ήταν από τον τύπο: 1-L01.
The BaseSimpleCPU serves several purposes:
- Holds architected state, stats common across the SimpleCPU models.
- Defines functions for checking for interrupts, setting up a fetch request, handling pre-execute setup, handling post-execute actions, and advancing the PC to the next instruction. These functions are also common across the SimpleCPU models.
- Implements the ExecContext interface.
The BaseSimpleCPU can not be run on its own. You must use one of the classes that inherits from BaseSimpleCPU, either AtomicSimpleCPU or TimingSimpleCPU.
The AtomicSimpleCPU is the version of SimpleCPU that uses atomic memory accesses (see Memory systems for details). It uses the latency estimates from the atomic accesses to estimate overall cache access time. The AtomicSimpleCPU is derived from BaseSimpleCPU, and implements functions to read and write memory, and also to tick, which defines what happens every CPU cycle. It defines the port that is used to hook up to memory, and connects the CPU to the cache.
The TimingSimpleCPU is the version of SimpleCPU that uses timing memory accesses (see Memory systems for details). It stalls on cache accesses and waits for the memory system to respond prior to proceeding. Like the AtomicSimpleCPU, the TimingSimpleCPU is also derived from BaseSimpleCPU, and implements the same set of functions. It defines the port that is used to hook up to memory, and connects the CPU to the cache. It also defines the necessary functions for handling the response from memory to the accesses sent out.
Minor is an in-order processor model with a fixed pipeline but configurable data structures and execute behaviour. It is intended to be used to model processors with strict in-order execution behaviour and allows visualisation of an instruction’s position in the pipeline through the MinorTrace/minorview.py format/tool. The intention is to provide a framework for micro-architecturally correlating the model with a particular, chosen processor with similar capabilities.
By introducing the basic CPU models in gem5, especially MinorCPU, we have paved our way to build a High-PerformanceIn-order CPU based on the Arm architecture, and we name it HPI. The HPI CPU timing model is tuned to be representativeof a modern in-order Armv8-A implementation. In Fig4, we portrayed the major components included in our model. Weintroduce each of these components more in detail.Processor PipelineThe pipeline of our HPI CPU uses the same four-stage model as the MinorCPU
Για τα επόμενα ερωτήματα έχουμε γράψει δύο προγράμματα διότι δεν ήμασταν σίγουροι αν έπρεπε να παραδώσουμε κοινό report.
Ένα απλό for-loop το οποίο απαριθμεί από το 0 έως το 9 και στη συνέχεια το έκανα compile σε arm με την εντολή:
arm-linux-gnueabihf-gcc --static for_loop.c -o for_loop_arm
./build/ARM/gem5.opt -d ./out/for-loop-timingSimpleCPU configs/example/se.py --cpu-type=TimingSimpleCPU --caches -c tests/test-progs/for-loop/src/for_loop_arm
./build/ARM/gem5.opt -d ./out/for-loop-minorCPU configs/example/se.py --cpu-type=MinorCPU --caches -c tests/test-progs/for-loop/src/for_loop_arm
| Stat | TimingSimpleCPU | MinorCPU |
|---|---|---|
| host_inst_rate | 378889 | 155042 |
| host_mem_usage | 648772 | 650052 |
| system.clk_domain.clock | 1000 | 1000 |
| sim_insts | 15197 | 15289 |
| sim_seconds | 0.000050 | 0.000040 |
| sim_ticks | 49982000 | 39938000 |
Από τα παραπάνω χαρακτηριστικά ενδιαφέρον παρουσιάζει το sim_seconds το οποίο δείχνει πόσο χρόνο πήρε στην κάθε αρχιτεκτονική να ολοκληρώσει το πρόγραμμα. Ομολογουμένως, επειδή το προγραμμά μου είναι πολύ απλό η διαφορά είναι πολύ μικρή ωστόσο υπαρκτή. Αυτό συμβαίνει διότι ο TimingSimpleCPU περιμένει την μνήμη να του φέρει το δεδομένο πριν συνεχίσει με την επόμενη εντολή, πράγμα που είναι πολύ χρονοβόρο. Αυτό δεν συμβαίνει στον MinorCPU για αυτό και ολοκληρώνει τη διαδικασία νωρίτερα.
Η χρήση της μνήμης καθώς και τα instructions που προσομοιώθηκαν είναι ελαφρώς μεγαλύτερα σε τιμές στον MinorCPU σε σχέση με τον TimingSimpleCPU, ωστόσο η διαφορά είναι σε σημείο στατιστικού λάθους. Δεν νομίζω ότι μπορεί να βγει κάποιο συμπέρασμα από αυτά τα κομμάτια.
c) Στην παραπάνω προσομοίωση χρησιμοποίησα τον default χρονισμό του ρολογίου στα 1GHz, οπότε ας δοκιμάσουμε να τρέξουμε το ίδιο πρόγραμμα για τη μισή και τη διπλάσια συχνότητα για κάθε τύπο CPU.
Ας ξεκινήσουμε με τον TimingSimpleCPU και ας δημιουργήσουμε τις προσομοιώσεις για 0.5GHz και 2GHz με τις εντολές:
./build/ARM/gem5.opt -d ./out/for-loop-timingSimpleCPU-500 configs/example/se.py --cpu-type=TimingSimpleCPU --sys-clock="0.5GHz" --caches -c tests/test-progs/for-loop/src/for_loop_arm
./build/ARM/gem5.opt -d ./out/for-loop-timingSimpleCPU-2000 configs/example/se.py --cpu-type=TimingSimpleCPU --sys-clock="2GHz" --caches -c tests/test-progs/for-loop/src/for_loop_arm
| Stat | TimingSimpleCPU 0.5GHz | TimingSimpleCPU 1GHz | TimingSimpleCPU 2GHz |
|---|---|---|---|
| host_inst_rate | 402814 | 378889 | 388422 |
| host_mem_usage | 648772 | 648772 | 648768 |
| system.clk_domain.clock | 2000 | 1000 | 500 |
| sim_insts | 15197 | 15197 | 15197 |
| sim_seconds | 0.000056 | 0.000050 | 0.000047 |
| sim_ticks | 56372000 | 49982000 | 46725500 |
Το ενδιαφέρον στατιστικό είναι το sim_seconds από το οποίο προκύπτει όπως είναι λογικό ότι το πρόγραμμα μας τρέχει πιο γρήγορα για μεγαλύτερο χρονισμό στο ρολόι. Η διαφορά εδώ είναι μικρή λόγω της απλότητας του προβλήματος ωστόσο είναι υπαρκτή και αναλογικά μεγάλη. Παρατηρούμε επίσης ότι η διαφορά στο ρολόι και μόνο δεν επηρέασε (σε σημείο μη στατιστικού λάθους) καμία από τις υπόλοιπες παραμέτρους.
| Stat | MinorCPU 0.5GHz | MinorCPU 1GHz | MinorCPU 2GHz |
|---|---|---|---|
| host_inst_rate | 153783 | 155042 | 161992 |
| host_mem_usage | 650048 | 650052 | 650052 |
| system.clk_domain.clock | 2000 | 1000 | 500 |
| sim_insts | 15289 | 15289 | 15289 |
| sim_seconds | 0.000046 | 0.000040 | 0.000036 |
| sim_ticks | 45932000 | 39938000 | 36009500 |
Ακριβώς όπως και στο TimingSimpleCPU το υψηλότερο ρολόι επιτυγχάνει ταχύτερη διεκπεραίωση του προγράμματος.
- υπολογίζει τον fibonacci αριθμό του 46 με δυναμικό προγραμματισμό
Το πρόγραμμα γράφτηκε σε C και στην συνέχεια με χρήση της παρακάτω εντολής κάναμε compile για να μπορούμε να το τρέξουμε με τον gem5.
arm-linux-gnueabihf-gcc --static fib.c -o fib_armΕκτελώντας τις παρακάτω εντολές διαδοχικά, δημιουργήσαμε τις απαιτούμενες προσομοιώσεις για το πρόγραμμα μας.
Αναλυτικότερα, υλοποιούν το πρόγραμμα με χρήση δύο διαφορετικών CPUs με default επιλογή για όλα τα υπόλοιπα.
./build/ARM/gem5.opt -d fib_res_MinorCPU configs/example/se.py --cpu-type=MinorCPU --caches -c my_tests/fib_with_dynamic_programming/fib_arm
./build/ARM/gem5.opt -d fib_res_TimingSimpleCPU configs/example/se.py --cpu-type=TimingSimpleCPU --caches -c my_tests/fib_with_dynamic_programming/fib_arm- Χρόνοι εκτέλεσης προγράμματος
- Βρίσκονται στο αρχείο stats.txt
| CPU model | sim_seconds |
|---|---|
| Minor CPU | 0.000039 |
| TimingSimpleCPU | 0.000047 |
b) Παρατηρώντας τα προηγούμενα, προκύπτει πως υπάρχει διαφορά χρόνων στα δυο διαφορετικά μοντέλα CPUs.
Η διαφορά αυτή στους χρόνους που προέκυψαν οφείλεται στο γεγονός ότι ο TimingSimpleCPU περιμένει την προσπέλαση μνήμης να ολοκληρωθεί προτού συνεχίσει. Αντίθετα, στον MinorCPU δεν συμβαίνει αυτό με αποτέλεσμα να έχουμε ταχύτερη προσομοίωση.
Στο συγκεκριμένο ερώτημα έγιναν δύο αλλαγές που αφορούν την τεχνολογία της μνήμης και την συχνότητα ως εξής:
-
Αλλαγή μνήμης
Τρέχοντας την παρακάτω εντολή,./build/ARM/gem5.opt configs/example/se.py --list-mem-type
προκύπτει η λίστα που ακολουθεί στην οποία φαίνονται όλες οι διαθέσιμες μνήμες που μπορούμε να χρησιμοποιήσουμε.
Εμείς χρησιμοποιήσαμε την DDR4_2400_16X4 για το παράδειγμα μας.
-
Αλλαγή συχνότητας
Η default συχνότητα που δοκιμάσαμε τo πρόγραμμα μας προηγουμένως ήταν 1GHz. Για το συγκεκριμένο ερώτημα ωστόσο, επιλέξαμε να τροποποιήσουμε την συχνότητα και να τρέξουμε δύο προσομοιώσεις με συχνότητες 0.8GHz και 1.4GHz. Αυτό υλοποιήθηκε με την προσθήκη των παρακάτω εντολών διαδοχικά.--sys-clock="0.8GHz" --sys-clock="1.4GHz"
Στις παρακάτω εικόνες φαίνονται οι εντολές που χρησιμοποιήθηκαν για το MinorCPU μοντέλο, αντίστοιχα έγιναν και για το TimingSimpleCPU.
| CPU model | sim_seconds | system.clk_domain.clock |
|---|---|---|
| MinorCPU 0.8GHz | 0.000040 | 1250 |
| MinorCPU 1.4GHz | 0.000036 | 714 |
| TimingSimpleCPU 0.8GHz | 0.000048 | 1250 |
| TimingSimpleCPU 1.4GHz | 0.000045 | 714 |
Παρατηρούμε ότι οι χρόνοι στο TimingSimpleCPU μοντέλο παραμένουν μεγαλύτεροι από το MinorCPU μοντέλο, γεγονός που είναι αναμενόμενο. Επίσης λογικό και αναμενόμενο είναι η αύξηση της συχνότητας να μας οδηγεί σε μείωση του χρόνου σε κάθε μοντέλο ξεχωριστά.
Το πρώτο πράγμα που κάναμε ήταν να διαβάσουμε το προηγούμενο pdf με τις γενικότερες πληροφορίες για το gem5, το οποίο μας φάνηκε μεν ενδιαφέρον, αλλά γρήγορα αδυνατούσαμε να το παρακολουθήσουμε και να το καταλάβουμε όντως. Οπότε προχωρήσαμε στο επόμενο pdf το οποίο περιείχε τις οδηγίες εγκατάστασης τις οποίες και ακολουθήσαμε ευλαβικά. Ένα θέμα μας ήταν ότι μέσα από το pdf δεν μπορούσαμε να κάνουμε copy paste τις εντολές στο terminal (διότι τις έσπαγε ο τρόπος αντιγραφής κατά την επικόλληση) και αναγκαστήκαμε να τις γράψουμε. Μικρό το κακό απλώς ενέχει πάντα ο κίνδυνος του τυπογραφικού. Από αυτές, η μόνη με την οποία αντιμετωπίσε θέμα ο ένας από τους δύο μας, ήταν η εντολη protobuf την οποία δεν αναγνώριζε ότι υπάρχει. Ωστόσο παρατήρησε ότι δεν του χρειάστηκε κατά τη διάρκεια του installation για αυτό και δεν τον απασχόλησε περισσότερο. Το άλλο πρόβλημα που αντιμετωπίσαμε ήταν ότι η ύπαρξη του προγράμματος anaconda στον υπολογιστή μας μπέρδευε το gem κατά το build, ως προς το ποιά έκδοση της python να χρησιμοποιεί. Αυτό λύθηκε με απεγκατάσταση του anaconda, μιας και δεν το χρειαζόμαστε πλέον.
Τέλος, μια παρατήρηση που έχουμε να κάνουμε σχετικά με την εγκατάσταση θα ήταν στην εντολή
sconsbuild/ARM/gem5.opt -jΝ όπου ως Ν αναφέρετε τον αριθμό των επεξεργαστών και ίσως να θέλατε να το αλλάξετε σε αριθμό πυρήνων του επεξεργαστή/ων.
Στο getting started tutorial προτείνει να τεθεί η τιμή αυτή στον αριθμό των πυρήνων του συστήματος + 1.
Στο build του ARM έχουμε θέσει λοιπόν N = 1, ωστόσο για το X86 το θέσαμε το Ν = 5.
Το επόμενο πράγμα που μας προβλημάτισε είναι στην αναζήτηση των αρχείων config και stats. Ακριβώς επειδή το περιεχόμενο των αρχείων ήταν τεράστιο και εμείς μη εξοικειωμένοι ακόμα με αυτά δεν είμαστε βέβαιοι αν έχουμε κοιτάξει τα σωστά κομμάτια για να απαντήσουμε στις παραπάνω ερωτήσεις. Ήταν ενδιαφέρον από τη μία να καθίσουμε να κοιτάξουμε ολόκληρο το αρχείο, ώστε να αποκτήσουμε μια ιδέα τι περιέχεται γενικά μέσα σε αυτά, ωστόσο ελαφρώς χρονοβόρο. Παρόλα αυτά θεωρούμε ότι μας βοήθησε να κατανοήσουμε καλύτερα την λειτουργία και την δομή αρκετώ πραγμάτων.
Τελευταία παρατήρηση θα ήταν ότι αυτή η εντολή ./build/ARM/gem5.opt configs/example/se.py --cpu=MinorCPU --caches tests/test-progs/hello/bin/arm/linux/hello δεν έτρεχε. Απαιτούταν η προσθήκη του '-c' ./build/ARM/gem5.opt configs/example/se.py --cpu=MinorCPU --caches -c tests/test-progs/hello/bin/arm/linux/hello.
Κάτι το οποίο βρήκαμε ιδιαίτερα βοηθητικό (και ίσως να μην συμφωνούν πολλοί με αυτό) είναι ότι μας δώσατε ένα τεκμηριωμένο pdf με οδηγίες οι οποίες (με εξαίρεση ένα δύο σημεία) ήταν αρκετές ώστε να μας εξοικειώσουν με τα βασικά, περιείχαν υπερσυνδέσμους στο documentation για καλύτερη κατανόηση και γενικότερα μας κράτησαν από το χεράκι τόσο όσο έπρεπε, ενώ συγχρόνως μας θέταν προβλήματα τα οποία δεν ήταν προφανή μα ούτε και αδύνατο να φανταστούμε πώς να τα συνεχίσουμε. Αναμφισβήτητα, αυτή η προσέγγιση μας βοήθησε διότι καθόλη την ενασχόληση μας με το gem5 (ακόμα και κατά τη συγγραφή αυτού το ReadMe) σκεφτόμασταν συνειδητά τι κάναμε, πώς λειτουργεί αυτό που εξετάζουμε τώρα, δίχως να ακολουθούμε παθητικά εντολές. Προφανώς σε καμία περίπτωση δεν νιώθουμε ακομα σε καλό επίπεδο την κατανόηση όλων των λειτουργιών του gem, ωστόσο πιστεύουμε ότι η προσέγγιση αυτή μας επιτρέπει να καταλαβαίνουμε πλήρως τις εντολές και τις διαδικασίες που ακολουθήσαμε για το πρώτο εργαστήριο.
Ιδιαίτερα ενδιαφέρουσα βρήκαμε επίσης την συγγραφή ενος Makefile αρχείου το οποίο παράγει binaries ανάλογα με την πλατφόρμα για την οποία προορίζεται (πχ ARM, X86). Δυστυχώς δεν μπορέσαμε να το υλοποιήσουμε ακόμα περαιτέρω και να το συμπεριλάβουμε στο δικό μας Makefile, ωστόσο τώρα που ξέρουμε πως γίνεται μας δίνεται το έναυσμα να το ψάξουμε περισσότερο.
Σε γενικές γραμμές, δεν θεωρούμε την εργασία αυτή εύκολη, αλλά σίγουρα δεν τη θεωρούμε και ακατόρθωτη. Εφόσον κάποιος ξεπεράσει πιθανά προβλήματα κατά την εγκατάσταση νομίζω ότι μπορεί να εκτιμήσει την δυσκολία που προκύπτει μέσα από τα ερωτήματα, ιδίως αν ενδιαφέρεται όντως να ασχολήθει με την αρχιτεκτονική υπολογιστών.




