Laboratorul 02 - ISA 1: Operații simple

În acest laborator vom învăța ce este un procesor și un Instruction Set Architecture (ISA). Vom învăța cum funcționează arhitectura AVR (Harvard) și vom începe implementarea procesorului nostru prin a decodifica și executa câteva operații simple.

Microprocesorul

Microprocesorul este o componentă principală a unui sistem de calcul care are rolul de a executa instrucțiunile unui program. Instrucțiunile ce pot fi rulate de către un procesor sunt de mai multe tipuri:

  • Aritmetice/Logice
  • De control
  • De transfer de date

 Intel 4004

Procesoarele pentru sisteme încorporate (embedded) se găsesc în număr foarte mare în jurul nostru (electrocasnice, dispozitive portabile, automobile etc.).

Arhitecturi de microprocesoare

O caracteristică foarte importantă a unui procesor este numărul de biți folosiți pentru a reprezenta un număr întreg (lungimea unui cuvânt sau lățimea procesorului). Aceasta influențează câți biți pot fi citiți/scriși într-o singură operație de citire/scriere și, în general, câtă memorie poate fi adresată direct. Cele mai întâlnite arhitecturi din ziua de azi sunt pe 32 și pe 64 de biți, dar există și procesoare pe 4, 8, 12, 16 sau 24 de biți.

După organizarea memoriei de date/instrucțiuni avem următoarele arhitecturi:

Arhitectura Von Neumann Arhitectura Harvard
Componente: o unitate de procesare, o memorie pentru instrucțiuni și date și unități de intrare/ieșire. Componente: o unitate de procesare, o memorie de date, o memorie de program și unități de intrare/ieșire.
Are o singură magistrală de adrese și o singură magistrală de date, pe care vor circula și date și instrucțiuni. Are două magistrale de date și de adrese: una pentru instrucțiuni și una pentru date.
Având o singură magistrală de date și instrucțiuni, acestea au aceeași dimensiune. Magistralele de instrucțiuni și date pot avea dimensiuni diferite.
Fiindcă instrucțiunile și datele se află în aceeași memorie, codul se poate auto-modifica. Memoria de program poate fi facută non-volatila (odată scris un program acesta nu mai trebuie reîncărcat la fiecare pornire).

Arhitectura Von Neumann pentru sisteme de calcul

Arhitectura Von Neumann pentru sisteme de calcul

Introdusă în anul 1945 de către matematicianul american John von Neumann, această arhitectură descrie un sistem de calcul ce conține o unitate de procesare, o memorie pentru instrucțiuni și date, și unități de intrare/ieșire. Astfel, un procesor pentru o astfel de arhitectura va avea o singura magistrală de adrese și o singură magistrală de date, pe care vor circula și date și instrucțiuni. Avantajele acestei arhitecturi sunt simplitatea și faptul că, fiindcă instrucțiunile și datele se află în aceeași memorie, codul se poate auto-modifica.

 Arhitectura Von Neumann

Arhitectura Harvard pentru sisteme de calcul

Arhitectura Harvard pentru sisteme de calcul

Spre deosebire de Von Neumann, arhitectura Harvard descrie un sistem de calcul unde memoria de date și memoria de program sunt separate. Asta înseamnă că procesoarele trebuie sa aibă două magistrale de date și de adrese: una pentru instrucțiuni și una pentru date. Avantajul este că aceste magistrale nu trebuie să aibă aceeași dimensiune, deci putem avea un procesor pe 8 biți care adresează mai mult de 256 de octeți de memorie. Un alt avantaj este ca memoria de program poate fi facută non-volatila, deci odată scris un program acesta nu mai trebuie reîncărcat la fiecare pornire.

 Arhitectura Harvard

Componentele unui microprocesor

Un procesor este format din patru componente principale:

  • Unitatea aritmetică/logică (UAL): execută operațiile aritmetice și logice.
  • Registre: cea mai mică unitate de stocare ce face parte dintr-un procesor, în general de dimensiunea unui cuvânt. Registrele care pot fi folosite pentru majoritatea operațiilor se numesc registre de uz general (e.g. AX, BX, CX, DX pe arhitectura x86). Totuși unele registre au un scop particular. Dintre acestea cele mai importante sunt Program Counter (PC), Instruction Register (IR), Stack Pointer (SP) și Flags Register (registru ce indică statusul curent al procesorului).
  • Porturi de intrare/ieșire (I/O): linii prin intermediul cărora procesorul se interfațează cu periferice (inclusiv cu memoria). Prin intermediul acestora el poate transmite sau recepționa date.
  • Unitatea de control (UC): decodifică instrucțiunea curentă și pe baza acesteia și a stării interne, generează semnale de control pentru toate resursele procesorului (e.g. registrele de intrare/ieșire și operația aritmetică necesare unei instrucțiuni) și coordonează activarea acestor resurse.

În funcție de arhitectura procesorului numărul și numele registrelor poate varia.

Microcontroller

Un microcontroller este un chip care conține un microprocesor, memorie de date, memorie de program și dispozitive periferice. Accentul în design se pune pe consum redus de energie și costuri mici de producție. De aceea, în general, rulează la frecvente reduse (zeci, sute de MHz). De asemenea pot fi specializate pe o anumită funcționalitate.

Majoritatea microcontrollerelor sunt formate din:

  • Unitatea centrală de procesare
  • RAM (memorie volatilă) și/sau Flash/EEPROM (memorie non-volatila)
  • Porturi de intrare/ieșire
  • Timere
  • Interfețe seriale și paralele

ATTiny20

ATTiny20 este un microcontroller produs de firma Microchip. El este construit pe arhitectura AVR și are următoarele caracteristici:

  • Arhitectura RISC
  • 112 de instrucțiuni – majoritatea executate într-un singur ciclu de ceas
  • 16 registre generale de 8 biți
  • 2048 de octeți de memorie programabilă Flash
  • 128 de octeți de memorie SRAM
  • 12 pini de intrare/ieșire programabili
  • Frecvența de operare de până la 12 MHz
  • Tensiune de alimentare 1.8V - 5V

Arhitectura unui set de instrucțiuni

Orice microprocesor are definit un Instruction Set Architecture (ISA):

  • O interfață între hardware și software. Așa cum în OOP rolul unei interfețe este de a oferi o imagine de ansamblu, fără a oferi detalii de implementare, așa și aici ISA definește operațiile, accesul la memorie, modurile de stocare suportate de hardware, fără a da detalii de implementare.
  • Definește setul de instrucțiuni recunoscute de către procesor. Orice altă instrucțiune are efecte nedeterminate asupra procesorului.
  • Definește tipurile de date care pot fi recunoscute de procesor.
  • Definește contextul în care o instrucțiune operează.
  • Definește cum o serie de instrucțiuni trebuie să interacționeze între ele (propagare de flaguri, executare de salturi condiționale, etc).

O instrucțiune este reprezentată de un șir de biți, o parte dintre ei fiind codul operatiei. Instrucțiunile pot fi codificate pe un număr constant de biți sau să fie de dimensiune variabilă.

În funcție de ISA numărul, numele, codificarea și funcționarea instrucțiunilor poate varia.

Etapele executării unei instrucțiuni

Orice procesor poate avea un ciclu de prelucrare a instrucțiunilor diferit, în funcție de ISA-ul pe care îl implementează, însă toate vor urma următoarea structură:

  • IF (Instruction Fetch): următoarea instrucțiune este adusă din memorie de la adresa către care pointează registrul Program Counter (PC) și este stocată în registrul Instruction Register (IR). PC este apoi incrementat pentru a pointa către următoarea instrucțiune de încărcat.
  • ID (Instruction Decode): instrucțiunea din IR este decodificată.
  • EX (Execute): execuția efectivă a instrucțiunii. Aceasta etapă variază în funcție de tipul instrucțiunii curente.
  • MEM (Memory Access): ciclu folosit în cazul în care instrucțiunea accesează memoria.
  • WB (Write Back): scrierea noilor valori în registre.

Instrucțiuni AVR

Instrucțiunile pentru AVR sunt pe 16 biți sau 32 de biți. Deși procesorul este pe 8 biți, memoria este organizată în rânduri de 16 biți lungime, procesorul putând citi câte un rând la fiecare ciclu de ceas din memoria de program. Astfel, memoria noastră de 2048 de octeți este definită ca 1024 * 2 octeți, adică în 1024 de rânduri, fiecare rând având 2 octeți (16 biți). Exemple de instrucțiuni AVR:

  • Aritmetice/Logice
    • ADD
    • SUB
    • MUL
    • AND
    • OR
    • NEG
  • De control
    • RJMP
    • BREQ
  • De transfer de date
    • LDS
    • MOV
    • STS
    • IN
    • OUT

 ADD

Mai sus puteți vedea un extras din definiția setului de instrucțiuni AVR. Observați numele instrucțiunii (ADD), descrierea funcționalității ei, o descriere matematică a funcționalității, sintaxa în limbaj de asamblare (AVRASM) și codul operației pe 16 biți.

Rd și Rr sunt nume generice date celor două registre folosite, în practică ambele pot fi oricare dintre registrele R0 - R31 (0 <= d <= 31, 0 <= r <= 31). Procesorul știe ce registre să folosească prin concatenarea biților marcați cu d si r din codul operației. Spre exemplu, codificarea operației ADD R16, R1 este 0000 1101 0000 0001.

Registrul SREG

Pentru a reține toate aceste evenimente se folosesc flag-uri grupate în ceea ce se numește Processor Status Register sau SREG (este oarecum echivalentul registrului flags din arhitectura x86).

Printre flag-urile des folosite din SREG se află:

  • Z (Zero) - indică dacă rezultatul unei operații aritmetice este zero.
  • C (Carry) - indică faptul că s-a realizat un transport la nivelul bitului cel mai semnificativ in cazul unei operații aritmetice.
  • V (Overflow) - arată că, în cazul unei operații aritmetice cu semn, complementul față de doi al rezultatului nu ar încăpea în numărul de biți folosiți pentru a reprezenta operanzii. Mai simplu, ia valoarea 1 dacă:
    • adun 2 numere pozitive și obțin rezultat negativ, sau
    • adun 2 numere negative și obțin rezultat pozitiv
  • N (Negative) - semnul rezultatului unei operații aritmetice.
  • S (Sign) - este un flag unic AVR, calculat după formula S = N xor V.

Exemplu: Pentru operatia add 129, 177, încep prin a scrie numerele în binar:

129 = 1000 0001
177 = 1011 0001

și după le adun:

  1000 0001 +
  1011 0001
-------------
1 0011 0010
  ^
  |
 MSB

Verific ce flag-uri sunt active astfel:

  • Z = 0 → rezultatul meu nu conține numai 0-uri (cei 8 biți pe care se scrie rezultatul)
  • C = 1 → rezultatul final a depășit numărul de 8 biți (cât are procesorul nostru)
  • V = 1 → MSB = 0, iar cele două numere adunate au ambele MSB = 1 (din două numere negative am obținut unul pozitiv)
  • N = 0 → rezultatul este unul pozitiv (MSB = 0)
  • S = 1 → S = N ^ V = 0 ^ 1 = 1

Detaliere flag-uri

Detaliere flag-uri

  • Z (Zero) - indică dacă rezultatul unei operații aritmetice este zero
  • C (Carry) - indică faptul că s-a realizat un transport la nivelul bitului cel mai semnificativ in cazul unei operații aritmetice. Altfel spus, a avut loc o depășire în aritmetica modulo N considerată. În procesorul nostru pe 8 biți, 255 + 1, deși ar trebui să aibă rezultatul 256, de fapt acesta este 0 din cauza aritmeticii modulo 256 (28). Pentru a diferenția dintre un 0 apărut real și unul cauzat de o depășire, se utilizează acest semnal de carry.
  • V (Overflow) - arată că, în cazul unei operații aritmetice cu semn, complementul față de doi al rezultatului nu ar încăpea în numărul de biți folosiți pentru a reprezenta operanzii. Cu alte cuvinte, se poate întampla ca adunând două numere pozitive, să obținem unul negativ (127signed + 1signed = -128signed), dar și adunând două numere negative să obținem unul pozitiv (-128signed + (-1)signed = +127signed). Evident, rezultatul nu este corect în aceste situații, și semnalarea se face prin flag-ul de overflow. Dacă am aduna un număr pozitiv (MSB = 0) cu unul negativ (MSB = 1), atunci nu obțin overflow.
  • N (Negative) - semnul rezultatului unei operații aritmetice (este pur și simplu valoarea bitului de semn al rezultatului)
  • S (Sign) - este un flag unic AVR, calculat după formula S = N xor V, ce reprezintă “care ar fi trebuit să fie semnul corect al rezultatului”. Cu alte cuvinte, dacă N == 1, dar și V == 1, înseamnă că rezultatul este negativ, dar din cauza unei depășiri. S este setat în acest caz pe 0, semnalând că semnul “corect” al operației ar fi trebuit să fie pozitiv.

Scrierea biților cu valorile corespunzătoare din SREG revine UAL-ului. La execuția unei operații, se calculeaza și valorile fiecărui flag ce poate fi afectat de acel tip de operație. Practic, ca și la arhitectura x86, putem considera că SREG este un registru global, a cărui valoare este setată de ultima instrucțiune aritmetico-logică executată de procesor.

TL;DR

  • Procesorul este unitatea principală a unui sistem de calcul.
    • Execută instrucțiunile unui program.
    • Poate avea arhitectură Von Neumann (o singura memorie ce conține instrucțiuni și date) sau Harvard (memoria de instrucțiuni este separată de memoria de date).
  • Un microcontroller este un chip ce conține un microprocesor, memorie de date și/sau program și dispozitive periferice.
    • ATtiny20 este microcontrollerul pe care îl vom implementa noi pe parcursul semestrului.
  • ISA definește interfața dintre hardware și software.
    • Instrucțiunile recunoscute de procesor și cum funcționează acestea.
    • ISA-ul pentru ATtiny20 este AVR (AVR Instruction Set).
  • Instrucțiunile au, în mod general, 5 etape în prelucrarea lor: IF, ID, EX, MEM, WB.
    • Informațiile necesare prelucrării instrucțiunii se găsesc în codul instrucțiunii care, în cazul AVR, poate fi de 16 sau de 32 biti.

Exerciții

În acest laborator vom implementa în Verilog o mică parte a unui procesor. La sfârșitul laboratorului procesul va fi capabil să execute instrucțiuniile din rom.v: nop, neg, add, sub, and și or.

nop
neg     r1
add     r1, r2
sub 	r1, r2
and 	r1, r2
or 	r1, r2

Folosiți manualul setului de instrucțiuni AVR pentru a implementa codificările și decodificările comenzilor. Căutați în meniu capitolele aferente fiecărei instrucțiuni.

În scheletul de laborator sunt câteva fișiere de interes:

  • decode_unit.v se ocupă de decodificarea instrucțiunilor. Aici trebuie să adăugăm instrucțiunile noi.
  • control_unit.v implementează logica de control. Aici trebuie sa translatăm type în opcode.
  • alu.v execută operații aritmetice și logice. Aici trebuie calculate rezultatele operațiilor aritmetice.
  • rom.v conține codul ce va fi executat.

Cheatsheet schelet laborator

Dacă implementați complet instrucțiunile necesare, în urma simulării checker_view.v toate semnalele vor fi verzi. Codul se află într-o memorie ROM, așadar pentru orice schimbare în cod tot designul trebuie resimulat.

Task 00 Descărcați scheletul de laborator.

Următoarele task-uri necesită modificări în mai multe fișiere din arhivă. Căutați TODO-urile din fișierele decode_unit.v (pentru etapa de ID), alu.v (pentru etapa de EXEC), control_unit.v (pentru etapa de WB).

Task 01 (1p) Implementați instrucțiunea NOP.

Task 02 (1p) Implementați instrucțiunea NEG.

Task 03 (2p) Implementați instrucțiunea ADD.

Task 04 (2p) Implementați instrucțiunea SUB.

Task 05 (2p) Implementați instrucțiunea AND.

Task 06 (2p) Implementați instrucțiunea OR.

Resurse

apm/laboratoare/02.txt · Last modified: 2024/02/29 15:06 (external edit)
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0