Diferență între revizuiri ale paginii „CID aplicatii 8 : Registre si memorii RAM”

De la WikiLabs
Jump to navigationJump to search
 
(Nu s-au afișat 6 versiuni intermediare efectuate de același utilizator)
Linia 24: Linia 24:
  
  
Pornind de la notiunea de registru, care poate fi vazut ca o memorie cu o locatie si avand "m" biti, se doreste cresterea acestei memorii astfel incat aceasta sa curpinda mai multe locatii adresabile, unde sa se poata stoca date. Adaugand mai multi registri in paralel, unul langa altul si cateva circuite de tip mux/demux se obtine o memorie de tip RAM (Random Access Memory), aceasta fiind cu "m" locatii de "n" biti fiecare. Aceste memorii se cheama "Random Access" deoarece permit si scriere si citire.
+
Pornind de la notiunea de registru (care poate fi vazut ca o memorie cu o locatie si avand "m" biti) se doreste cresterea acestei memorii astfel incat aceasta sa curpinda mai multe locatii adresabile, unde se pot stoca date. Adaugand mai multi registri in paralel, unul langa altul, si cateva circuite de tip mux/demux se poate obtine o memorie de tip RAM (Random Access Memory), cu "m" locatii de "n" biti fiecare. Aceste memorii se cheama "Random Access" deoarece permit si scriere si citire. In functie de tehologia folosita, memoriile RAM pot fi construite din bistabili sau din latch-uri.
  
  
Linia 32: Linia 32:
  
  
Semnalele acestui circuit se pot imparti in semnale ce tin de interfata de scriere sau interfata de citire si apoi mai in semnale de date si semnale de control. Interfata sa este:  
+
Semnalele acestui circuit se pot imparti in semnale ce tin de interfata de scriere sau interfata de citire si mai apoi in semnale de date si semnale de control. Interfata sa este:  
 
 
:Semnalul de "clock" : controleaza sincronizarea registrilor din memorie.
+
:Semnalul de "clock": controleaza sincronizarea registrilor din memorie.
:Semnalul de "addr_read" : adresa de la care se citesc datele.  
+
:Semnalul "addr_read": adresa de la care se citesc datele.  
:Semnalul de "data_read" : data ce se citeste.
+
:Semnalul "data_read": data ce se citeste.
:Semnalul de "we" : semnal de control ce controleaza activarea scrierii. Scrierea are loc doar cand acest semnal este activ.
+
:Semnalul de "we": semnal de control ce controleaza activarea scrierii. Scrierea are loc doar cand acest semnal este activ.
:Semnalul de "addr_write" : adresa la care se scriu datele.
+
:Semnalul "addr_write": adresa la care se scriu datele.
:Semnalul de "data_write" : data ce urmeaza a fi scrisa atunci cand semnalul "we" este activ.
+
:Semnalul "data_write": data ce urmeaza a fi scrisa atunci cand semnalul "we" este activ.
  
  
Linia 47: Linia 47:
 
'''Observatie''': In unele situatii se mai poate pune un registru suplimentar pe iesirea datelor, cu rol in sincronizare si evitarea timpilor de propagare prea lungi intre elemente de memorare (ajuta implementarea conceptului de pipeline).
 
'''Observatie''': In unele situatii se mai poate pune un registru suplimentar pe iesirea datelor, cu rol in sincronizare si evitarea timpilor de propagare prea lungi intre elemente de memorare (ajuta implementarea conceptului de pipeline).
  
'''Observatie''': Exista mai multe variante de memorii ram (cu registru pe iesire/fara, multiport/single port citire, adrese separate sau nu pentru citire/scriere). Cea de mai sus fiind o memorie cu un singur port de citire, fara registru suplimentar pe iesire, ce foloseste adrese distincte pentru citire si pentru scriere (la fel de bine se poate lucra si cu o singura adresa, comuna pentru scriere si citire).
+
'''Observatie''': Exista mai multe variante de memorii RAM (cu registru pe iesire/fara, multiport/single port citire, adrese separate sau nu pentru citire/scriere). Cea de mai sus este o memorie cu un singur port de citire, fara registru suplimentar pe iesire, ce foloseste adrese distincte pentru citire si pentru scriere (la fel de bine se poate lucra si cu o singura adresa, comuna pentru scriere si citire).
 
   
 
   
'''Observatie''': Memoriile RAM pot sa nu aibe reset, utilizatorul trebuie apoi sa aibe grija sa citeasca doar din locatii scrise de el anterior.
+
'''Observatie''': Memoriile RAM pot sa nu aiba reset, utilizatorul trebuie apoi sa aiba grija sa citeasca doar din locatii scrise de el anterior.
 
In mod uzual ele nu au reset.
 
In mod uzual ele nu au reset.
 
 
  
 
==Exemple==
 
==Exemple==
Linia 61: Linia 59:
  
  
'''Descrierea registrului (fisierul register_8b.v):'''
+
'''Descrierea registrului (fisierul register_8b.sv):'''
 
<syntaxhighlight lang="Verilog">
 
<syntaxhighlight lang="Verilog">
 
module register_8b
 
module register_8b
 
(
 
(
input wire clock,
+
input logic clock,
input wire reset, // activ pe "1"
+
input logic reset, // activ pe "1"
input wire we,
+
input logic we,
input wire [7:0] data_in,  
+
input logic [7:0] data_in,  
output reg [7:0] data_out // data_out are aceeasi dimensiune ca data_in
+
output logic [7:0] data_out // data_out are aceeasi dimensiune ca data_in
 
     );
 
     );
 
      
 
      
always@(posedge clock) // clock sincronizeaza actiunile circuitului
+
always_ff @(posedge clock) // clock sincronizeaza actiunile circuitului
 
begin    // doar pe edge-ul pozitiv circuitul actioneaza
 
begin    // doar pe edge-ul pozitiv circuitul actioneaza
 
     if(reset == 1)
 
     if(reset == 1)
Linia 97: Linia 95:
 
Un alt mod de a scrie un registru este dat mai jos, cu observatia ca aici am pus semnalul de reset activ in logica negativa:
 
Un alt mod de a scrie un registru este dat mai jos, cu observatia ca aici am pus semnalul de reset activ in logica negativa:
  
'''Descrierea registrului (fisierul register_8b_v2.v):'''
+
'''Descrierea registrului (fisierul register_8b_v2.sv):'''
 
<syntaxhighlight lang="Verilog">
 
<syntaxhighlight lang="Verilog">
 
module register_8b_v2
 
module register_8b_v2
 
(
 
(
input wire clock,
+
input logic clock,
input wire reset_n, // activ in "0"
+
input logic reset_n, // activ in "0"
 
// uzual semnalel cu n in fata sau la sfarsit sunt in logica negativa: nreset, resetn
 
// uzual semnalel cu n in fata sau la sfarsit sunt in logica negativa: nreset, resetn
input wire we,
+
input logic we,
input wire [7:0] data_in,
+
input logic [7:0] data_in,
output wire [7:0] data_out
+
output logic [7:0] data_out
 
     );
 
     );
  
reg [7:0] memorie_efectiva;   
+
logic [7:0] memorie_efectiva;   
  
 
assign data_out = memorie_efectiva;
 
assign data_out = memorie_efectiva;
 
      
 
      
always@(posedge clock)
+
always_ff @(posedge clock)
 
begin   
 
begin   
 
if( reset_n == 0)  
 
if( reset_n == 0)  
Linia 133: Linia 131:
  
  
'''Descrierea test bench-ului registrului pe 8biti: (fisierul register_8b_tb.v):'''
+
'''Descrierea test bench-ului registrului pe 8biti: (fisierul register_8b_tb.sv):'''
 
<syntaxhighlight lang="Verilog">
 
<syntaxhighlight lang="Verilog">
 
`timescale 1ns / 1ps
 
`timescale 1ns / 1ps
Linia 139: Linia 137:
 
module register_8b_tb();
 
module register_8b_tb();
  
reg clock_tb;
+
logic clock_tb;
reg reset_tb;
+
logic reset_tb;
reg we_tb;
+
logic we_tb;
reg [7:0] data_in_tb;
+
logic [7:0] data_in_tb;
wire [7:0] data_out_tb;
+
logic [7:0] data_out_tb;
  
 
register_8b dut // varianta cu reset activ in "1"
 
register_8b dut // varianta cu reset activ in "1"
Linia 165: Linia 163:
 
initial
 
initial
 
begin
 
begin
reset_tb = 0;
+
reset_tb <= 0;
we_tb =0;
+
we_tb <= 0;
data_in_tb = 0;
+
data_in_tb <= 0;
 
// observatie: pana la primul reset sau prima scriere, valoarea din registru va fi necunoscuta (in simulare X)
 
// observatie: pana la primul reset sau prima scriere, valoarea din registru va fi necunoscuta (in simulare X)
 
 
 
// dau reset la circuit
 
// dau reset la circuit
 
@(posedge clock_tb); // astept sa treaca 1 clock cycle
 
@(posedge clock_tb); // astept sa treaca 1 clock cycle
reset_tb = 1;
+
reset_tb <= 1;
 
@(posedge clock_tb);
 
@(posedge clock_tb);
reset_tb = 0;
+
reset_tb <= 0;
 
 
 
repeat(5) // dupa 5 cicli de ceas
 
repeat(5) // dupa 5 cicli de ceas
Linia 182: Linia 180:
 
 
 
// incep sa fac scrieri
 
// incep sa fac scrieri
we_tb =1;
+
we_tb <= 1;
data_in_tb = 5;
+
data_in_tb <= 5;
 
@(posedge clock_tb);
 
@(posedge clock_tb);
we_tb =0;
+
we_tb <= 0;
data_in_tb = 10; // scrierea asta nu se face deoarece nu am write enable activ
+
data_in_tb <= 10; // scrierea asta nu se face deoarece nu am write enable activ
 
@(posedge clock_tb);
 
@(posedge clock_tb);
data_in_tb = 11; // nici asta
+
data_in_tb <= 11; // nici asta
 
@(posedge clock_tb);
 
@(posedge clock_tb);
data_in_tb = 12; // nici asta
+
data_in_tb <= 12; // nici asta
 
@(posedge clock_tb);
 
@(posedge clock_tb);
we_tb =1;
+
we_tb = 1;
data_in_tb = 42; // asta da
+
data_in_tb <= 42; // asta da
 
@(posedge clock_tb);
 
@(posedge clock_tb);
data_in_tb = 51; // si asta da
+
data_in_tb <= 51; // si asta da
 
@(posedge clock_tb);
 
@(posedge clock_tb);
we_tb =0;
+
we_tb <= 0;
 
 
 
repeat(5) // dupa 5 cicli de ceas
 
repeat(5) // dupa 5 cicli de ceas
Linia 208: Linia 206:
 
endmodule
 
endmodule
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Proiectul complet se poate descarca de aici: [https://wiki.dcae.pub.ro/images/7/7b/Exemplu_registru_8b.zip Exemplu_registru_8b.zip]
 
  
  
Linia 215: Linia 211:
 
===Exemplul 2: Memorie RAM===
 
===Exemplul 2: Memorie RAM===
 
 
In urmatorul exemplu se implementeaza memoria RAM descrisa mai sus, particularizata pentru 64 de locatii a cate 8b fiecare :
+
In urmatorul exemplu se implementeaza memoria RAM descrisa mai sus, particularizata pentru 64 de locatii a cate 8b fiecare:
  
'''Descrierea memoriei RAM: (fisierul ram64x8_v1.v):'''
+
'''Descrierea memoriei RAM: (fisierul ram64x8_v1.sv):'''
 
<syntaxhighlight lang="Verilog">
 
<syntaxhighlight lang="Verilog">
 
module ram64x8_v1
 
module ram64x8_v1
 
(
 
(
input wire clock,
+
input logic clock,
 
//interfata de citire
 
//interfata de citire
input wire [5:0] addr_read, // 64 locatii => 6 biti de adresa  
+
input logic [5:0] addr_read, // 64 locatii => 6 biti de adresa  
output wire [7:0] data_read, // fiecare locatie are 8b   
+
output logic [7:0] data_read, // fiecare locatie are 8b   
 
// interfata de scriere
 
// interfata de scriere
input wire we,
+
input logic we,
input wire [5:0] addr_write,
+
input logic [5:0] addr_write,
input wire [7:0] data_write
+
input logic [7:0] data_write
 
     );
 
     );
 
      
 
      
reg [7:0] memorie_efectiva [0:63]; // memorie cu locatiile de la 0 la 63, fiecare avand 8 biti     
+
logic [7:0] memorie_efectiva [0:63]; // memorie cu locatiile de la 0 la 63, fiecare avand 8 biti     
  
 
assign data_read = memorie_efectiva[addr_read]; // fara registru pe iesire => citire asincrona fata de clock  
 
assign data_read = memorie_efectiva[addr_read]; // fara registru pe iesire => citire asincrona fata de clock  
 
      
 
      
always@(posedge clock)
+
always_ff @(posedge clock)
 
begin     
 
begin     
 
     if(we == 1)
 
     if(we == 1)
 
     begin
 
     begin
     memorie_efectiva[addr_write] = data_write; // scriu la locatia data de "addr_write" din memoria efectiva datele "data_write"
+
     memorie_efectiva[addr_write] <= data_write; // scriu la locatia data de "addr_write" din memoria efectiva datele "data_write"
 
     end  
 
     end  
 
end     
 
end     
Linia 250: Linia 246:
 
Echivalent se poate folosi si sintaxa cu always ca mai jos.
 
Echivalent se poate folosi si sintaxa cu always ca mai jos.
  
'''Descrierea memoriei RAM: (fisierul ram64x8_v2.v):'''
+
'''Descrierea memoriei RAM: (fisierul ram64x8_v2.sv):'''
 
<syntaxhighlight lang="Verilog">
 
<syntaxhighlight lang="Verilog">
 
module ram64x8_v2 // varianta cu always combinational pe citire
 
module ram64x8_v2 // varianta cu always combinational pe citire
 
(
 
(
input wire clock,
+
input logic clock,
 
//interfata de citire
 
//interfata de citire
input wire [5:0] addr_read,
+
input logic [5:0] addr_read,
output reg [7:0] data_read,
+
output logic [7:0] data_read,
 
// interfata de scriere
 
// interfata de scriere
input wire we,
+
input logic we,
input wire [5:0] addr_write,
+
input logic [5:0] addr_write,
input wire [7:0] data_write
+
input logic [7:0] data_write
 
     );
 
     );
 
      
 
      
reg [7:0] memorie_efectiva [0:63];   
+
logic [7:0] memorie_efectiva [0:63];   
  
always@(*) //  citire asincrona fata de clock  
+
always_comb //  citire asincrona fata de clock  
 
begin
 
begin
 
data_read = memorie_efectiva[addr_read];  
 
data_read = memorie_efectiva[addr_read];  
 
end
 
end
  
always@(posedge clock)
+
always_ff @(posedge clock)
 
begin     
 
begin     
 
     if(we == 1)
 
     if(we == 1)
 
     begin
 
     begin
     memorie_efectiva[addr_write] = data_write; // scriu la locatia data de "addr_write" din memoria efectiva datele "data_write"
+
     memorie_efectiva[addr_write] <= data_write; // scriu la locatia data de "addr_write" din memoria efectiva datele "data_write"
 
     end  
 
     end  
 
end     
 
end     
Linia 282: Linia 278:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
'''Descrierea test bench-ului memoriei RAM: (fisierul ram64x8_v1_tb.v):'''
+
'''Descrierea test bench-ului memoriei RAM: (fisierul ram64x8_v1_tb.sv):'''
 
<syntaxhighlight lang="Verilog">
 
<syntaxhighlight lang="Verilog">
 
`timescale 1ns / 1ps
 
`timescale 1ns / 1ps
Linia 288: Linia 284:
 
module ram64x8_tb();
 
module ram64x8_tb();
  
reg clock_tb;
+
logic clock_tb;
reg [5:0] addr_read_tb;
+
logic [5:0] addr_read_tb;
wire [7:0] data_read_tb;
+
logic [7:0] data_read_tb;
reg we_tb;
+
logic we_tb;
reg [5:0] addr_write_tb;
+
logic [5:0] addr_write_tb;
reg [7:0] data_write_tb;
+
logic [7:0] data_write_tb;
  
  
Linia 320: Linia 316:
 
initial
 
initial
 
begin
 
begin
we_tb =0;
+
we_tb <= 0;
data_write_tb = 0;
+
data_write_tb <= 0;
addr_read_tb = 0; // citind de la o adresa nescrisa inca, iesirea e necunoscuta
+
addr_read_tb <= 0; // citind de la o adresa nescrisa inca, iesirea e necunoscuta
addr_write_tb = 0;
+
addr_write_tb <= 0;
 
repeat(5) // dupa 5 cicli de ceas
 
repeat(5) // dupa 5 cicli de ceas
 
begin
 
begin
Linia 330: Linia 326:
 
 
 
// incep sa fac scrieri
 
// incep sa fac scrieri
we_tb =1; // scriu data 5 la adresa 10
+
we_tb <= 1; // scriu data 5 la adresa 10
addr_write_tb = 10;
+
addr_write_tb <= 10;
data_write_tb = 5;
+
data_write_tb <= 5;
addr_read_tb = 11; // citind de la o adresa nescrisa inca, iesirea e necunoscuta
+
addr_read_tb <= 11; // citind de la o adresa nescrisa inca, iesirea e necunoscuta
 
@(posedge clock_tb);
 
@(posedge clock_tb);
we_tb =0; // scrierea asta nu se face deoarece nu am write enable activ
+
we_tb <= 0; // scrierea asta nu se face deoarece nu am write enable activ
addr_write_tb = 11;
+
addr_write_tb <= 11;
data_write_tb = 10;
+
data_write_tb <= 10;
 
@(posedge clock_tb);
 
@(posedge clock_tb);
data_write_tb = 11; // nici asta
+
data_write_tb <= 11; // nici asta
 
@(posedge clock_tb);
 
@(posedge clock_tb);
data_write_tb = 12; // nici asta
+
data_write_tb <= 12; // nici asta
 
@(posedge clock_tb);
 
@(posedge clock_tb);
we_tb =1;
+
we_tb <= 1;
addr_write_tb = 20; // scriere ok  
+
addr_write_tb <= 20; // scriere ok  
data_write_tb = 42; // scriu data 42 la adresa 20
+
data_write_tb <= 42; // scriu data 42 la adresa 20
 
@(posedge clock_tb);
 
@(posedge clock_tb);
data_write_tb = 51; // si asta; suprascriu datele anterioare la adresa 20.
+
data_write_tb <= 51; // si asta; suprascriu datele anterioare la adresa 20.
 
@(posedge clock_tb);
 
@(posedge clock_tb);
addr_write_tb = 21;
+
addr_write_tb <= 21;
data_write_tb = 11; // scriere ok
+
data_write_tb <= 11; // scriere ok
addr_read_tb = 20; // citesc de la adresa 20, scrisa anteior deci voi vedea date cunoscute pe iesire.
+
addr_read_tb <= 20; // citesc de la adresa 20, scrisa anteior deci voi vedea date cunoscute pe iesire.
 
@(posedge clock_tb);
 
@(posedge clock_tb);
addr_write_tb = 23;
+
addr_write_tb <= 23;
data_write_tb = 14; // scriere ok  
+
data_write_tb <= 14; // scriere ok  
addr_read_tb = 21; // variez adresa de citire si pot parcurge memorie locatie cu locatie
+
addr_read_tb <= 21; // variez adresa de citire si pot parcurge memorie locatie cu locatie
 
@(posedge clock_tb);
 
@(posedge clock_tb);
we_tb =0; // opresc scrierea
+
we_tb <= 0; // opresc scrierea
 
 
 
repeat(5) // dupa 5 cicli de ceas
 
repeat(5) // dupa 5 cicli de ceas
Linia 375: Linia 371:
 
In simularea completa, se poate observa si scrierea in registrii din memorie a datelor dorite  la adresa setata.
 
In simularea completa, se poate observa si scrierea in registrii din memorie a datelor dorite  la adresa setata.
  
Proiectul complet se poate descarca de aici: [https://wiki.dcae.pub.ro/images/9/94/Exemplu_ram64x8.zip Exemplu_ram64x8.zip]
 
  
 
==Exercitii==
 
==Exercitii==
Linia 382: Linia 377:
 
Pornind de la exemplul anterior, se doreste adaugarea unui registru la citirea datelor, astfel citirea devenind sincrona cu semnalul de ceas. In cod acest lucru se poate face foarte usor, punand operatia de citire pe ceas.
 
Pornind de la exemplul anterior, se doreste adaugarea unui registru la citirea datelor, astfel citirea devenind sincrona cu semnalul de ceas. In cod acest lucru se poate face foarte usor, punand operatia de citire pe ceas.
  
Se doreste de asemenea testarea fizica a acestei memorii si din cauza numarului limitat de switchuri/butoane al placii, se doreste micsorarea ei, astfel incat sa lucram cu o memorie de 8 locatii X 4 biti.  
+
Se doreste simularea si apoi testarea fizica a acestei memorii. Simularea noii memorii se face prin executarea a 3 scrieri la adrese diferite si citirea apoi a datelor respective. Sinteza si testarea fizica necesita micsorarea memoriei din cauza numarului limitat de switchuri/butoane al placii. Vom lucra acum cu o memorie de 8 locatii X 4 biti.  
 
Adresa va fi comandata de switch-uri si datele de intrare din butoane. Se va folosi Button[0] pentru semnalul de "we" (write enable). Afisarea datelor se va face pe leduri.
 
Adresa va fi comandata de switch-uri si datele de intrare din butoane. Se va folosi Button[0] pentru semnalul de "we" (write enable). Afisarea datelor se va face pe leduri.
 
Inainte de testare fizica se doreste si simularea noii memorii prin executarea a 3 scrieri la adrese diferite si citirea apoi a datelor respective.
 
 
  
 
===Exercitiul 2: RAM multiport citire===
 
===Exercitiul 2: RAM multiport citire===
  
Pornind de la exercitiul 1 se doreste modificarea memoriei astfel incat sa poata fi citite 2 locatii simultan si independent de adresa la care se face scrierea. Se pastreaza citirea sincrona.
+
Pornind de la exercitiul 1, se doreste modificarea memoriei astfel incat sa poata fi citite 2 locatii simultan si independent de adresa la care se face scrierea. Se pastreaza citirea sincrona.
  
 
Se obtine astfel un circuit a carui interfata este la randul ei compusa din 3 interfete (+semnal comun "clock"):  
 
Se obtine astfel un circuit a carui interfata este la randul ei compusa din 3 interfete (+semnal comun "clock"):  
Linia 426: Linia 418:
  
  
'''Bonus''': Realizati acelasi circuit si folosind o descriere structurala.
+
'''Bonus''': Realizati acelasi circuit folosind o descriere structurala.
  
 
===Exercitiul 4: Registrul serie-paralel===
 
===Exercitiul 4: Registrul serie-paralel===
Linia 437: Linia 429:
  
 
Semnalul de "en" (enable) are rolul de a controla deplasarea, care se executa prin operatia de shiftare la dreapta ">>". Daca acesta are valoarea "1", datele salvate se muta la dreapta cu o pozitie.
 
Semnalul de "en" (enable) are rolul de a controla deplasarea, care se executa prin operatia de shiftare la dreapta ">>". Daca acesta are valoarea "1", datele salvate se muta la dreapta cu o pozitie.
 +
 +
Generati forme de unda corespunzatoare pentru a testa aceast circuit (minim 3 scrieri/paralelizari).
  
 
Citirea se poate face oricand, desi doar dupa "n" pasi, va fi cu valoarea corecta.
 
Citirea se poate face oricand, desi doar dupa "n" pasi, va fi cu valoarea corecta.
 
  
 
===Exercitiul 5: Registrul de intarziere pe 8b===
 
===Exercitiul 5: Registrul de intarziere pe 8b===
  
Prin conectarea in serie a mai multor registri pe 8b astfel incat data ce iese din unul sa fie intrare pentru urmatorul, se pot construi registri de intarziere cu "x" ciclii de ceas. Cum fiecare registru salveaza datele pe ceas, se poate spune ca datele de la iesire sunt intarziate cu un ceas fata de datele de la intrare, astfel o intarziere cu "x" ciclii de ceas se obtine prin punerea in serie a "x" registrii.  
+
Prin conectarea in serie a mai multor registri pe 8b (astfel incat data ce iese din unul sa fie intrare pentru urmatorul) se pot construi registri de intarziere cu "x" cicli de ceas. Cum fiecare registru salveaza datele pe ceas, se poate spune ca datele de la iesire sunt intarziate cu un ceas fata de datele de la intrare, astfel o intarziere cu "x" cicli de ceas se obtine prin punerea in serie a "x" registri.  
  
 
Pornind de la un registru simplu pe 8b (exemplul 1), construiti structural un registru de intarziere cu 4 cicli de ceas.  
 
Pornind de la un registru simplu pe 8b (exemplul 1), construiti structural un registru de intarziere cu 4 cicli de ceas.  
  
 
Testati functionarea acestuia prin simulare. Datale introduse trebuie sa iasa defazate cu 4 cicli de ceas (adica cu 4 cicli de ceas mai tarziu).
 
Testati functionarea acestuia prin simulare. Datale introduse trebuie sa iasa defazate cu 4 cicli de ceas (adica cu 4 cicli de ceas mai tarziu).

Versiunea curentă din 22 octombrie 2024 13:57


Teorie

Acest laborator are scopul de a prezenta circuitele secventiale simple: registre si memorii RAM.


Pornind de la bistabilii prezentati/construiti in laboratorul anterior, apare notiunea de registru, acesta fiind o grupare de bistabili. Astfel in loc sa se memoreze un singur bit de informatie (bistabil), se pot memora acum numere pe mai multi biti (registru).

Din exterior, un registru este vazut astfel:

Registru exterior view.png


Semnalul de "clock" controleaza sincronizarea registrilor din tot sistemul.
Semnalul de "reset" aduce registrul la o valoare initiala (uzual 0).
Semnalul de "we" (write enable) controleaza salvarea unor date noi. Cand acesta este activ, data de pe intrarea "data_in" se salveaza in registru.
Semnalul "data_in" reprezinta datele ce se doresc a fi scrise in registru.
Semnalul "data_out" reprezinta valoarea stocata in registru.


Semnalele "data_in" si "data_out" pot fi pe oricat de multi biti se doreste, in mod uzual multipli de 8.


Pornind de la notiunea de registru (care poate fi vazut ca o memorie cu o locatie si avand "m" biti) se doreste cresterea acestei memorii astfel incat aceasta sa curpinda mai multe locatii adresabile, unde se pot stoca date. Adaugand mai multi registri in paralel, unul langa altul, si cateva circuite de tip mux/demux se poate obtine o memorie de tip RAM (Random Access Memory), cu "m" locatii de "n" biti fiecare. Aceste memorii se cheama "Random Access" deoarece permit si scriere si citire. In functie de tehologia folosita, memoriile RAM pot fi construite din bistabili sau din latch-uri.


Din exterior, o memorie RAM este vazuta astfel:

Ram mXn 1read 1write exterior view.png


Semnalele acestui circuit se pot imparti in semnale ce tin de interfata de scriere sau interfata de citire si mai apoi in semnale de date si semnale de control. Interfata sa este:

Semnalul de "clock": controleaza sincronizarea registrilor din memorie.
Semnalul "addr_read": adresa de la care se citesc datele.
Semnalul "data_read": data ce se citeste.
Semnalul de "we": semnal de control ce controleaza activarea scrierii. Scrierea are loc doar cand acest semnal este activ.
Semnalul "addr_write": adresa la care se scriu datele.
Semnalul "data_write": data ce urmeaza a fi scrisa atunci cand semnalul "we" este activ.


Observatie: Orice memorie are "m" locatii de "n" biti. Semnalele de "data_read" si "data_write" au aceeasi dimensiune, "n", numarul de biti ai fiecarei locatii. Semnalele de adresa au dimensiunea log2(m). Pentru o memorie cu 16 locatii va fi nevoie de 4 biti de adresa pentru a putea selecta orice locatie, pentru 32 locatii 5b s.a.m.d.

Observatie: In unele situatii se mai poate pune un registru suplimentar pe iesirea datelor, cu rol in sincronizare si evitarea timpilor de propagare prea lungi intre elemente de memorare (ajuta implementarea conceptului de pipeline).

Observatie: Exista mai multe variante de memorii RAM (cu registru pe iesire/fara, multiport/single port citire, adrese separate sau nu pentru citire/scriere). Cea de mai sus este o memorie cu un singur port de citire, fara registru suplimentar pe iesire, ce foloseste adrese distincte pentru citire si pentru scriere (la fel de bine se poate lucra si cu o singura adresa, comuna pentru scriere si citire).

Observatie: Memoriile RAM pot sa nu aiba reset, utilizatorul trebuie apoi sa aiba grija sa citeasca doar din locatii scrise de el anterior. In mod uzual ele nu au reset.

Exemple

Exemplul 1 : Registrul

In urmatorul exemplu se implementeaza registrul descris mai sus:


Descrierea registrului (fisierul register_8b.sv):

module register_8b
	(
		input logic clock,
		input logic reset, // activ pe "1"
		input logic we,
		input logic [7:0] data_in, 
		output logic [7:0] data_out // data_out are aceeasi dimensiune ca data_in
    );
    
always_ff @(posedge clock) // clock sincronizeaza actiunile circuitului
begin    // doar pe edge-ul pozitiv circuitul actioneaza
    if(reset == 1)
    	begin
    	data_out <= 0;
    	end
    else
    	begin
		if(we == 1) // comanda de scriere
			begin
			data_out <= data_in;
			end
		else // puteam sa omit acest else
			begin
			data_out <= data_out; // raman datele salvate anterior
			end
		end
end
    
endmodule


Un alt mod de a scrie un registru este dat mai jos, cu observatia ca aici am pus semnalul de reset activ in logica negativa:

Descrierea registrului (fisierul register_8b_v2.sv):

module register_8b_v2
	(
		input logic clock,
		input logic reset_n, // activ in "0"
		// uzual semnalel cu n in fata sau la sfarsit sunt in logica negativa: nreset, resetn
		input logic we,
		input logic [7:0] data_in,
		output logic [7:0] data_out
    );

logic [7:0] memorie_efectiva;   

assign data_out = memorie_efectiva;
    
always_ff @(posedge clock)
begin  
	if( reset_n == 0) 
		begin
		memorie_efectiva <= 0;
		end
	else
		begin
		if(we == 1)
			begin
			memorie_efectiva <= data_in;
			end
		end
end
    
endmodule


Descrierea test bench-ului registrului pe 8biti: (fisierul register_8b_tb.sv):

`timescale 1ns / 1ps

module register_8b_tb();

logic clock_tb;
logic reset_tb;
logic we_tb;
logic [7:0] data_in_tb;
logic [7:0] data_out_tb;

register_8b dut 	// varianta cu reset activ in "1"
	(
		.clock(clock_tb),
		.reset(reset_tb),
		.we(we_tb),
		.data_in(data_in_tb),
		.data_out(data_out_tb)
    );
    
initial
begin
	clock_tb = 0;
	forever 
		begin
		#5 clock_tb = ~clock_tb; // perioada totala 10 !!!
		end
end    

initial
begin
	reset_tb <= 0;
	we_tb <= 0;
	data_in_tb <= 0;
		// observatie: pana la primul reset sau prima scriere, valoarea din registru va fi necunoscuta (in simulare X)
		
		// dau reset la circuit
	@(posedge clock_tb); // astept sa treaca 1 clock cycle
	reset_tb <= 1;	
	@(posedge clock_tb);
	reset_tb <= 0;
	
	repeat(5) // dupa 5 cicli de ceas
		begin
		@(posedge clock_tb);
		end
		
		// incep sa fac scrieri
	we_tb <= 1;
	data_in_tb <= 5;
	@(posedge clock_tb);
	we_tb <= 0;
	data_in_tb <= 10;	// scrierea asta nu se face deoarece nu am write enable activ
	@(posedge clock_tb);
	data_in_tb <= 11;	// nici asta
	@(posedge clock_tb);
	data_in_tb <= 12;	// nici asta
	@(posedge clock_tb);
	we_tb = 1;
	data_in_tb <= 42;	// asta da
	@(posedge clock_tb);
	data_in_tb <= 51;	// si asta da
	@(posedge clock_tb);	
	we_tb <= 0;
	
	repeat(5) // dupa 5 cicli de ceas
		begin
		@(posedge clock_tb);
		end
	$stop();
end 

endmodule


Exemplul 2: Memorie RAM

In urmatorul exemplu se implementeaza memoria RAM descrisa mai sus, particularizata pentru 64 de locatii a cate 8b fiecare:

Descrierea memoriei RAM: (fisierul ram64x8_v1.sv):

module ram64x8_v1
	(
		input logic clock,
		//interfata de citire
			input logic [5:0] addr_read, // 64 locatii => 6 biti de adresa 
			output logic [7:0] data_read, // fiecare locatie are 8b  
		// interfata de scriere
			input logic we,
			input logic [5:0] addr_write,
			input logic [7:0] data_write
    );
    
logic [7:0] memorie_efectiva [0:63]; // memorie cu locatiile de la 0 la 63, fiecare avand 8 biti    

assign data_read = memorie_efectiva[addr_read]; // fara registru pe iesire => citire asincrona fata de clock 
    
always_ff @(posedge clock)
begin    
    if(we == 1)
    	begin
    	memorie_efectiva[addr_write] <= data_write; // scriu la locatia data de "addr_write" din memoria efectiva datele "data_write"
    	end 
end    
    
endmodule

Acesta va fi folosit si pe post de modul de "top".

Echivalent se poate folosi si sintaxa cu always ca mai jos.

Descrierea memoriei RAM: (fisierul ram64x8_v2.sv):

module ram64x8_v2 // varianta cu always combinational pe citire
	(
		input logic clock,
		//interfata de citire
			input logic [5:0] addr_read,
			output logic [7:0] data_read,
		// interfata de scriere
			input logic we,
			input logic [5:0] addr_write,
			input logic [7:0] data_write
    );
    
logic [7:0] memorie_efectiva [0:63];  

always_comb //  citire asincrona fata de clock 
begin
	data_read = memorie_efectiva[addr_read]; 
end

always_ff @(posedge clock)
begin    
    if(we == 1)
    	begin
    	memorie_efectiva[addr_write] <= data_write; // scriu la locatia data de "addr_write" din memoria efectiva datele "data_write"
    	end 
end    
    
endmodule

Descrierea test bench-ului memoriei RAM: (fisierul ram64x8_v1_tb.sv):

`timescale 1ns / 1ps

module ram64x8_tb();

logic clock_tb;
logic [5:0] addr_read_tb;
logic [7:0] data_read_tb;
logic we_tb;
logic [5:0] addr_write_tb;
logic [7:0] data_write_tb;


ram64x8_v1 dut
	(
		.clock(clock_tb),
		//interfata de citire
			.addr_read(addr_read_tb), // 64 locatii => 6 biti de adresa 
			.data_read(data_read_tb), // fiecare locatie are 8b  
		// interfata de scriere
			.we(we_tb),
			.addr_write(addr_write_tb),
			.data_write(data_write_tb)
    );


initial
begin
	clock_tb = 0;
	forever 
		begin
		#5 clock_tb = ~clock_tb; // perioada totala 10 !!!
		end
end    

initial
begin
	we_tb <= 0;
	data_write_tb <= 0;
	addr_read_tb <= 0; // citind de la o adresa nescrisa inca, iesirea e necunoscuta
	addr_write_tb <= 0;
	repeat(5) // dupa 5 cicli de ceas
		begin
		@(posedge clock_tb);
		end
		
		// incep sa fac scrieri
	we_tb <= 1;	// scriu data 5 la adresa 10
	addr_write_tb <= 10;
	data_write_tb <= 5;
	addr_read_tb <= 11; // citind de la o adresa nescrisa inca, iesirea e necunoscuta
	@(posedge clock_tb);
	we_tb <= 0;			// scrierea asta nu se face deoarece nu am write enable activ	
	addr_write_tb <= 11;
	data_write_tb <= 10;	
	@(posedge clock_tb);
	data_write_tb <= 11;	// nici asta
	@(posedge clock_tb);
	data_write_tb <= 12;	// nici asta
	@(posedge clock_tb);
	we_tb <= 1;
	addr_write_tb <= 20; // scriere ok 
	data_write_tb <= 42;	// scriu data 42 la adresa 20
	@(posedge clock_tb);
	data_write_tb <= 51;	// si asta; suprascriu datele anterioare la adresa 20.
	@(posedge clock_tb);
	addr_write_tb <= 21;
	data_write_tb <= 11;	// scriere ok
	addr_read_tb <= 20; // citesc de la adresa 20, scrisa anteior deci voi vedea date cunoscute pe iesire.
	@(posedge clock_tb);
	addr_write_tb <= 23;
	data_write_tb <= 14;	// scriere ok 
	addr_read_tb <= 21; // variez adresa de citire si pot parcurge memorie locatie cu locatie
	@(posedge clock_tb);	
	we_tb <= 0;	// opresc scrierea
	
	repeat(5) // dupa 5 cicli de ceas
		begin
		@(posedge clock_tb);
		end
	$stop();
end 	

endmodule

Ram64X8 waveform.png

In forma de unda de mai sus sunt marcate momentele de timp cand au loc scrieri in memorie (cand am write enable activ si valori cunoscute pentru data si adresa de scriere). Se salveaza valorile imediat la stanga frontului de ceas.

In simularea completa, se poate observa si scrierea in registrii din memorie a datelor dorite la adresa setata.


Exercitii

Exercitiul 1: RAM cu registru la iesire

Pornind de la exemplul anterior, se doreste adaugarea unui registru la citirea datelor, astfel citirea devenind sincrona cu semnalul de ceas. In cod acest lucru se poate face foarte usor, punand operatia de citire pe ceas.

Se doreste simularea si apoi testarea fizica a acestei memorii. Simularea noii memorii se face prin executarea a 3 scrieri la adrese diferite si citirea apoi a datelor respective. Sinteza si testarea fizica necesita micsorarea memoriei din cauza numarului limitat de switchuri/butoane al placii. Vom lucra acum cu o memorie de 8 locatii X 4 biti. Adresa va fi comandata de switch-uri si datele de intrare din butoane. Se va folosi Button[0] pentru semnalul de "we" (write enable). Afisarea datelor se va face pe leduri.

Exercitiul 2: RAM multiport citire

Pornind de la exercitiul 1, se doreste modificarea memoriei astfel incat sa poata fi citite 2 locatii simultan si independent de adresa la care se face scrierea. Se pastreaza citirea sincrona.

Se obtine astfel un circuit a carui interfata este la randul ei compusa din 3 interfete (+semnal comun "clock"):

- interfata de scriere
- interfata de citire 0
- interfata de citire 1


Interfata circuitului arata ca in figura de mai jos:

Ram 64X8 2read 1write exterior view.png


Generati forme de unda corespunzatoare pentru a testa aceasta memorie.

Observatie: Blocul de registri (register file) din interiorul procesoarelor (vedeti AMP) poate fi o astfel de memorie RAM. Are 2 adrese pentru citirea celor 2 operanzi care intra in ALU si o adresa pentru rezultatul calculului ce este salvat.

Exercitiul 3: Registrul paralel-serie

Descrieti comportamental un registru paralel-serie. Acesta are rolul de a salva datele de la intrare pe "n" biti (aici 8) si apoi de a le scoate la iesire bit cu bit.

Interfata acestuia este data in desenul de mai jos.

Registru paralel serie.png


Semnalul de "en" (enable) are rolul de a controla deplasarea, care se executa prin operatia de shiftare la dreapta ">>". Daca acesta are valoarea "1", datele salvate se muta la dreapta cu o pozitie.
Semnalul de write_en/start/save are rolul de a salva cei 8 biti care urmeaza a fi serializati.


Generati forme de unda corespunzatoare pentru a testa aceast circuit (minim 3 scrieri/serializari).


Bonus: Realizati acelasi circuit folosind o descriere structurala.

Exercitiul 4: Registrul serie-paralel

Descrieti comportamental un registru serie-paralel. Acesta are rolul de a salva date introduse bit cu bit ca apoi sa fie scoase cate "n" biti deodata.

Interfata acestuia este data in desenul de mai jos.

Registru serie paralel.png

Semnalul de "en" (enable) are rolul de a controla deplasarea, care se executa prin operatia de shiftare la dreapta ">>". Daca acesta are valoarea "1", datele salvate se muta la dreapta cu o pozitie.

Generati forme de unda corespunzatoare pentru a testa aceast circuit (minim 3 scrieri/paralelizari).

Citirea se poate face oricand, desi doar dupa "n" pasi, va fi cu valoarea corecta.

Exercitiul 5: Registrul de intarziere pe 8b

Prin conectarea in serie a mai multor registri pe 8b (astfel incat data ce iese din unul sa fie intrare pentru urmatorul) se pot construi registri de intarziere cu "x" cicli de ceas. Cum fiecare registru salveaza datele pe ceas, se poate spune ca datele de la iesire sunt intarziate cu un ceas fata de datele de la intrare, astfel o intarziere cu "x" cicli de ceas se obtine prin punerea in serie a "x" registri.

Pornind de la un registru simplu pe 8b (exemplul 1), construiti structural un registru de intarziere cu 4 cicli de ceas.

Testati functionarea acestuia prin simulare. Datale introduse trebuie sa iasa defazate cu 4 cicli de ceas (adica cu 4 cicli de ceas mai tarziu).