CID aplicatii 4 : Alte circuite combinationale

De la WikiLabs

Memoria ROM

Memoria este un dispozitiv electronic utilizat pentru stocarea datelor. Aceasta poate fi de mai multe feluri, având diverse caracteristici, cum ar fi capacitatea de a-și păstra sau nu conținutul după întreruperea alimentării sau capacitatea de a-și modifica sau nu conținutul după terminarea procesului de producție.

Memoria ROM (Read-Only Memory) este un circuit combinational folosit pentru stocarea unor date ce pot fi accesate cu ajutorul unei intrari de adresă. Asa cum sugerează și numele, memoria ROM clasică nu poate fi modificată. În plus, datorită modului în care este implementată, ea își păstrează conținutul după întreruperea alimentării (este nevolatilă).

Pentru a înțelege termenul de memorie, putem face următorul exercițiu de imaginație: să ne gândim la un dulap cu mai multe sertare. Conținutul memoriei este reprezentat de conținutul sertarelor, iar adresele sunt reprezentate de etichetele lipite pe aceste sertare pentru a le identifica. Dacă avem nevoie de conținutul unui sertar, va trebui să știm eticheta aferentă acestuia (adresa).

O memorie ROM are următorii parametri: numărul de biți ai adresei, care este legat de numarul de locații de memorie (cu n biți de adresă putem forma 2𝑛 combinații diferite, deci putem accesa maxim 2𝑛 locații de memorie) și dimensiunea locației de memorie, care ne spune cât de multă informație poate stoca o locație de memorie.


Să urmărim exemplul din figura următoare:

ROM memory.PNG


Aici, adresa are dimensiunea de 4 biți, asadar putem accesa cu aceasta 16 locații diferite de memorie. Dimensiunea fiecărei locații este de 8 biți, așadar fiecare locație de memorie poate stoca numere de 8 biți. Spunem că memoria ROM este o memorie 16x8. Capacitatea acestei memorii este de 16 x 8 biți = 128 biți.

Exemple

Urmatoarele circuite pot fi vazute in 2 moduri: ca memorii de tip ROM, cu date de iesire care depind de adresa (data de intrare) de la care citesti cat si ca circuite combinationale a caror iesire este generata prin porti din intrare.

Exemplul 1: Decodorul

Decodorul este circuitul care, pentru o anumită valoare a intrării, va genera la ieșire un șir binar care conține 1 pe poziția cu indicele egal cu valorea intrării și 0 in rest. De exemplu, decodorul de mai jos pe 2 biți va avea următoarea corespondență intrare-iesire:

in out
2'b00 4'b0001
2'b01 4'b0010
2'b10 4'b0100
2'b11 4'b1000

Decoder.PNG

Vom implementa acest circuit ca memorie ROM. "in" are rolul de adresa iar out are rolul de date de la adresa respectiva.

Implementarea Verilog a circuitului

module Decoder(
  input [1:0] in,
  output reg [3:0] out
  );

  always@(in) begin
    case(in)
      2'b00: out = 4'b0001; //la adresa 0 avem valoarea 1
      2'b01: out = 4'b0010; //la adresa 1 avem valoarea 2
      2'b10: out = 4'b0100; //la adresa 2 avem valoarea 4
      2'b11: out = 4'b1000; //la adresa 3 avem valoarea 8
      default: out = 4'b0000;
    endcase
  end

endmodule

Vom implementa acest circuit ca circuit combinational cu iesirile generate din intrari prin operatii logice.

Implementarea Verilog a circuitului

module Decodor_v2(
  input [1:0] in,
  output reg [3:0] out
  );
	
// fiecare bit din iesire calculat independent
assign out[0] = ~in[0] & ~in[1];
assign out[1] = in[0] & ~in[1];
assign out[2] = ~in[0] & in[1];
assign out[3] = in[0] & in[1];

endmodule


Implementarea Verilog a modulului de test

`timescale 1ns/1ps

module Decoder_TB();
  reg [1:0] in_tb;
  wire [3:0] out_tb;
  
  integer idx;

  initial begin
    for(idx=0; idx<4; idx = idx + 1) begin
      in_t = idx;
      #1;
    end 
    #2 $stop();
  end

  Decoder DUT(
    .in(in_tb),
    .out(out_tb)
  );

endmodule

Observații:

  • Circuitul Decoder va avea o intrare pe 2 biți, care va reprezenta adresa memoriei ROM și o iesire pe 4 biți, reprezentând conținuturile memoriei ROM corespunzătoare fiecărei combinații de la intrare.
  • Chiar dacă în interiorul instrucțiunii case se acoperă toate cazurile posibile (toate combinațiile de 2 biți), este bine sa punem și un default pentru a evita inserarea în locul circuitului combinațional dorit (aici, memoria ROM) a unui latch.


Exemplul 2: Transcodorul

Trancodorul este o memorie ROM care asociază unei anumite intrări, reprezentată de adresă, un anumit cod, reprezentat de conținutului locației de memorie asociată acelei adrese.

In acest exemplu, vom folosi transcodorul pentru a realiza un modul ce controlează afișajul cu 7 segmente. Acest dispozitiv de afișare este format, așa cum sugerează și numele, din 7 segmente ce pot fi controlate separat. Putem aprinde sau stinge fiecare segment controlând tensiunea aplicată pe acesta. Pentru acest afișaj, comanda este negativă: aplicând 0 logic pe un segment acesta se va aprinde, iar aplicând 1 logic, acesta se va stinge.

Structura unui afisaj cu 7 segmente este prezentată în imaginea de mai jos:

7LED display.PNG

Pentru a realiza controlul afișării, avem nevoie de o memorie ROM ce trebuie să asocieze codului binar al fiecărui element ce poate fi afișat un șir de biți care sting sau aprind diversele segmente ale afișajului, astfel încât să apară imaginea elementului dorit.

Pentru a reprezenta, de exemplu, cele 16 numere hexa, segmentele trebuie aprinse conform Anexei 1. De exemplu, pentru primele 8 numere (0 până la 7), tabelul de asociere este următorul:

Număr Cod intrare Șir de afisare
0 4'b0000 7'b100_0000
1 4'b0001 7'b111_1001
2 4'b0010 7'b010_0100
3 4'b0011 7'b011_0000
4 4'b0100 7'b001_1001
5 4'b0101 7'b001_0010
6 4'b0110 7'b000_0010
7 4'b0111 7'b111_1000

Implementarea Verilog a modulului Display7Seg

module Display7Seg(
  input [3:0] in,
  output reg [6:0] out
  );

  always@(in) begin
    case(in)
      4'd0: out = 7'b1000000;
      4'd1: out = 7'b1111001;
      4'd2: out = 7'b0100100;
      4'd3: out = 7'b0110000;
      4'd4: out = 7'b0011001;
      4'd5: out = 7'b0010010;
      4'd6: out = 7'b0000010;
      4'd7: out = 7'b1111000;
      default: out = 7'b0000110;
    endcase 
  end

endmodule

Exerciții:

Exercițiul 1

Completați codul din Exemplul 2 astfel încât să se poată reprezenta toate numerele în bază 16 (hexazecimal), conform Anexei 1

Exercițiul 2

Un procesor are nevoie de un șir de biți numit instrucțiune pentru a știi ce trebuie să facă. Această instrucțiune codează în șirul său de biți toate informațiile necesare.

De exemplu, pentru un grup de instrucțiuni care operează cu registre, aceste informații pot fi:

  • codul instrucțiunii (operation code - opcode): un șir de biți care ne spune ce operație se execută.
  • destinația: un șir de biți care codează numărul registrului destinație.
  • sursa1: un șir de biți care codează numărul registrului folosit ca operand 1.
  • sursa2: un șir de biți care codează numărul registrului folosit ca operand 2.


Vom considera codul operației pe 4 biți, ceea ce inseamnă ca putem avea 16 instrucțiuni diferite. In plus, câmpurile care codează destinația și cei doi operanzi vor avea fiecare câte 4 biți, ceea ce inseamnă că vom avea 16 registre cu care putem lucra (R0 ... R15). Adunând dimensiunile fiecărui câmp, vom obține o instrucțiune pe 16 biți.

Instrucțiunile și codul operațiilor:

Vom considera ca procesorul nostru elementar poate suporta următoarele instrucțiuni:

Operație Cod operație Sintaxă Descriere
ADD 4'b0000 ADD Rd Rs1 Rs2 Rd = Rs1 + Rs2
SUB 4'b0001 SUB Rd Rs1 Rs2 Rd = Rs1 - Rs2
OR 4'b0010 OR Rd Rs1 Rs2 Rd = Rs1 OR Rs2
AND 4'b0011 AND Rd Rs1 Rs2 Rd = Rs1 AND Rs2
LOADC 4'b0100 LOADC Rd, const Rd = const

Pentru instrucțiunile prezentate mai sus, vom avea următoarele moduri de codare (de împărțire a informației în cadrul instrucțiunii):

Codare instructiune.png

Registrele:

Cele 16 registre vor fi codate cu un număr egal cu indexul său. De exemplu, R0 va avea codul 4'b000, R1 va avea codul 4'b0001 și așa mai departe.


Considerând cele spuse mai sus, vom avea următoarele exemple de instrucțiuni:

Exemple codare instructiuni.png


Având toate informațiile de mai sus, descrieți o memorie ROM cu adresă pe 4 biți și locații pe 16 biți, care să conțină următorul program, începând cu adresa 0:

LOADC R0, 2h
LOADC R1, 5h
LOADC R2, 7h
ADD R3, R1, R0
SUB R4, R2, R1
OR R5, R3, R4

Exercițiul 3

Proiectați un Codor Cezar cu cheie fixă sub forma unei memorii ROM.

Un Codor Cezar preia un mesaj și codează independent fiecare literă a acestuia pentru a obține un mesaj cifrat. El se folosește de o cheie fixă care se adună la fiecare literă a mesajului. De exemplu, pentru cheia "+3": a->d; b->e; c->f;...; y->b; z->c. Observați că atunci când alfabetul a ajuns la capăt, se reia de la început. Pentru decodare, se va folosi procedura inversă: pentru fiecare literă, vom aplica cheia cu semn invers (în exemplul nostru, cheia de decodare este "-3").

Observații, sfaturi, task-uri suplimentare:

  • Codorul va putea prelucra doar litere mici și spațiu gol (acesta rămâne neschimbat).
  • Cel mai usor mod de a scrie această funcționalitate este folosind un bloc de tip case.
  • Pentru testare, se pot folosi în testbench variabile de tip reg pe 8 biți, astfel:
reg [7:0] litera;
litera = "A";
  • Avansat: În SystemVerilog există și tipul de date string, ceea ce permite parcurgerea cu un for a mesajului. În Verilog clasic, astfel de parcurgeri sunt posibile, dar sintaxa este mai complicată.
  • Se va folosi codul ASCII pentru valori. Pentru o ușoara vizualizare a datelor în simulare, se poate schimba radix-ul în ASCII.
  • Avansat: Pentru a testa automat codul, se poate instanția încă un modul în testbench cu aceeași funcționalitate pe post de "golden model". Acest modul nu trebuie să fie sintetizabil și atunci sintaxa permite mai multă libertate (cum ar fi operația de modulo "%", utilă pentru testare, dar care determină un circuit complicat dacă ar fi sintetizată). Ieșirile celor două module din testbench sunt apoi comparate pentru a se verifica că ambele implementări oferă același rezultat.
  • Implementați și Decodorul Cezar și testați Codorul împreună cu acesta. Aveți grijă ca cele două module să aibă aceeași cheie.

Anexa1

Anexa1.PNG