This is an old revision of the document!
= Laboratorul 3 - Instruction Set Architecture 2 =
* Responsabil: Petru Guriță * Data publicării: 21.10.2018 * Data ultimei modificări: ~~LASTMOD~~
Cum citim un datasheet? ATTiny20
Mai jos avem reprezentarea binară a instrucțiunii add R5, R3
:
Bit index | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Bit value | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
Bit significance | opcode | Rd | Rr | Rd | Rr |
Fiindcă register file-ul procesorului nostru (ATtiny20) are doar 16 registre, putem ignora biții Rr
si Rd
de pe pozițiile 9 și 8 (Atmel garantează că, din motive de compatibilitate, vor fi mereu setați pe 1, sau, echivalent, vor fi folosite numai registrele R16
→ R31
).
Așadar, instrucțiunile add R21, R19
și add R5, R3
sunt echivalente în implementarea noastră.
Mereu filtrați de la instrucțiunile cele mai particulare (cele care au partea constanta cea mai mare) până la cele mai generale (au partea constanta mai mica). Adică? dintre opcode-ul 1) 0000_1111_1010_0101 și opcode-ul 2) 1111_0101_10rr_rddd mai particulara este 1.
Pentru a asigura comunicația dintre procesor, memorie și periferice le vom interconecta pe toate într-o topologie de tip magistrala. Aceasta este un mediu partajat de comunicație, în sensul că există un singur set de semnale (de control, date și adrese) la care au acces toate modulele conectate (spre deosebire de o topologie point-to-point unde există o legătură separată între fiecare modul care dorește să comunice cu altul).
Magistrala de sistem este, pe scurt, o magistrală care conectează procesorul de memoria principala și de periferice. Magistrala de sistem trebuie sa aibă, în general, 3 componente:
În general, pentru comunicația mai multor module pe o magistrala, trebuie stabilit un protocol de comunicație (trebuie evitată situația în care există mai mulți vorbitori concomitenți pe aceeași linie, fiindcă acest gen de conflicte pot duce la scurtcircuite și alte probleme de natură electrică. În practică, există un bus master și mai mulți bus slaves. Nu întâmplător, în cazul nostru bus master va fi CPU-ul iar, momentan, singurul slave de pe system bus este memoria.
Un exemplu de system bus este Wishbone, un standard open-source folosit în multe design-uri IP (vezi OpenCores). De asemenea, ISA bus este un exemplu de system bus, folosit în strămoșul calculatoarelor desktop moderne, IBM PC.
Ierarhii de magistrale În sistemele desktop, system bus, adică o singură magistrala general-purpose, a fost demult înlocuita cu o ierarhie de magistrale (unele locale, altele externe) de viteze si lățimi de bandă diferite. Astfel, pentru asigurarea conectivității la memorie, un CPU x86 folosește un memory controller hub, care se ocupă de interfațarea propriu-zisă (generarea semnalelor de control) pentru memoria SDRAM conectată pe placa de bază.
Istoric, acest controller de memorie s-a aflat pe un chip extern procesorului, dar situat pe placa de baza în apropierea acestuia, numit Northbridge. Magistrala de mare viteza dintre CPU și Northbridge se numește Front-side bus (sau FSB, pe scurt) pentru sistemele Intel, sau EV6, pentru implementarea cu același rol de la AMD.
Despre FSB:
Modurile de adresare a memoriei descriu metoda prin care este calculată adresa efectivă a datelor accesate, folosind informații constante și/sau conținute în registre. Pentru a fi mai flexibil pentru programatori un ISA va implementa mai multe moduri de adresare a memoriei, fiecare cu avantaje și dezavantaje. Aceste moduri pot fi folosite prin diferite instrucțiuni. Mai departe ne vom uita peste modurile de adresare a memoriei de date disponibile pe arhitectura AVR.
Adresa de memorie dorită se află chiar în corpul instrucțiunii. Un exemplu de instrucțiune ce folosește adresare directa este LDS (16-bit).
Biții marcați cu k formează o constantă ce reprezintă adresa de memorie de unde sunt citite datele, iar aceste date sunt scrise în registrul Rd.
Adresa de memorie dorită se află în registrul X, Y sau Z. O instrucțiune ce folosește adresare indirectă cu deplasament este LDD (i).
Conform datasheetului când ne referim la registrul X, Y sau Z ne referim de fapt la grupări de câte doua registre:
Există desigur și variații Adresare indirectă cu offset: Adresa de memorie dorită se află în registrul Y sau Z, la care se adaugă un deplasament găsit în corpul instrucțiunii. O instrucțiune ce folosește adresare indirectă cu deplasament este LDD (iv).
Biții marcați cu q formează o constantă ce reprezintă deplasamentul ce trebuie adăugat adresei de memorie găsită în registrul Y sau Z. Datele sunt scrie în registrul Rd.
Adresare indirectă cu pre-decrementare
Adresa de memorie dorită se afla în registrul X, Y sau Z, care este decrementată înainte de a fi folosită. O instrucțiune ce folosește adresare indirectă cu deplasament este LDD (iii).
Adresare indirectă cu post-incrementare
Adresa de memorie dorita se afla în registrul X, Y sau Z, care este incrementata după ce este folosită. O instrucțiune ce folosește adresare indirectă cu deplasament este LDD (ii).
Procesorul pe care îl implementăm la laborator are o arhitectura de tip Harvard. Asta înseamnă că vom avea 2 memorii separate pentru instrucțiuni și date.
* Memoria de instrucțiuni este de tip ROM (2048 bytes) și se găsește în rom.v adresele sunt pe 8 biți datele au 16 biți (adică lungimea unei instrucțiuni) * Memoria de date este un SRAM sincron de tip pipeline (128 bytes) și se găsește în sram.v adresele sunt pe 7 biți datele au 8 biți (= dimensiunea unui registru)
Procesorul lucrează cu 16 registre “general purpose” de 8 biți. Teoretic există 32 registre, însă implementarea noastră nu va folosi niciodată primele 16. Astfel când scriem cod AVR vom putea folosi numai registrele din intervalul R16 → R31.
Totuși, după cum ați văzut în laboratorul trecut, nu vom lucra direct cu aceste registre, ci vom folosi registrul sursa Rr și registrul sursă/destinație Rd. Aceștia în spate adresează cele 16 registre. Puteți vedea acest lucru în sursa register_file.v în care este implementat un Dual Port SRAM. Acesta garantează accesul concomitent (într-un ciclu de ceas) atât la Rd, cât și la Rr.
Driver-ul de memorie pentru modulul dual_port_sram este reg_file_interface_unit definit în fișierul reg_file_interface_unit.v. Aici vom seta biții de *cs*, *we* si *oe*, precum și liniile de date și adrese ce sunt necesare pentru lucrul cu memoria.
În cazul memoriei Dual Port SRAM din laborator, vom avea acești biți atât pentru Rd, cât și pentru Rr (*rr_cs, rd_cs*, etc). Similar, vor exista linii de date și adrese distincte pentru fiecare dintre Rd si Rr.
Fata de laboratorul anterior, veți găsi o serie de semnale noi ce au fost adăugate unor modulelor procesorului nostru pentru a putea permite implementarea instrucțiunilor de lucru cu memoria. În această secțiune va fi explicat pe scurt rolul acestor semnale noi și utilitatea lor.
Instrucțiunile pe care le veți implementa în acest laborator diferă putin de cele UAL pe care le-ați întâlnit pana acum. În aceasta secțiune va fi explicat pe scurt cum trebuie citit si interpretat datasheet-ul în cazul acestor instrucțiuni.
Instrucțiunile prezentate pot fi grupate și după tipurile de memorie intre care transfera date, astfel:
Modurile de adresare ale memoriei sunt urmatoarele:
Semnale pentru implementarea instructiunilor de lucru cu memoria:
decode.v
reg_file_interface_unit.v * rr_addr si rd_addr - iesirile modulului care spun ce adresa RD si RR folosim pentru operatia curenta * internal_rr_addr si internal_rd_addr - determina ce adresa se asigneaza pe iesirile rr_addr si rd_addr * signals - un registru ai carui biti reprezinta ce operatie(READ sau WRITE) se executa asupra memoriei sau registrelor (RD,RR)
control_unit.v * writeback_value - valoarea ce trebuie salvata in memorie la finalul ultimului stagiu de pipeline * bus_data - valoarea aflata pe magistrala de date in urma ultimei operatii de citire din memorie * alu_rr - valoarea curenta stocata in registrul RR * alu_rd - valoarea curenta stocata in registrul RD
Instructiunile ATTiny pentru acces la memorie:
Hinturi generice: Incercati sa folositi semnale cat mai generice. Preferati sa folositi grupuri in loc de tipuri de instructiuni. Checkerul este destul de hard-codat in aceasta faza. Cuvantul asistentului ramane final la corectare.
Task 1 (1p). rom.v Generati codul acesta folosind toolul avr
ldi r29, 10 sts 10, r29 ldi r29, 0 ldi r28, 138 ld r27, y lds r28, 10 mov r27, r28
Task 2 (1p). simulati unitTestCpu.v
Unele zone sunt rosii, pentru a investiga ce mai trebuie implementat: De la instante si procese, expandati pana ajungeti la cpu.v. Adaugati semnalele la fereastra (rclick → add to wave window). Re-lansati (Re-launch).
Task 3 (2p). Pentru LDI. Modificati decode_unit.v
Pentru a scrie in registrul Rd, trebuie sa controlam 2 semnale: writeback_value (control_unit.v) si signals[CONTROL_REG_RD_WRITE] (signal_generation_unit.v)
Task 4 (2p). Pentru STS. Modificati decode_unit.v. Atenție la calcularea adresei - adică ce valoare atribuim în opcode_imd. Memoria de date e mapată între 0x0040 și 0x00BF .
Pentru a scrie pe bus, trebuie sa controlam 2 semnale: signals[`CONTROL_REG_RR_READ] si signals[`CONTROL_MEM_WRITE] (signal_generation_unit.v).
Inspectati bus_interface_unit.v.
Valoarea scrisa pe bus, este transmisa prin data_to_store din control_unit.v. La acest laborator va fi preluata din variabila de buffer pipeline alu_rr .
Task 5 (2p). Pentru LD. Modificati decode_unit.v
Trebuie generate mai multe semnale pentru o adresare indirectă:
Ultimele două semnale sunt active și pentru o adresare directă a memoriei? Modificati semnalele corespunzatoare în signal_generation_unit.v .
Adresa indirecta este transmisa la bus_interface_unit prin indirect_address (o cuplare de alu_rr si alu_rd), inspectați control_unit.v.
Valoarea scrisa pe bus este transmisa prin writeback_value din control_unit.v.
Task 6 (2p). Pentru LDS. Modificati decode_unit.v (Atenție la calcularea adresei)
Pentru a citi de pe bus, trebuie sa controlam 2 semnale: signals[`CONTROL_REG_RD_WRITE] si signals[`CONTROL_MEM_READ]
Inspectati bus_interface_unit.v.
Valoarea scrisa pe bus este transmisa prin writeback_value din control_unit.v.
Task 7 (Bonus - 1p). Pentru MOV. Modificati decode_unit.v
Modificati scheletul astfel incat mov sa fie executata corect.
Tool java pentru a genera cod mașină AVR
Greseli comune intalnite in laborator: * Ati setat opcode_rr in loc de opcode_rd. * Ati setat opcode_rd in loc de opcode_rr. * Ati calculat adresa directa considerand 7 biti, in loc de adresa specificata in datasheet. La LDS si STS cautati forma {~8, 8, 10, 9, 3, 2, 1, 0}. * Ati uitat sa setati grupurile in decode_unit.v. * Nu ati setat CONTROL_REG_RR_READ si CONTROL_REG_RD_READ pentru toate instructiunile care au nevoie de ele (de ce are nevoie LD Y sa citeasca 2 registre?). * LDI, STS(16-bit) și LDS(16-bit) folosesc doar 4 biti in loc de 5 biti pentru codificarea opcode_rr/opcode_rd. Totusi, aceste instructiuni pot opera doar pe registri R16-R31, asadar o decodificare corectă seteaza bitul cel mai semnificativ al lui opcode_rr/opcode_rd la valoarea 1. * Ati setat o valoare gresita in decode_unit.v * Nu ati adaugat toate semnalele la wave window. Nu ati inspectat unit testele sa vedeti exact care pas returneaza 1'bx, si ce semnale nu sunt pe valorile asteptate.