Bloqueo vs asignaciones no bloqueadas

3

Me ha costado mucho entender la diferencia entre bloquear y no bloquear las asignaciones en Verilog. Quiero decir, entiendo la diferencia conceptual entre los dos, pero estoy realmente perdido cuando se trata de la implementación.

Me referí a varias fuentes, incluida esta pregunta , pero todas las explicaciones parecen explicar la diferencia en términos de código (lo que sucede con la secuencia de ejecución de las líneas cuando se usa el bloqueo frente al no bloqueo). Mi pregunta es un poco diferente.

Al escribir código verilog (ya que lo estoy escribiendo para sintetizarlo en un FPGA), siempre trato de visualizar cómo se verá el circuito sintetizado, y ahí es donde comienza el problema:

1) No puedo entender cómo el cambio de las asignaciones de bloqueo a no bloqueo podría alterar mi circuito sintetizado. Por ejemplo:

    always @* begin

        number_of_incoming_data_bytes_next <= number_of_incoming_data_bytes_reg;
        generate_input_fifo_push_pulse_next <= generate_input_fifo_push_pulse;

        if(state_reg == idle) begin
            // mealey outputs
            count_next = 8'b0;

            if((rx_done_tick) && (rx_data_out == START_BYTE)) begin
                state_next = read_incoming_data_length;
                end else begin
                    state_next = idle;
                end

        end else if(state_reg == read_incoming_data_length) begin
            // mealey outputs
            count_next = 8'b0;

            if(rx_done_tick) begin
                number_of_incoming_data_bytes_reg <= rx_data_out;
                state_next = reading;
            end else begin
                state_next = read_incoming_data_length;
            end

        end else if(state_reg == reading) begin

            if(count_reg == number_of_incoming_data_bytes_reg) begin
                state_next = idle;
                // do something to indicate that all the reading is done
                // and to send all the data in the fifo
            end else begin
                if(rx_done_tick) begin
                    generate_input_fifo_push_pulse_next = ~ generate_input_fifo_push_pulse;
                    count_next = count_reg + 1;
                end else begin
                    count_next = count_reg;
                end
            end

        end else begin
            count_next = 8'b0;
            state_next = idle;
        end
    end

En el código anterior, ¿cómo cambiaría el circuito sintetizado si reemplazara todas las asignaciones de bloqueo por no bloqueado?

2) Comprender la diferencia entre las instrucciones de bloqueo y no bloqueo cuando se escriben de forma secuencial es un poco más simple (y la mayoría de las respuestas a esta pregunta se centran en esta parte), pero ¿cómo afectan los comportamientos de bloqueo cuando se declaran en comportamientos condicionales separados? . Por ejemplo:

¿Haría una diferencia si escribiera esto?

if(rx_done_tick) begin
    a = 10;
end else begin
    a = 8;
end

o si escribí esto:

if(rx_done_tick) begin
    a <= 10;
end else begin
    a <= 8;
end

Sé que las sentencias condicionales se sintetizan para convertirse en multiplexores o estructuras de prioridad, por lo que creo que usar sentencias de bloqueo o no de bloqueo no debería hacer una diferencia, pero no estoy seguro.

3) Al escribir bancos de pruebas, el resultado de la simulación es muy diferente cuando se usan las instrucciones de bloqueo de v / s no bloqueantes. El comportamiento es muy diferente si escribo:

initial begin
    #31 rx_data_out = 255;
    rx_done_tick = 1;
    #2 rx_done_tick = 0;
    #30 rx_data_out = 3;
    rx_done_tick = 1;
    #2 rx_done_tick = 0;
    #30 rx_data_out = 10;
    rx_done_tick = 1;
    #2 rx_done_tick = 0;
end

frente cuando escribo esto:

initial begin
    #31 rx_data_out <= 255;
    rx_done_tick <= 1;
    #2 rx_done_tick <= 0;
    #30 rx_data_out <= 3;
    rx_done_tick <= 1;
    #2 rx_done_tick <= 0;
    #30 rx_data_out <= 10;
    rx_done_tick <= 1;
    #2 rx_done_tick <= 0;
end

Esto es muy confuso. En mi práctica, la señal rx_done_tick será generada por un Flip Flop. Por lo tanto, creo que las declaraciones no bloqueantes deben usarse para representar este comportamiento. Estoy en lo correcto?

4) Finalmente, ¿cuándo usar las asignaciones de bloqueo y cuándo no usar las instrucciones de no bloqueo? Es decir, ¿es cierto que las instrucciones de bloqueo deben usarse solo en comportamientos combinacionales, y las instrucciones de no bloqueo en comportamientos secuenciales solamente? En caso afirmativo o no, ¿por qué?

    
pregunta ironstein

6 respuestas

4
  

1) No puedo entender cómo cambiar de bloqueo a no bloqueo

El código de ejemplo publicado fue para un bloque combinatorio, cambiar todo el bloqueo ( = ) por no bloquear ( <= ) puede afectar a la simulación pero no afectará a la síntesis. Esto da lugar a una falta de coincidencia de RTL a nivel de puerta. Es un lugar incorrecto para usar la asignación sin bloqueo, no la use en una sección combinatoria.

Para resumir para la otra pregunta, el bloqueo no simula que los datos cambian justo después de un evento como la marca de un reloj. Esto permite la correcta simulación de un flip-flop.

esto implica para bancos de pruebas:

initial begin
    #31 rx_data_out = 255;

En el tiempo 31 ocurre la asignación.

initial begin
    #31 rx_data_out <= 255;

Solo después de la hora 31, la tarea se realizará. Prueba ambos en paralelo con un

initial begin
  #31 $display(rx_data_out);
end 

Para el primer ejemplo, en realidad tiene una condición de carrera, ambas suceden al mismo tiempo, debe imprimir 255. Para el segundo ejemplo, siempre se imprimirá x porque la asignación se realiza justo después del evento de la hora 31, no en él.

el no bloqueo puede ser útil para los bancos de prueba en los que desea imitar los datos controlados por flip-flops, es decir, cambia justo después del evento. por ejemplo, liberar un reinicio de alimentación.

initial begin
  @(posedge clk);
  @(posedge clk);
  rst_n <= 1'b0;
end

Imagina que tenemos una serie de flip-flops (a, b, c) creando una línea de retardo, cada uno tiene una entrada d y una salida q.

si las asignaciones se encadenaron con:

c = b = a

Los datos se precipitarían de a a c al instante. pero si tenemos

c <= b <= a

tenemos una tubería, y cada flip-flop puede mantener su valor.

Código actual:

always @(posedge clk) begin
  c = b;
  b = a;
  a = in;
end

versus:

always @(posedge clk) begin
  c <= b;
  b <= a;
  a <= in;
end

Esta es la razón por la cual para la pregunta 2 con una sola tarea no importa. pero si hay varias asignaciones que dependen unas de otras, realmente importa porque estás controlando si se maneja desde un flip-flop ( <= ) o un bloque de lógica combinatoria ( = ).

Mis reglas de oro: Utilice el bloqueo ( = ) para la lógica combinatoria y el no bloqueo ( <= ) para la secuencia (flip-flops)

    
respondido por el pre_randomize
1

La asignación de bloqueo vs no bloqueo es un concepto crucial y tiene dificultades para implementarlos correctamente porque no ha entendido la diferencia conceptual.

He adjuntado una diapositiva de MIT OCV PowerPoint lecture, 2005, que describe claramente la diferencia entre los dos

Debe comprender el concepto de cálculo de RHL (lado derecho). Verilog siempre calcula el RHS y lo pone en LHS. En el bloqueo, la asignación ocurre exactamente después de que se realiza el cálculo, mientras que en el bloqueo no, la asignación de RHS a LHS ocurre cuando se alcanza el final del bloque. Es por eso que como 'the Photon' ha mencionado para las líneas individuales, tanto el bloqueo como el no bloqueo serán iguales, pero si tiene más de una línea, entonces las cosas PUEDEN cambiar o no cambiar.

    
respondido por el Ehsan
1

Esto es más una adición a la respuesta de pre_randomize, pero como los comentarios no permiten imágenes, las publico como respuesta.

La regla general de oro, como ya se ha indicado es:

  

Utilice el bloqueo (=) para la lógica combinatoria y el no bloqueo (< =) para la secuencia (flip-flops)

La cadena D flip-flop es un buen ejemplo de cómo el uso de una asignación incorrecta (en este caso, una asignación de bloqueo para procedimientos secuenciales) crea resultados de simulación inconsistentes con la lógica sintetizada.

La otra cara es si tiene lógica combinacional multinivel, como se muestra a continuación:

simular este circuito : esquema creado usando CircuitLab

En este caso, si escribiéramos esto como dos líneas separadas para las salidas X e Y, escribiríamos:

Y = B&C;
X = A^Y;

Lo que tiene sentido, Y es que primero se convierte en B * C y después de eso, X se convierte en A + Y. Tenga en cuenta que existe un ordenamiento implícito debido a la forma en que se dibujan las puertas, la puerta AND se resuelve antes que la puerta OR porque un cable va desde la puerta AND a la puerta OR.

Considere lo que está describiendo si escribe lo siguiente:

Y <= B&C;
X <= A^Y;

En este caso, estamos diciendo que Y se convierte en B * C al mismo tiempo que X se convierte en X + Y (en el mismo paso de tiempo). Esto significa que un simulador evaluará Y con los valores anteriores (último paso de tiempo) de B y C (no es un problema) pero también X con los valores anteriores de A e Y (potencialmente incorrectos).

    
respondido por el Zuofu
0

Pregunta 1

es demasiado grande para responder aquí. Si realmente quieres saberlo, escríbelo de dos maneras y simula

Pregunta 2

Si solo tiene una declaración en un bloque de procedimiento, no importa (al menos en la práctica) si está bloqueando o no.

Pregunta 3

Su banco de pruebas no se implementará en flip-flops, solo será interpretado por la herramienta de simulación. No te preocupes por cómo se sintetizará el código de testbench.

Pregunta 4

Aprendí a usar siempre el no bloqueo para el código de síntesis. Utilice el bloqueo solo para casos especiales donde hace que el código sea más fácil de entender. Pero hay otras filosofías sobre esto.

    
respondido por el The Photon
0

He encontrado una respuesta satisfactoria y necesito información para ella. Creo que deberíamos usar declaraciones sin bloqueo para ambas declaraciones combinacionales y secuenciales.

Para la secuencia es bastante claro y debemos usar.

Describiré la razón de Combi Blocks.

Por ejemplo, toma el siguiente código

module block_nonblock(output logic x,x1,y,y1,input logic a,b,c);

always@* begin : BLOCKING
    x1 = a & b;
    x  = x1 & c; 
end

always@* begin : NONBLOCKING
    y1 <= a & b;
    y  <= y1 & c; 
end

endmodule

Aquí se infiere el mismo hardware para ambos circuitos.

Sin embargo, si damos las entradas juntas como (A = 1, B = 1, C = 0) y luego las cambiamos juntas después de decir 10ns como (A = 1, B = 0, C = 1) entonces podemos ver que hay una falla. Esta falla también estará presente en el hardware real. Pero esto solo se muestra en la simulación por la salida de declaraciones de no bloqueo (Y) y no por la salida de declaraciones de bloqueo (X). Una vez que vemos un fallo técnico, tomamos medidas adicionales para evitar que esto suceda, para que no suceda en el hardware.

Para los segmentos combinacionales, usaremos declaraciones de no bloqueo porque, cuando usamos declaraciones de bloqueo o no de bloqueo, a pesar de que nos da el mismo hardware o RTL al final; son las declaraciones sin bloqueo las que nos muestran los fallos en la simulación. Estas fallas también estarán presentes en el hardware (debido a los retrasos de la puerta), por lo que podemos corregirlas cuando las veamos en la simulación para que causen menos daño en una etapa posterior del ciclo de diseño / desarrollo.

Por lo tanto, creo que es seguro concluir que debemos usar declaraciones sin bloqueo para los bloques combinados.

    
respondido por el Edwin Joseph
0

Una respuesta muy simple podría resolver el problema. Cuando realiza la síntesis, está utilizando un subconjunto de las capacidades expresivas completas del lenguaje verilog. En lógica sintetizada, puedes tener flops, latches y lógica combinatoria. Los flops y los latches en particular son inferidos por la herramienta de síntesis usando plantillas.

Si escribe una función temporizada con una asignación de bloqueo, la simulación es feliz, pero no coincidirá con su comportamiento FPGA. Por razones de compatibilidad o heredadas, la herramienta de síntesis podría no negarse a aceptar la entrada y solo se quejará. Simplemente, nunca intentes esto.

La única vez que verá una simulación exponer estos comportamientos diferentes para una función temporizada en un código sintetizable, es cuando la RHS de una expresión se actualiza con otros bloques cronometrados. Visualizo que esto es similar a los problemas de mantenimiento de la instalación, que se ven igualmente extraños si dibujas diagramas de tiempo para una simulación de demora cero. En la práctica, las salidas de flop siempre cambian después del borde del reloj, pero al observar en simulación, esta información generalmente no se muestra.

    
respondido por el Sean Houlihane

Lea otras preguntas en las etiquetas