Cómo enviar datos DDR a 1 registro

Tengo una entrada que proporciona datos DDR, necesito capturarlos y enviarlos desde el FPGA en un registro (12 bits).

¿Cómo puedo hacerlo?

Lo que hice por ahora es capturar los datos de entrada con bloque siempre en el borde ascendente y almacenarlos en reg1. capture los datos de entrada con otro bloque siempre en el borde descendente y guárdelos en reg2.

Ahora tengo datos del flanco ascendente en reg1 [12 bits] y el flanco descendente en reg2 [12 bits], ¿cómo puedo enviar estos datos ahora a otro registro de salida de 12 bits?

mi código:

module ADC(
    input rstn,
    input clk,                       
    input [11:0] data_in, 
    output [11:0] data_out
    );

    reg rst;
    reg [11:0] posedge_data;
    reg [11:0] negedge_data;


    always @(posedge clk or negedge rstn)
    begin
        rst <= ~rstn;
    end

    always @(posedge clk, posedge rst)
    begin
        if (rst) begin
            posedge_data   <= 12'b100000000000;
        end else begin
            posedge_data   <= data_in;
        end
    end

    always @(negedge clk, posedge rst)
    begin
        if (rst) begin
            negedge_data   <= 12'b100000000000;
        end else begin
            negedge_data   <= data_in;
        end
    end

endmodule

asumiendo que duplicar la frecuencia clk con PLL no es una opción. Estoy usando Lattice ECP3 FPGA. Si me pueden ayudar con las IP de Lattice y cómo conectarlas, eso también ayudaría... pero creo que hay una forma posible sin IP, ¿no?

gracias.

Editar:

después del comentario de Oldfart

si cambio data_out a 24 bits y agrego esta línea de código:

assign data_out = {posedge_data,negedge_data};

¿Lo hace un buen diseño DDR? o hay una mejor manera?

No me queda claro lo que estás tratando de lograr. Supongamos que en algún momento posedge_data = 12'h123 y negedge_data=12'hABC. ¿Qué valor de datos de 12 bits desearía como salida? ¿Qué flanco de reloj debe hacer que los datos de salida se enganchen?
mi objetivo es generar los datos en posedge_data y neg_edge en la salida en el borde descendente y ascendente del reloj ... hacer que los datos sean DDR. significa que en 1 ciclo de reloj veré negedge_data y posedge_data en el registro de salida
El EPC3 tiene bloques DDR dedicados en sus entradas para este mismo propósito.
@TomCarpenter Hola, gracias por el comentario, intenté usar DDR, más específicamente, intenté usar: GDDRX1_RX.SCLK.PLL.Aligned que creé desde IPexpress en Diamond -> DDR_GENERIC -> y especifiqué mis necesidades. Cuando agrego el archivo verilog al proyecto y agrego la instanciación a mi nivel superior, el proyecto se sintetizó, pero después de eso, ¡REVELAR piensa que la IP ddr es mi diseño de nivel superior! y no puedo agregar señales a REVEAL desde mi archivo de nivel superior, cuando estoy cambiando el nivel superior a mi archivo superior está causando problemas. ¿Sabes por qué?

Respuestas (2)

El procedimiento normal es utilizar el doble de ancho de datos dentro de un receptor DDR.

Entonces, con un bus DDR de 12 bits, debe enviar los datos a un puerto/registro de 24 bits de ancho.

La alternativa es usar el doble de la frecuencia del reloj (o más) en el interior, pero como dijiste, a menudo no es una opción.

NO seguiría procesando los datos internamente en flancos de reloj alternativos. Eso es mal diseño.

¿No puedo transferir los datos de borde neg/pos del registro de 2 a un registro de 24 bits? ¿O debería usar el receptor DDR dentro de la FPGA?
mira mi edición. gracias
Esa es la manera de hacerlo. Los datos que llegan negedge tienen solo medio borde de reloj hasta el siguiente registro, por lo que normalmente intentaría NO agregar ninguna lógica en esa etapa.

Modifiqué su código y agregué sincronizadores de reinicio y sincronizadores normales en caso de que los necesite, también puede agregarlos ... desde las conexiones de su puerto asumió que su bus de datos de entrada de 12 bits con velocidad DDR, por lo que la salida será 12 * 2 = 24 bits y necesita para transmitirlo de manera lineal, es decir, data_out [1:0] correspondiente a los datos de flanco descendente y los datos de flanco ascendente, respectivamente.

puede usar las macros predeterminadas de Lattice IDDR * para este caso de todos modos, también puede recodificar, he codificado en el sistema verilog en caso de que su herramienta no sea compatible con el SV, use los tipos de datos de verilog respectivamente en el código a continuación. Como he trabajado estrechamente con las herramientas xilinx, he usado algunas directivas de herramientas como (* Dont_touch y ASYNC_REG*) para sincronizaciones para colocarlas muy útiles para la condición de metaestabilidad. Utilice dichas directivas en su código modificándolas de acuerdo con las directivas de la herramienta de celosía.

module ADC(
    input         rstn,
    input         clk,                       
    input  [11:0] data_in, 
    output [23:0] data_out
);

    reg rst;
    reg [11:0] posedge_data;
    reg [11:0] negedge_data;

    always @(posedge clk or negedge rstn)  // expecting reset is synchronous to this clock domain if not use 
        rst <= ~rstn;                      // reset synchronizer in ur code to remove metastability during reset removal and recovery

    always @(posedge clk, posedge rst)
        if (rst) posedge_data   <= 12'b1000_0000_0000;
        else     posedge_data   <= data_in; // Assuming data_in in synchronous to the design else u need to atleast 2-FF stage synchronizer

    always @(negedge clk, posedge rst)
        if (rst) negedge_data   <= 12'b100000000000;
        else     negedge_data   <= data_in;// Assuming data_in in synchronous to the design else u need to atleast 2-FF stage synchronizer

   for (int i=0;i<12;i++)
    assign data_out[i+:2]={negedge_data[i],posedge_data[i]};

endmodule

module reset_sync #(
    parameter TCQ                   = 100,
    parameter NUM_OF_STAGES         = 2,
    parameter  IN_ACTIVE_HIGH_RESET = 1,
    parameter OUT_ACTIVE_HIGH_RESET = 1
)(
    input        dest_clk, async_rst,
    output logic sync_rst_dest_clk
    output logic async_rst_out;
);

   (* DONT_TOUCH = "TRUE" , ASYNC_REG = "TRUE" *) logic [NUM_OF_STAGES-1:0] sync = 0;
   logic sync_rst_out;
   reg rst_out = 0;

   if(IN_ACTIVE_HIGH_RESET)begin: ASYNC_RESET
     always_ff @(posedge dest_clk or posedge async_rst)
      if (async_rst) sync <= #TCQ OUT_ACTIVE_HIGH_RESET ? '1 : '0;
      else           sync <= #TCQ OUT_ACTIVE_HIGH_RESET ? {sync[NUM_OF_STAGES-2:0],1'b0}
                                                        : {sync[NUM_OF_STAGES-2:0],1'b1} ;
   end else begin: ASYNC_RESET
     always_ff @(posedge dest_clk or negedge async_rst)
      if(~async_rst) sync <= #TCQ OUT_ACTIVE_HIGH_RESET ? '1 : '0;
      else           sync <= #TCQ OUT_ACTIVE_HIGH_RESET ? {sync[NUM_OF_STAGES-2:0],1'b0}
                                                        : {sync[NUM_OF_STAGES-2:0],1'b1} ;
    end

   assign async_rst_out = sync[NUM_OF_STAGES-1];
   sync # (
        .SYNC_MTBF (2)
       ,.WIDTH     (1)
    ) u_dest_rst_sync(
        .clk        (dest_clk)
       ,.data_in    (async_rst_out)
       ,.data_out   (sync_rst_out)
    );

   assign sync_rst_dest_clk = sync_rst_out;

endmodule

module sync #(
    parameter SYNC_MTBF = 2
   ,parameter WIDTH     = 1
   ,parameter TCQ       = 100
)(
    input              clk
   ,input  [WIDTH-1:0] data_in
   ,output [WIDTH-1:0] data_out
);

  for (genvar wd=0; wd<WIDTH; wd=wd+1) begin : SYNC
    (* dont_touch = "true" *) (* ASYNC_REG = "TRUE" *) reg [SYNC_MTBF-1:0] sync_reg;

    always @(posedge clk)
      sync_reg <= #TCQ {sync_reg[0+:SYNC_MTBF-1], data_in};

    assign data_out[wd] = sync_reg[SYNC_MTBF-1];
  end

endmodule