CID Aplicatii 8
1. Registrul
Pentru a stoca date cu o dimensiune mai mare de un bit, putem folosi registre. Un registru este o colectie de bistabili ce lucreaza in paralel, folosind acelasi semnal de ceas. Cu alte cuvinte, vom avea cate un bistabil pentru fiecare bit ce trebuie stocat.
Structura registrelor, ca si cea a bistabilelor, poate diferi usor in functie de ce semnale de control dorim sa accepte. In urmatorul exemplu, vom avea un registru ce accepta atat un semnal de reset sincron prin care continutul acestuia va deveni 0, cat si un semnal de write enable, prin care dorim sa controlam procesul de scriere. Asadar, vom avea o conditie in plus: pentru a efectua o scriere in momentul aparitiei evenimentului de front crescator de ceas, va fi nevoie ca semnalul de reset sa fie inactiv si ca semnalul write_enable sa fie activ. Vom considera in exemplul nostru semnalul reset activ in 0 si semnalul write_enable activ in 1.
Semnalul de activare a scrierii poate bloca scrierea in doua moduri: fie este folosit pentru a bloca ceasul atunci cand este activ (clock gating), fie este folosit impreuna cu un multiplexor pentru a selecta intre data de intrare si cea memorata anterior pentru a face scrierea curenta. In urmatorul exemplu vom folosi a doua implementare, prezentata in schema de mai jos.
Implementarea registrului cu reset si write_enable
module register
#(parameter WIDTH = 8)
(
input [WIDTH-1:0] data_in,
input clock,
input reset,
input we,
output reg [WIDTH-1:0] data_out
);
always@(posedge clock) begin
if(reset == 0)
data_out <= 0;
else if(we == 1)
data_out <= data_in;
else
data_out <= data_out;
end
endmodule
Implementarea modulului de test
`timescale 1ns/1ps
module register_TB();
parameter WIDTH_T = 8;
reg [WIDTH_T-1:0] data_in_t;
reg clock_t, reset_t, we_t;
wire [WIDTH_T-1:0] data_out_t;
initial begin
reset_t = 0;
#2 reset_t = 1;
we_t = 1;
data_in_t = 8'h01;
#2 data_in_t = 8'h02;
#2 data_in_t = 8'h03;
we_t = 0;
#5 $stop();
end
initial begin
clock_t = 0;
forever #1 clock_t = !clock_t;
end
register #(.WIDTH(WIDTH_T)) DUT(
.data_in(data_in_t),
.reset(reset_t),
.clock(clock_t),
.we(we_t),
.data_out(data_out_t)
);
endmodule
Observatie: Parametrul WIDTH permite generalizarea dimensiunii registrului. Folosind aceeasi descriere de mai sus, dar folosind o alta valoare a parametrului la instantiere, putem obtine registre de orice dimensiune.
2. Memoria RAM
O memorie RAM este o colectie de celule de memorie organizate sub forma unei matrice. In general, partea de memorare efectiva a RAM este implementata cu latch-uri, datele si adresele fiind distribuite cu ajutorul altor elemente combinationale: multiplexoare, demultiplexoare, etc.
Indiferent de modul de implementare al acesteia, comportamentul unei memorii RAM este urmatorul: scrierea se va face mereu pe frontul crescator al ceasului, folosind pentru identificarea locatiei de memorie al carui continut va fi modificat, o adresa. Asadar, la fiecare front crescator de ceas, daca semnalul write_enable este activ, se va scrie data de intrare la adresa specificata pe portul de adrese. Pentru citire, avem doua posibilitati: daca memoria RAM este cu citire sincrona, la fiecare front crescator al ceasului pentru care read_enable este activ, vom citi continutul locatiei de memorie a carei adresa este specificata pe portul de adrese, iar daca citirea este asincrona, iesirea va fi modificata imediat, cu continutul locatiei de memorie a carei adresa este specificata pe portul de adrese.
Exista, asadar, multe modalitati de a implementa o memorie RAM. Noi ne vom ocupa in acest exemplu de o descriere comportamentala a unei memorii RAM cu porturi separate pentru datele si adresele de scriere si citire. Citirea se va face sincron.
Implementarea memoriei RAM dual-port cu porturi de scriere si citire sincrone
module RAMDP
#( parameter DATA_W = 8, ADDR_W = 5)
(
input clock,
input read_enable,
input write_enable,
input [ADDR_W-1:0] addr_read,
input [ADDR_W-1:0] addr_write,
input [DATA_W-1:0] data_in,
output reg [DATA_W-1:0] data_out
);
reg [DATA_W-1:0] memory [0:2**ADDR_W-1];
always@(posedge clock) begin
if(write_enable)
memory[addr_write] <= data_in;
if(read_enable)
data_out <= memory[addr_read];
end
endmodule
Implementarea modulului de test
`timescale 1ns/1ps
module RAMDP_TB();
parameter DATA_W_T = 8;
parameter ADDR_W_T = 5;
reg clock_t, read_enable_t, write_enable_t;
reg [ADDR_W_T-1:0] addr_read_t, addr_write_t;
reg [DATA_W_T-1:0] data_in_t;
wire [DATA_W_T-1:0] data_out_t;
integer idx;
initial begin
read_enable_t = 0;
write_enable_t = 1;
for(idx = 0; idx < 2**ADDR_W_T; idx = idx + 1) begin
#2;
data_in_t = idx;
addr_write_t = idx;
end
#2 read_enable_t = 1;
write_enable_t = 0;
for(idx = 0; idx < 2**ADDR_W_T; idx = idx + 1) begin
#2;
addr_read_t = idx;
end
#10 $stop();
end
initial begin
clock_t = 0;
forever #1 clock_t = !clock_t;
end
RAMDP #(.DATA_W(DATA_W_T), .ADDR_W(ADDR_W_T)) DUT (
.clock(clock_t),
.read_enable(read_enable_t),
.write_enable(write_enable_t),
.addr_read(addr_read_t),
.addr_write(addr_write_t),
.data_in(data_in_t),
.data_out(data_out_t)
);
endmodule
Observatie: Parametrii DATA_W si ADDR_W permit generalizarea dimensiunii memoriei RAM. Numarul de locatii de memorie va fi 2ADDR_W
3. Registrul paralel-serie
Registrul Paralel-Serie incarca datele in paralel (ca un registru normal) si apoi, prin operatia de deplasare, pune la iesire cate un bit la fiecare ciclu de ceas, incepand cu bitul 0. Aceasta operatie poate fi folosita, de exemplu, in transmiterea datelor folosind un protocol de tip serial. Datele sunt utilizate in interiorul unui modul in paralel, iar apoi acestea trebuie transmise folosind un singur fir si nu o magistrala de n biti.
De exemplu, avem doua numere pe 8 biti ce trebuie adunate sau prelucrate intr-un anumit mod, iar rezultatul trebuie apoi transmis pe un singur fir unui alt modul.
Daca acest modul este implementat si pe FPGA, putem folosi butoanele pentru a controla semnalele de comanda, pe folosi logica negata (semnalele vor fi active in 0 si inactive in 1). Vom avea nevoie aici de 3 semnale de comanda: reset, pentru aducerea la 0 a continutului bistabilelor ce formeaza registrul, we_n, ce controleaza scrierea registrului (write enable negat) si shift, semnal ce controleaza pornirea deplasarii datelor prin registru catre iesire.
Implementarea registrului paralel-serie
module ParalelSerial
#(parameter WIDTH = 8)
(
input [WIDTH-1:0] data_in,
input clock,
input reset,
input we_n,
input shift_n,
output data_out
);
reg [WIDTH-1:0] shiftreg;
wire [WIDTH-1:0] shiftreg_next;
always@(posedge clock) begin
if(reset == 0)
shiftreg <= 0;
else if(shift_n == 1 && we_n == 0)
shiftreg <= data_in;
else if(shift_n == 0)
shiftreg <= shiftreg_next;
end
assign shiftreg_next = {1'b1, shiftreg[WIDTH-1:1]};
assign data_out = shiftreg[0];
endmodule
Implementarea modulului de test
`timescale 1ns/1ps
module ParalelSerial_TB();
parameter WIDTH_T = 8;
reg [WIDTH_T-1:0] data_in_t;
reg clock_t, reset_t, we_n_t, shift_n_t;
wire data_out_t;
initial begin
reset_t = 0;
we_n_t = 1;
shift_n_t = 1;
#2 reset_t = 1;
we_n_t = 0;
shift_n_t = 1;
data_in_t = 8'hAA;
#2 we_n_t = 1;
#2 shift_n_t = 0;
#20 $stop();
end
initial begin
clock_t = 0;
forever #1 clock_t = !clock_t;
end
ParalelSerial #(.WIDTH(WIDTH_T)) DUT(
.data_in(data_in_t),
.reset(reset_t),
.clock(clock_t),
.we_n(we_n_t),
.shift_n(shift_n_t),
.data_out(data_out_t)
);
endmodule