División aritmética en Verilog

1
module averager(
    clk,
    rst,
    n,
    sum,
    cnt,
    out,
    avg
 );

input  [9:0] n;
input clk;
input rst;
output reg [19:0] out;
output reg [9:0] cnt;
output reg [19:0] sum;
output reg [9:0] avg;

integer i = 0;

always @(posedge clk ) 
    if (rst == 1) begin 
        sum = 20'b0;
        cnt = 10'b0;
        out = 20'b0; 
        avg = 10'b0;
    end else if (rst == 0) begin
        sum = sum + n;
        out = sum;
        cnt = cnt + 1;
        avg = 0;

        for (i=0; i<641; i=i+1) begin
            if(out >= cnt) begin
                out = out - cnt;
                avg = avg + 1;
            end
        end
    end
endmodule

Lo anterior es el código para implementar un filtro de promedio móvil acumulativo. El bucle for se usa para la división para encontrar el promedio e implica la resta repetida. Sin embargo, estoy recibiendo la siguiente advertencia y error:

WARNING:Xst:2254 - Area constraint could not be met for block <averager>, final ratio is 509.
WARNING:Xst:1336 -  (*) More than 100% of Device resources are used
ERROR:Pack:18 - The design is too large for the given device and package. 
  Please check the Design Summary section to see which resource requirement for
  your design exceeds the resources available in the device.

Esto debe ser porque estoy usando valores grandes en el bucle for y, por lo tanto, estoy obteniendo un circuito grande que no se puede implementar. Estoy buscando una alternativa del bucle for, que podría encontrar el promedio para mí. Solo necesito el valor del cociente.

Propiedades de diseño: Familia: Spartan3E Dispositivo: XC3S500E

    
pregunta vikiboy

2 respuestas

3

Tienes razón al adivinar el bucle for.

La lógica de bucle for es enorme cuando, después de que se desenrolla, la estática. Con su código actual, no puede manejar el peor de los casos donde n = 1023. Para cubrir esto con su código actual, necesitaría un bucle for con 1024 iteraciones.

En lugar de un contador hacia arriba, puede usar un contador hacia abajo y solo examinar una porción de la matriz, donde el índice representa lsb de la porción de la matriz. Por ejemplo:

for (i=9; i>=0; i=i-1) begin // lsb index of the slice
  if (out[i+:11] >= cnt) begin // 11-bit slice compare
    out[i+:11] = out[i+:11] - cnt; // 11-bit slice subtraction
    avg[i] = 1'b1; // 1-bit assign
  end
end

Esto para el bucle se desenreda a 10 iteraciones (9 a 0), cada iteración solo se ve en un segmento de 11 bits de out y solo un bit de avg . Es posible que no esté familiarizado con el operador +: . Es un operador de división de bits introducido en IEEE Std 1364-2001. Lado izquierdo si el índice de inicio (dinámico está permitido) y el lado derecho es el bit con desplazamiento (debe ser una constante estática). Puede leer más sobre esto aquí .

Como se trata de una cuenta atrás, podemos asumir de forma segura (comprobado matemáticamente) que los bits superiores de la división son ceros y nunca tendremos subdesbordamiento con la condición de protección si. Así que ahora tenemos diez restadores de 11 bits, cada uno con asignadores de 1 bit, que es una lógica mucho más pequeña que los 642 (deberían ser 1024), restadores de 20 bits, cada uno con sumador de 10 bits.

Algunas otras sugerencias:

  1. Use el encabezado de estilo ANSI (compatible desde IEEE Std 1364-2001). Se trata de algunas líneas de código y, por lo tanto, menos propensas a errores tipográficos y copiar y pegar errores.
  2. Separe la lógica síncrona y la lógica de combinación. Esto significa declarar más firmas, pero generar le da un mejor control sobre qué es un flop y qué es la lógica de peine. También sigue las mejores prácticas. Tu bucle for debería salir en la lógica de peine.
  3. Use asignaciones no bloqueantes ( <= ) en su bloque lógico síncrono. Esto sigue las mejores prácticas de codificación y evita las condiciones de carrera de flop a flop en la simulación.
  4. out se puede simplificar a un registro de 10 bits, asumiendo que hiciste las sugerencias 2 & 3. Esto se debe a que sabemos que los bits superiores siempre serán ceros, por lo que podemos guardar 10 flops.

prueba de concepto: con un sencillo banco de pruebas SystemVerilog aquí :

module averager( // ANSI style header
  input              clk, rst,
  input [9:0]        n,
  output reg [19:0]  sum,
  output reg [9:0]   cnt,
  output reg [9:0]  out, // was [19:0]
  output reg [9:0]   avg
  );

  reg [19:0] next_sum, next_out;
  reg [9:0]  next_cnt, next_avg;

  integer i;

  always @* begin
    next_sum = sum + n;
    next_out = next_sum;
    next_cnt = cnt + 1'b1;
    next_avg = 10'b0;

    for (i=9; i>=0; i=i-1) begin // lsb index for slice
      if (next_out[i+:11] >= next_cnt) begin // 11-bit slice compare
        next_out[i+:11] = next_out[i+:11] - next_cnt; // 11-bit slice subtract
        next_avg[i] = 1'b1;  // 1-bit assign
      end
    end
  end

  always @(posedge clk) begin
    if (rst == 1'b1) begin
      sum <= 20'b0;
      cnt <= 10'b0;
      out <= 10'b0; // was 20'b0
      avg <= 10'b0;
    end
    else begin
      sum <= next_sum;
      cnt <= next_cnt;
      out <= next_out[9:0]; // only assign lsb
      avg <= next_avg;
    end
  end

endmodule

Si aún experimenta problemas en el área o el rendimiento no es lo suficientemente bueno, entonces debe canalizar su diseño y / o ver si hay un módulo dedicado de divisor + resto definido en su hoja de datos de FPGA que pueda crear una instancia.

    
respondido por el Greg
0

No estoy seguro de qué experiencia tienes en el diseño de software integrado (en su mayoría instrucciones secuenciales), pero una verdadera división toma FOREVER en comparación con casi cualquier otra cosa, incluso si existe tal instrucción. Así que tenemos todo tipo de trucos para lograr la meta de alto nivel sin realmente dividirnos. Dos de ellos son:

  • Cambio de bits: esto es tan barato y fácil, es casi como hacer trampa. Sin embargo, te restringe a poderes de 2. Desplazar a la derecha por N equivale a dividir por 2 ^ N.
  • Aritmética de punto fijo: esto puede ser difícil de entender, pero la idea básica es que usted decida como desarrollador (el hardware no sabe o le importa) interpretar cada número como si tuviera un punto fraccionario entre ciertos bits. , como en el binario 1011.1001 = decimal 185/16 = 11.5625. (un depurador leería ese byte como decimal 185) Esto le permite multiplicarse por un recíproco, que es más caro que desplazar pero aún más barato que dividir, y permite cualquier número que se pueda almacenar.
    • Por supuesto, el producto puede tener el punto fraccionario entre un conjunto diferente de bits si insiste en que sea matemáticamente correcto, pero nada realmente dice que tenga que serlo. En este punto, podría aceptar de manera conveniente que el producto en bruto se incremente o disminuya automáticamente, o podría cambiarlo a un rango diferente. Solo tenga cuidado de no dejar caer ningún fragmento significativo.
    • Y esto solo funciona para la división por una constante (constante en lo que respecta a este fragmento de código). Por lo que sé, se necesita una verdadera división para generar el recíproco, por lo que no está mejor a menos que pueda hacerlo una vez y reutilizar el resultado.

¿Por qué no punto flotante? Porque la aritmética FP en general es casi lo único que es más caro que la división entera. Al menos las CPU de gama alta tienen hardware dedicado para ello.

Sé que estás trabajando en una máquina paralela y mi experiencia es secuencial, pero estoy bastante seguro de que son idénticos al nivel que acabo de describir. Mi máquina reutiliza el mismo bloque lógico para cada operación, mientras que la suya tiene uno dedicado para cada operación. Solo tiempo vs. espacio.

    
respondido por el AaronD

Lea otras preguntas en las etiquetas