Diferență între revizuiri ale paginii „CID aplicatii 8 : Registre si memorii RAM”
(Nu s-au afișat 9 versiuni intermediare efectuate de același utilizator) | |||
Linia 24: | Linia 24: | ||
− | Pornind de la notiunea de registru | + | 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 | + | 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 | + | :Semnalul "addr_read": adresa de la care se citesc datele. |
− | :Semnalul | + | :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 | + | :Semnalul "addr_write": adresa la care se scriu datele. |
− | :Semnalul | + | :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 | + | '''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 | + | '''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. | + | '''Descrierea registrului (fisierul register_8b.sv):''' |
<syntaxhighlight lang="Verilog"> | <syntaxhighlight lang="Verilog"> | ||
module register_8b | module register_8b | ||
( | ( | ||
− | input | + | input logic clock, |
− | input | + | input logic reset, // activ pe "1" |
− | input | + | input logic we, |
− | input | + | input logic [7:0] data_in, |
− | output | + | 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 | 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. | + | '''Descrierea registrului (fisierul register_8b_v2.sv):''' |
<syntaxhighlight lang="Verilog"> | <syntaxhighlight lang="Verilog"> | ||
module register_8b_v2 | module register_8b_v2 | ||
( | ( | ||
− | input | + | input logic clock, |
− | input | + | 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 | + | input logic we, |
− | input | + | input logic [7:0] data_in, |
− | output | + | output logic [7:0] data_out |
); | ); | ||
− | + | logic [7:0] memorie_efectiva; | |
assign data_out = memorie_efectiva; | assign data_out = memorie_efectiva; | ||
− | + | 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. | + | '''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(); | ||
− | + | 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" | 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> | ||
− | |||
− | |||
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. | + | '''Descrierea memoriei RAM: (fisierul ram64x8_v1.sv):''' |
<syntaxhighlight lang="Verilog"> | <syntaxhighlight lang="Verilog"> | ||
module ram64x8_v1 | module ram64x8_v1 | ||
( | ( | ||
− | input | + | input logic clock, |
//interfata de citire | //interfata de citire | ||
− | input | + | input logic [5:0] addr_read, // 64 locatii => 6 biti de adresa |
− | output | + | output logic [7:0] data_read, // fiecare locatie are 8b |
// interfata de scriere | // interfata de scriere | ||
− | input | + | input logic we, |
− | input | + | input logic [5:0] addr_write, |
− | input | + | 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 | assign data_read = memorie_efectiva[addr_read]; // fara registru pe iesire => citire asincrona fata de 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. | + | '''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 | + | input logic clock, |
//interfata de citire | //interfata de citire | ||
− | input | + | input logic [5:0] addr_read, |
− | output | + | output logic [7:0] data_read, |
// interfata de scriere | // interfata de scriere | ||
− | input | + | input logic we, |
− | input | + | input logic [5:0] addr_write, |
− | input | + | input logic [7:0] data_write |
); | ); | ||
− | + | logic [7:0] memorie_efectiva [0:63]; | |
− | + | always_comb // citire asincrona fata de clock | |
begin | begin | ||
data_read = memorie_efectiva[addr_read]; | data_read = memorie_efectiva[addr_read]; | ||
end | end | ||
− | + | 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. | + | '''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(); | ||
− | + | 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; | |
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 369: | Linia 365: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | In | + | [[Fișier:Ram64X8_waveform.png|1000px]] |
+ | |||
+ | 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. | |
Linia 379: | 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 | + | 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. | ||
− | |||
− | |||
− | |||
===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 402: | Linia 397: | ||
[[Fișier:Ram_64X8_2read_1write_exterior_view.png | 400px]] | [[Fișier:Ram_64X8_2read_1write_exterior_view.png | 400px]] | ||
+ | |||
+ | 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. | '''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=== | ===Exercitiul 3: Registrul paralel-serie=== | ||
Linia 419: | Linia 415: | ||
− | + | 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=== | ===Exercitiul 4: Registrul serie-paralel=== | ||
Linia 431: | 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 | + | 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:
- 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:
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
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:
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.
- 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.
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).