di  -  mercoledì 10 febbraio 2010

Forte del successo e consenso ricevuto dal primo rappresentante, il 68000, negli anni a seguire Motorola si dedicò a ritoccare il progetto col 68010 (sistemando alcuni errori e aggiungendo qualche funzionalità utile per la scrittura di s.o.) e fornendo anche una soluzione a basso costo col 68008 (che, col bus indirizzi a 20 bit anziché 24 e col bus dati a 8 bit anziché 16, permetteva di utilizzare un package da 48 pin invece dei canonici 64).

Si trattò, in ogni caso, di leggere modifiche al core (che cambiò poco), portandosi dietro alcune limitazioni non indifferenti, come ad esempio l’ALU interna che per i calcoli rimaneva a 16 bit, costringendo quindi a utilizzarla due volte per le istruzioni che manipolavano dati a 32 bit (il calcolo degli indirizzi, invece, poteva contare su alcuni sommatori dedicati a 32 bit), e richiedendo pertanto più cicli di clock per l’espletamento della funzione.

Purtroppo all’epoca era veramente difficile fare di più, in quanto l’architettura del 68000 era già abbastanza complicata e richiedeva non pochi transistor (circa 68000, appunto; si trattava di una CPU molto costosa per l’epoca), per cui la sua architettura a 32 bit dovette attendere ben 5 anni, con l’arrivo del 68020 nel 1984, per esprimere tutto il suo potenziale.

Grazie ai suoi 190mila transistor, infatti, metteva finalmente a disposizione un’ALU che permetteva di operare con dati a 8, 16 e 32 bit alla stessa velocità, oltre a un’efficiente unità di calcolo degli indirizzi (le cui modalità con questo microprocessore arrivano a essere particolarmente complicate).

L’efficiente pipeline a 3 stadi permetteva un certo livello di sovrapposizione nell’esecuzione delle istruzioni la quale, con un’accorta schedulazione nella sequenza delle istruzioni, comportava un certo risparmio di cicli di clock per alcune di esse, fino ad arrivare addirittura a non impiegarne nessuno (l’esecuzione viene completamente “assorbita” da quella per la precedente istruzione):

Come si può notare dall’esempio, la seconda istruzione non richiede alcun ciclo di clock in quanto risulta del tutto “assorbita” dall’esecuzione della precedente, mentre la quarta, approfittando di una parziale sovrapposizione con la terza, riesce a essere completata richiedendo soltanto un ciclo di clock.

Tutto avviene anche grazie ad altre migliorie introdotte con questo processore, prima fra tutte l’introduzione di una cache di 256 byte per il codice, che consente di memorizzare fino a 64 longword (allineate ovviamente a indirizzi di 32 bit), sfruttando un meccanismo di tipo direct mapped (si sfruttano i bit da 7 a 2 per selezionare la longword nell’instruction cache, per poi controllare, se è presente un dato caricato, se i rimanenti bit da 31 a 8 coincidono con quello “in lavorazione”).

Un meccanismo molto semplice, ma abbastanza efficiente, che permette di risparmiare gli accessi in memoria per il fetch delle istruzioni, in particolare quando si eseguono cicli. Per la gestione di questa cache Motorola ha introdotto soltanto due registri disponibili esclusivamente nella modalità supervisore, CACR (Cache Control Register; contiene alcun flag, fra cui quello di abilitazione della stessa) e CAAR (Cache Address Register; usato soltanto per azzerare una specifica linea di cache).

Altro elemento che ha contribuito notevolmente al miglioramento delle prestazioni è stato il bus dati a 32 bit, che ha permesso finalmente di manipolare informazioni di questa dimensione in un colpo solo. In realtà tutta la logica legata all’interfaccia esterna è cambiata, e permette adesso di leggere e scrivere da 1 a 4 byte (quindi anche 3: numero abbastanza strano per chi è a abituato a lavorare con potenze di due) da qualunque periferica, togliendo di mezzo il precedente collo di bottiglia che imponeva indirizzi pari per l’accesso a dati di 16 o 32 bit (pena il sollevamento di un’eccezione).

A conti fatti, è il controller del bus che, in maniera del tutto trasparente all’applicazione, si occupa di verificare l’allineamento dei dati, e di provvedere poi a effettuare uno o più accessi al bus, e infine ricombinare opportunamente i dati selezionando le parti interessate (esempio: leggo un solo byte dall’indirizzo $3, e i primi 3 byte a partire dall’indirizzo $4, che combinati assieme forniscono la longword che m’interessava leggere dall’indirizzo $3).

Ovviamente si tratta di un meccanismo che ha un suo costo in termini di transistor, e che comporta in ogni caso una penalizzazione in termini prestazionali, a causa del maggior numero di accessi al bus necessari per completare l’operazione, per cui è sempre consigliabile allineare opportunamente i dati in modo da massimizzare l’efficienza del sistema (è un consiglio che vale per qualunque microprocessore).

Oltre ai registri per la gestione della cache, a livello supervisore troviamo pochi cambiamenti. E’ stato aggiunto un nuovo bit nel registro di stato supervisore (SR) che consente di abilitare il tracing delle sole istruzioni che comportano un cambio nel flusso dell’esecuzione (salti, trap, loop, ecc.; prima venivano “tracciate” tutte le istruzioni).

Inoltre è adesso disponibile un nuovo registro per lo stack da utilizzare in occorrenza degli interrupt (chiamato ISP, Interrupt Stack Pointer; mentre MSP, Master Stack Pointer, si riferisce al tradizionale stack supervisore, SSP), che consente di trattare in maniera più efficiente gli interrupt (avendo uno stack dedicato allo scopo).

Sono stati aggiunti anche dei nuovi stack frame, concetto utilizzato da Motorola per la gestione delle eccezioni che fa uso dello stack per memorizzare delle strutture dati particolari a seconda della tipologia di evento verificatosi. Si tratta, in buona sostanza, di informazioni che riflettono lo stato dell’evento e/o quello interno della CPU, in modo da permettere all’handler di avere un quadro completo per poterlo gestire correttamente.

Scusate per l’orientamento dell’immagine, ma verticalmente avrebbe occupato troppo spazio. Quest’esempio serve a rendere l’idea di quali informazioni venivano memorizzate nello stack, e quindi della loro utilità per l’handler deputato alla gestione dell’apposito evento, che poteva anche cambiare alcuni dati poi utilizzati dall’istruzione RTE (utilizzata per il rientro dall’eccezione). Da notare che sono presenti dati che riguardano addirittura gli stadi della pipeline.

Il 68020 non manca, comunque, di portare innovazioni anche a quelle aree che possiamo definire più “care” a un programmatore: i nuovi tipi di dato, le nuove istruzioni e, nel caso di questo microprocessore, le nuove (e complicate) modalità d’indirizzamento introdotte.

Due nuovi tipi di dato fanno capolino: i dati “unpacked” e i campi di bit. I primi sono legati al tipo BCD, che prevedeva già le operazioni di somma, sottrazione, e negazione di byte contenenti due cifre BCD “packed“. Con le istruzioni PACK e UNPK adesso è possibile prelevare due byte “unpacked”, aggiustarli opportunamente (tramite l’uso di un offset, utile, ad esempio, per convertire dati ASCII o EBCDIC), e convertirli in due cifre BCD “packed”; e viceversa, chiaramente.

Molto più interessanti sono, invece, i campi di bit che, come si può intuire dal nome, sono dati costituiti da un numero variabile di bit (da 1 a 32). A tal proposito è presente un nutrito numero di istruzioni che, sfruttando il barrel shifter integrato in questa CPU, copre gli utilizzi più noti: test, azzeramento, set di tutti i bit a 1, complemento, estrazione con o senza segno, inserimento, e ricerca del primo bit a 1.

Inoltre tutte le istruzioni eseguono prima un test del campo di bit, copiando il bit più significativo del campo nel flag N (che normalmente contiene il segno di un numero intero), e settando il flag Z (di “zero”) opportunamente (1 se il campo di bit vale zero, 0 altrimenti). In pratica è come se ogni istruzione eseguisse prima una BFTST (Bit Field TeST) prima di procedere all’operazione vera e propria, in pieno “stile Motorola” (coi 68000 anche delle normali MOVE impostano questi flag, come se venisse eseguita una TST sull’operando sorgente).

Sempre per quanto riguarda gli interi, è stata introdotta un’istruzione che permette di estendere con segno un byte a una longword (prima era possibile estenderlo soltanto a una word). Ma la novità più importante è l’introduzione di apposite istruzioni che consentono finalmente di eseguire moltiplicazioni e divisioni che vanno oltre le classiche 16×16 -> 32 bit e 32/16 -> 16,16 bit (quoziente e resto) rispettivamente.

Finalmente è possibile eseguire moltiplicazioni 32×32 -> 32 bit (vengono conservati i soli 32 bit bassi del risultato) e 32×32 -> 64 bit. Più variegate le divisioni, che adesso consentono 32/32 -> 32 bit (solo quoziente), 32/32 -> 32,32 bit (quoziente e resto), 64/32 -> 32,32 (quoziente e resto). Si tratta di una grossa lacuna che la famiglia 68000 si trascinava da tempo, e che viene finalmente colmata.

Le nuove istruzioni di check/confronto completano il quadro per quanto riguarda le estensioni sugli interi. Il 68000 metteva già a disposizione l’istruzione CHK per verificare se un intero era compreso in un intervallo (altrimenti veniva sollevata un’eccezione), ma aveva tre limitazioni: l’intero doveva risiedere su un registro dati (niente registri indirizzi), doveva essere una word o una longword (niente byte), e il limite inferiore era fissato a 0. CHK2 li rimuove tutti e tre, fornendo pertanto maggior flessibilità.

Un grosso tallone d’Achille di questa famiglia è stato rappresentato dalla penuria di istruzioni per la sincronizzazione in ambienti multiprocessor. L’unica istruzione in merito era la TAS (Test And Set), che agiva su un solo byte, leggendolo dalla memoria e settando il bit più significativo in un ciclo di read-modify-write col bus lockato (da evitare come la peste sugli Amiga).

Ben poca cosa, insomma, ed è per questo che Motorola ha messo a disposizione CAS e CAS2, istruzioni che consentono di eseguire un confronto (di byte, word e longword per la prima; coppie di word e longword la seconda) di un registro (due per la CAS2) con un operando in memoria (ancora due per la CAS2) e, nel caso in cui i valori coincidano, scrivere in memoria il valore di un altro registro (sempre due per la CAS2), il tutto col bus lockato.

A livello di istruzioni utili al s.o. c’è da segnalare la presenza di un’istruzione di TRAP condizionata (viene sollevata in presenza di una condizione), e del supporto al concetto di chiamata a “modulo” (tramite le istruzioni CALLM e RTM). Quest’ultimo riguarda la possibilità di richiamare delle funzioni effettuando dei precisi controllo sulle modalità di esecuzione, provvedendo eventualmente a copiare dati dal chiamante all’area dimemoria del modulo chiamato. Si tratta di un meccanismo complicato e, com’è possibile immaginare, particolarmente lento (nel peggiore dei casi si arriva a superare i 450 cicli di clock).

Il pezzo forte di questa CPU è, a mio avviso, rappresentato dalle nuove modalità d’indirizzamento che mette a disposizione:

Siamo passati dalle 14 del 68000, alle 18 attuali. Le quattro nuove modalità sono denominate Memory Indirect (Postindexed e Preindexed) e PC Indirect (Postindexed e Preindexed). Possono sembrare poche, ma risultano estremamente flessibili in quanto permettono di indirizzare la memoria indirettamente, come specifica il nome.

Il meccanismo funziona in questo modo: viene calcolato prima l’indirizzo del puntatore da leggere valutando tutti gli operandi presenti fra parentesi quadre; viene quindi prelevato il puntatore, e a questo vengono sommati gli operandi presenti fuori dalla parentesi quadre; a questo punto l’indirizzo risultante viene finalmente utilizzato per leggere o scrivere il dato dalla memoria.

I benefici sono indubbi, considerato che queste modalità sono applicabili ove nel codice siano presenti strutture dati che fanno uso di puntatori, oppure per accedere alla tabella dei metodi virtuali delle classi per i linguaggi orientati gli oggetti. Inoltre anche le “vecchie” modalità d’indirizzamento presentano dei miglioramenti, perché a ogni registro Xn è possibile applicare un fattore di scala da 1 a 8.

Infine, e non meno importante, il 68020 porta in dote una completa e avanzata interfaccia verso i coprocessori, per i quali è possibile salvare e ripristinare lo stato interno, eseguire salti condizionati sulla base del loro registro di stato, eseguire cicli nelle stesse modalità, TRAP condizionate, settare un byte sulla base di una condizione, e ovviamente eseguire delle operazioni.

Tanta, veramente tantissima carne sul fuoco che Motorola mette con questo microprocessore, che possiamo considerare come ideale completamento di un’architettura che già col capostipite 68000 aveva strappato consensi e fatto felici schiere di programmatori che ancora oggi serbano un ottimo ricordo di questa gloriosa famiglia.

31 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    ReNeSiS
     scrive: 

    Eccellente articolo. Non serve aggiungere altro.

    Aspetto i mercoledì proprio per questo :)

  • # 2
    neutrino76
     scrive: 

    Ottimo articolo anche se troppo tecnicno per un grafico come me….ho cominciato a fare grafica 3D nel 1992 proprio grazie a un MC68020 dell’Amiga 1200….gran bel processore e grande macchina!

  • # 3
    Caterpillar
     scrive: 

    Sei ingegnere informatico vè? Io che sto ancora studiando faccio un po’ fatica a seguirti eheheh

  • # 4
    Cesare Di Mauro (Autore del post)
     scrive: 

    No, ho una laurea in informatica, ma la passione per le architetture degli elaboratori la coltivo da ben prima che frequentassi l’università e mi laureassi.

    @neutrino76: mi spiace, ma purtroppo è difficile spiegare concetti complessi senza scendere nel tecnico.

  • # 5
    iva
     scrive: 

    Bell’articolo, complimenti e continua la serie fino al 68060, mi raccomando!

  • # 6
    Dubbioso
     scrive: 

    qual era la differenza tra 68020 e 68EC020 montato sul’A600 se non ricordo male?

  • # 7
    Cesare Di Mauro (Autore del post)
     scrive: 

    L’Amiga 600 montava il classico 68000.

    Il 1200, invece, montava il 68EC020, che differiva dal 68020 per il fatto che il bus indirizzi era troncato a 24 bit (quindi poteva indirizzare al massimo 16MB di memoria, esattamente come il 68000).

  • # 8
    TheFoggy
     scrive: 

    Studio ingegneria informatica e trovo i tuoi articoli sempre molto interessanti! La cosa che mi rammarica di più, è che i processori non Intel vengano trattati solo marginalmente (anzi..in pratica vengono solo citati..eccezion fatta per il Cell, dato che il nostro prof era invaghito di tale processore!), mentre alcuni meriterebbero decisamente di più. Tra questi il 68000 e lo Z80, molto utilizzati ancora oggi.. Per fortuna arrivano i tuoi articoli! ;)
    Sinceramente li trovo decisamente chiari (più di alcune slide che ci han fornito sull’8086, ad esempio), ma forse sono avvantaggiato dal mio percorso di studi..(anche se, devo essere sincero, la specialistica mi sta deludendo un pochino, concentrandosi all’80% sui protocolli di rete, che non incontrano i miei interessi..)

    ps: noto con piacere che è cambiato il sistema per il captcha! Spero sia più “affidabile” di quello vecchio! ;)

  • # 9
    D
     scrive: 

    “Il 1200, invece, montava il 68EC020, che differiva dal 68020 per il fatto che il bus indirizzi era troncato a 24 bit (quindi poteva indirizzare al massimo 16MB di memoria, esattamente come il 68000).”

    Giusto per avere un ripassino.
    I 16MB indirizzabili erano la fast ram ovvero quella che negli attuali pc sarebbe la comune ram dico bene ? La Chip Ram invece è una cosa a parte dedicata al chipset ricordo bene ? Nell’eventualità che la chip ram fosse espandibile (lo era ?) quale sarebbe stato il limite ?
    Le schede acceleratrici utilizzavano infine una terza ram dedicata giusto ?

  • # 10
    Cesare Di Mauro (Autore del post)
     scrive: 

    No, ho parlato propriamente di 16MB di memoria, indicando quindi il totale: chip ram + fast ram + rom + spazio d’indirizzamento per le periferiche.

    Negli Amiga, la chip ram è l’unica memoria a cui può accedere il chipset. Ma alla chip ram può accedere la CPU, sebbene abbia una priorità più bassa (per cui è preferibile utilizzare la fast ram).

    Per quanto riguarda l’espandibilità della chip ram, dipende dal chipset utilizzato. Quello vecchio arrivava al massimo a 512KB (quindi non era espansibile). Le versioni più recenti che facevano uso di FatAgnus, potevano arrivare a 1MB con le schede madri più moderne. Con ECS e AGA si potevano indirizzare fino a 2MB di chip ram (il 1200 ne aveva già 2MB, il massimo; per fortuna).

    Le schede acceleratrici erano di diversi tipi. Potevano utilizzare i 24 bit di indirizzamento e, quindi, essere limitate sempre a 16MB. Altre, invece, permettevano di montare memoria collegata direttamente alla nuova CPU montata sulla scheda, per cui in linea teorica il limite era di 4GB (in realtà era molto meno, a causa del costo e dei limiti dei banchi di memoria disponibili all’epoca).

    Grazie a tutti per i complimenti. :)

  • # 11
    D
     scrive: 

    Quindi escludendo le schede acceleratrici quanta ram si poteva al massimo montare su un amiga 1200 togliendo la chip e le rom ?

    Da 16 passiamo a 14 causa chip e dobbiamo ancora scendere: si trovavano banchi di memoria che riempivano perfettamente quella differenza o si compravano più “grossi” e si accettava di sprecarne un po’ ?

  • # 12
    Cesare Di Mauro (Autore del post)
     scrive: 

    Il 1200 poteva montare fino a 8MB di fast ram, se non ricordo male.

  • # 13
    D
     scrive: 

    Ma veniva castrato così dalla commodore o semplicemente non esistevano dei banchi di ram intermedi tra gli 8 ed i 16MB ?

  • # 14
    Cesare Di Mauro (Autore del post)
     scrive: 

    Non è che Commodore lo castrasse. Il fatto è che non puoi riservare tutta la memoria per la fast ram. Ogni computer suddivide lo spazio d’indirizzamento in base alle sue esigenze, e fra queste ci sono le ROM (che sono memorie, e devono essere mappate, appunto), l’I/O, e nel caso degli Amiga anche la chip ram.

    Per cui se dai 16MB totali togli di mezzo i 2MB della chip ram, e gli 8MB della fast ram, i rimanenti 6MB sono utilizzati per mappare ROM e I/O. In realtà in mezzo a questi 6MB c’è dello spazio inutilizzato, ma la fast va mappata linearmente a partire dalla fine dei 2MB di chip ram ($00200000), per cui si ferma all’indirizzo $009FFFFF (poco prima dei registri CIA, che sono mappati verso la fine di $00Bxxxxx).

  • # 15
    D
     scrive: 

    ok quindi c’è dello spreco
    in mezzo a tutte le schede che sono state prodotte non c’è stato niente che abbia tentato di occupare anche quella parte di memoria ?

  • # 16
    Cesare Di Mauro (Autore del post)
     scrive: 

    Per il 500 c’erano alcune espansioni che dai 512KB (rispetto ai 512KB di base) normalmente aggiungevano, arrivavano a poco meno 2MB.

    Questa memoria si chiamava “slow mem”, come ho spiegato in qualche articolo, e iniziava dall’indirizzo $00C00000. I chip custom, invece, iniziavano a $00DFF000 (ma erano replicati ad altri indirizzi nell’area $00Dxxxxx). Quindi queste schede di memoria occupavano lo spazio da $00C00000 a $00Dxxxxx (con xxxxx inferiore al limite minimo dei registri dei chip custom), occupando un’area normalmente inutilizzata.

    Ciò valeva solo per il 500, perché per gli altri modelli la memoria aggiunta era generalmente fast ram (decisamente più veloce, visto che l’accesso era esclusivo della CPU), e partiva dal canonico indirizzo $00200000.

    Comunque se consideriamo che ai tempi la memoria la si pagava a peso d’oro, questi “sprechi” non comportavano problemi. Considera che il mio 1200 non l’ho mai espanso, e coi suoi 2MB di chip ram c’ho fatto di tutto. ;)

  • # 17
    D
     scrive: 

    “questi “sprechi” non comportavano problemi”

    Sarà però lo trovo comunque strano in un’epoca dove l’ottimizzazione delle poche risorse era la parola d’ordine in virtù proprio dei costi enormi

  • # 18
    Cesare Di Mauro (Autore del post)
     scrive: 

    Ma non c’era proprio nulla da ottimizzare: una macchina con tutti e 16 i MB sfruttati per infilarci ram e quant’altro, era semplicemente improponibile a causa degli enormi costi.

    In ogni caso i 16MB erano un limite delle macchine aventi le CPU col bus indirizzi castrato. Macchine economiche, per l’appunto.

    L’Amiga 2500, il 3000, il 4000 e in generale tutte le schede acceleratrici dotate di CPU con indirizzamento a 32 bit, potevano arrivare a 4GB, e soltanto per i primi 16MB la situazione era esattamente la stessa delle macchine economiche.

  • # 19
    D
     scrive: 

    Beh non dico proprio a quei tempi là, anche in seguito, del resto non mi pare di averla sognata la daughter board per aggiungere slot zorro e pci proprio al 1200 rendendolo di fatto quasi un 4000.

  • # 20
    Cesare Di Mauro (Autore del post)
     scrive: 

    E potevi aggiungere quanta memoria volevi con alcune schede acceleratrici, grazie al fatto che la fast ram veniva mappata oltre i 16MB (nello spazio d’indirizzamento dei 4GB). ;)

  • # 21
    D
     scrive: 

    Un bel articoletto su cosa se ne sarebbe fatto il vecchio amiga os di 4GB ?

  • # 22
    Cesare Di Mauro (Autore del post)
     scrive: 

    Te lo dico subito: all’epoca poco o nulla. Serviva soltanto a chi aveva a che fare con immagini grandi, filmati, scene 3D complesse, desktop publishing con documenti aventi diversi oggetti. ;)

  • # 23
    D
     scrive: 

    Se non ricordo male la serie tv babylon 5 aveva delle scene 3D che erano state realizzate proprio con gli amiga. Non è che si riesce a risalire alle configurazioni di quelle workstation per potersi fare un’idea ?

  • # 24
    Andrea Carnera
     scrive: 

    Bellissimo articolo!

  • # 25
    Cesare Di Mauro (Autore del post)
     scrive: 

    @D: qui http://www.atarimagazines.com/compute/issue166/68_The_making_of_Babylo.php trovi qualcosa.

  • # 26
    D
     scrive: 

    Visto, effettivamente non erano neanche troppo potenti come sistemi anche se non ho capito una cosa. C’erano più sistemi che lavoravano ognuna su un frame separato o operavano come un cluster ?

  • # 27
    Cesare Di Mauro (Autore del post)
     scrive: 

    Penso che ognuno lavorasse su un frame separato, visto che ogni frame richiedeva circa 45 minuti per il rendering.

  • # 28
    D
     scrive: 

    Oltretutto esisteva qualcosa per far operare gli amiga come un cluster ?

  • # 29
    Cesare Di Mauro (Autore del post)
     scrive: 

    No, ma si poteva sfruttare AREXX http://en.wikipedia.org/wiki/AREXX scrivendo le applicazioni in modo da accettare comandi di questo tipo, e facendo in modo da far girare un client in ascolto su ogni macchina per pilotare l’applicazione che faceva il rendering.

    AREXX è stato molto usato per il controllo delle applicazioni.

    Per Babylon 5 è stato usato un programma che faceva da master, ma non so se lo usava allo scopo.

  • # 30
    Marco
     scrive: 

    Altra differenza, il 68EC020 non esternalizzava nemmeno i segnali BGACK (per l’arbitraggio “avanzato” del BUS), OCS, ECS, DBEN e IPEND, tutte rilevanti soprattutto in ambienti multiprocessore.
    Questo dovuto al fatto che il 68020 integrava anche il supporto ad ambienti multiprocessore (anche con bus asincrono) glueless fino ad 8 CPU, cosa particolarmente raffinata per l’epoca e sfruttata da moltissimi mini con system V che fino a pochi anni fa pullulavano nelle università.

  • # 31
    Cesare Di Mauro (Autore del post)
     scrive: 

    Certamente. D’altra parte l’EC era low cost non a caso…

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.