CID Aplicatii 11

De la WikiLabs

1. Memoria FIFO

Memoria FIFO este o structura de memorare ce respecta regula First-In First-Out. Asadar, din aceasta memorie se va citi mereu cea mai veche data scrisa. Structura interna a acestui circuit este prezentata in figura urmatoare.

FIFO.png

Dupa cum se poate observa, nu mai este nevoie de magistrale de adrese pentru scriere si pentru citire. Acestea sunt calculate intern cu ajutorul unor numaratoare cu enable (WRITE_PTR si READ_PTR). Pentru usurinta intelegerii, putem privi FIFO ca o memorie circulara, cu adrese de citire si scriere ce se „urmaresc” una pe cealalta. Memoria tinde spre „umplere” prin scrieri si spre „golire” prin citiri:

Buffer circular.png

FIFO descris mai sus are trei semnale de feedback:

  • fifo_counter, care ne spune cate locatii de memorie din cele totale sunt ocupate.
  • empty, activ atunci cand memoria este goala (0 locatii de memorie ocupate)
  • full, activ atunci cand toate locatiile sunt ocupate. Citirea este blocata din momentul in care full devine activ pana se elibereaza locatii prin citire.

Semnalele write_enable si read_enable care sunt transmise modulului din exterior nu pot ajunge direct la memorie deoarece trebuie impiedicate scrierile sau citirile eronate: o citire eronata apare atunci cand se incearca citirea dintr-o memorie goala, iar o scriere eronata apare atunci cand se incearca scrierea intr-o memorie complet ocupata. De aceea, semnalul real de comanda a scrierii (wr_en_mem) este activ doar daca se declanseaza o scriere si memoria nu este plina, iar semnalul real de citire (rd_en_mem) este activ doar daca se declanseaza o citire dintr-o memorie care nu este goala.

Principalele blocuri ale memoriei FIFO sunt:

  • RAMDP: memorie dual-port, cu citire sincrona. Acesta este elementul de memorare efectiva.
  • WRITE_PTR: numarator ce incrementeaza adresa de scriere, daca apare o scriere valida (wr_en_mem este 1).
  • READ_PTR: numarator ce incrementeaza adresa de citire, daca apare o citire valida (rd_en_mem este 1).
  • FIFO_COUNTER: numarator ce contorizeaza locatiile ocupate: se incrementeaza daca are loc o scriere fara citire (wr_en_mem este 1 si rd_en_mem este 0) si se decrementeaza daca are loc o citire fara scriere (rd_en_mem este 1 si wr_en_mem este 0).
  • CONTROL: modul combinational care genereaza semnalele de control: empty, full, wr_en_mem si rd_en_mem, cu functionalitatile descrise anterior.


Implementarea memoriei RAMDP

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 memoriei FIFO

module FIFO
#( parameter DATA_W = 8, ADDR_W = 5)
( 
    input clock, reset, // semnal de ceas si reset
    input write_enable, // comanda de scriere
    input read_enable, // comanda de citire
    input [DATA_W-1 : 0] data_in, // date de intrare
    output [DATA_W-1 : 0] data_out, // date de iesire
    output empty, // semnal activ cand memoria este goala
    output full, // semnal activ cand memoria este plina
    output reg [ADDR_W:0] fifo_counter 	//nr locatii folosite
 );
 wire rd_en_mem, wr_en_mem;
 reg [ADDR_W-1:0] write_ptr_reg, read_ptr_reg;
 
 //WRITE POINTER	
 always@(posedge clock) begin
    if(reset == 0)
        write_ptr_reg <= 0;
    else if (wr_en_mem == 1)
        write_ptr_reg <= write_ptr_reg + 1;
 end
 
 //READ POINTER	
 always@(posedge clock) begin
    if(reset == 0)
        read_ptr_reg <= 0;
    else if (rd_en_mem == 1)
        read_ptr_reg <= read_ptr_reg + 1;
 end
 
 //FIFO COUNTER
 always@(posedge clock) begin
    if(reset == 0)
        fifo_counter <= 0;
    else if (rd_en_mem && !wr_en_mem)
        fifo_counter <= fifo_counter - 1;
    else if (!rd_en_mem && wr_en_mem)
        fifo_counter <= fifo_counter + 1;	
 end
 
 //READ_ENABLE AND WRITE_ENABLE
 assign wr_en_mem = write_enable & ~full;
 assign rd_en_mem = read_enable & ~empty;
 
 //FULL AND EMPTY
 assign full  = (fifo_counter == 2**ADDR_W);
 assign empty = (fifo_counter == 0);

 RAMDP #(.DATA_W(DATA_W), .ADDR_W(ADDR_W)) MEM(
    .clock(clock),
    .read_enable(rd_en_mem),
    .write_enable(wr_en_mem),
    .addr_read(read_ptr_reg),
    .addr_write(write_ptr_reg),
    .data_in(data_in),
    .data_out(data_out)
 );
 
 endmodule

Implementarea unui test bench pentru memoria FIFO

`timescale 1ns/1ps

module FIFO_TB;

parameter DATA_W_T = 8;
parameter ADDR_W_T = 5;

reg clock_t, reset_t;
reg write_enable_t, read_enable_t;
reg [DATA_W_T-1 : 0] data_in_t; 
wire [DATA_W_T-1 : 0] data_out_t;
wire empty_t, full_t;
wire [ADDR_W_T:0] fifo_counter_t;

integer i;
integer j;

initial begin
       reset_t = 0;
    #2 reset_t = 1;
       read_enable_t = 0;
       for(j = 0; j<= 2**ADDR_W_T; j = j + 1) begin //umplem complet FIFO. La final, semnalul full va deveni HIGH
           #2;
           data_in_t = j;
           write_enable_t = 1;
       end
    #2 read_enable_t = 1;
       write_enable_t = 0; //pornim citirea pana cand FIFO se va goli complet, iar semnalul empty va deveni HIGH
       #80 $stop();

end

initial begin
    clock_t = 0;
    forever #1 clock_t = ~clock_t;
end

FIFO #(.DATA_W(DATA_W_T), .ADDR_W(ADDR_W_T)) DUT( 
    .clock(clock_t), 
    .reset(reset_t), 
    .write_enable(write_enable_t), 
    .read_enable(read_enable_t), 
    .data_in(data_in_t), 
    .data_out(data_out_t), 
    .empty(empty_t), 
    .full(full_t), 
    .fifo_counter(fifo_counter_t) 	

 );

endmodule

2. Stiva

Stiva este o structura de memorie care se supune regulii LIFO (Last-In First-Out). Asadar, data citita din stiva va fi ultima data introdusa.

Stiva.png

Introducerea elementelor in stiva se face prin operatia push, iar extragerea lor prin pop. Asadar, vom avea doua semnale care vor comanda scrierea, respectiv citirea: push si pop. Considerand structura stivei, cele doua operatii nu vor avea loc in acelasi timp, spre deosebire de FIFO. Umplerea si golirea completa a stivei vor fi semnalizate cu ajutorul semnalelor full, respectiv empty.

Deoarece scrierea si citirea sunt ambele legate de varful stivei, este nevoie de o singura adresa pentru acestea: adresa varfului stivei (ptr_reg). Aceasta se va incrementa atunci cand un element va fi introdus in varful stivei si se va descrementa atunci cand un element va fi extras din stiva. Atat scrierea cat si citirea sunt sincrone si au loc atunci cand se comanda o operatie de push si stiva nu este plina, respectiv atunci cand se comanda o operatie de pop si stiva nu este goala.


Implementarea modulului Stack

module STACK
#( parameter DATA_W = 8, ADDR_W = 5)
( 
    input clock, reset, // semnal de ceas si reset
    input push, // comanda de scriere
    input pop, // comanda de citire
    input [DATA_W-1 : 0] data_in, // date de intrare
    output reg [DATA_W-1 : 0] data_out, // date de iesire
    output empty, // semnal activ cand memoria este goala
    output full // semnal activ cand memoria este plina
 );
 
 wire rd_en_mem, wr_en_mem;
 reg [ADDR_W:0] ptr_reg;
 
 reg [DATA_W-1:0] stack [0:2**ADDR_W - 1];
 
 //WRITE POINTER	
 always@(posedge clock) begin
    if(reset == 0)
        ptr_reg <= 0;
    else if (wr_en_mem == 1)
        ptr_reg <= ptr_reg + 1;
    else if (rd_en_mem == 1) 
        ptr_reg <= ptr_reg - 1;	   
 end
 
 //READ_ENABLE AND WRITE_ENABLE
 assign wr_en_mem = push & ~full;
 assign rd_en_mem = pop & ~empty;
 
 //FULL AND EMPTY
 assign full  = (ptr_reg == 2**ADDR_W);
 assign empty = (ptr_reg == 0);

 always@(posedge clock) begin
    if (wr_en_mem == 1)
        stack[ptr_reg] <= data_in;
    if (rd_en_mem == 1) 
        data_out <= stack[ptr_reg-1];	   
 end
 
 endmodule

Implementarea unui test bench pentru modulul Stack

`timescale 1ns/1ps

module STACK_TB;

parameter DATA_W_T = 8;
parameter ADDR_W_T = 5;

reg clock_t, reset_t;
reg push_t, pop_t;
reg [DATA_W_T-1 : 0] data_in_t; 
wire [DATA_W_T-1 : 0] data_out_t;
wire empty_t, full_t;

integer i;
integer j;

initial begin
    reset_t = 0;
    push_t = 0;
    pop_t = 0;
    #2;	
    //umplerea completa a stivei
    reset_t = 1;
    push_t = 1;
    for(j = 0; j<= 2**ADDR_W_T; j = j + 1) begin
        data_in_t = j;
        #2;
    end
    //golirea completa a stivei
    push_t = 0;
    pop_t = 1;
    #80;
    //intraoducerea a 5 elemente in stiva
    push_t = 1;
    pop_t = 0;
    for(j = 0; j<= 4; j = j + 1) begin
        data_in_t = j*2;
        #2;
    end
    //extragerea celor 5 elemente din stiva
    push_t = 0;
    pop_t = 1;
    #80;		    
    $stop();
end

initial begin
    clock_t = 0;
    forever #1 clock_t = ~clock_t;
end

STACK #(.DATA_W(DATA_W_T), .ADDR_W(ADDR_W_T)) DUT( 
    .clock(clock_t), 
    .reset(reset_t), 
    .push(push_t), 
    .pop(pop_t), 
    .data_in(data_in_t), 
    .data_out(data_out_t), 
    .empty(empty_t), 
    .full(full_t)
 );

endmodule