CID Aplicatii 8

De la WikiLabs
Jump to navigationJump to search

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.

Register.png

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.

RAM.png

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.

Paralel-serie.png

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