Código verilog del bombeo con ciclo del 50%

166 views
Skip to first unread message

Jorge Garcia Mateos

unread,
Jan 31, 2021, 1:06:15 PM1/31/21
to fpga-wars-explora...@googlegroups.com, obijua...@gmail.com

Hola,

Estoy estudiando y tratando de entender el código Verilog del bloque de bombeo a 1Hz con ciclo de trabajo del 50% en la colección "Academia-Jedi-HW-06". He hecho la prueba con "localparam  M= 6", para obtener un divisor por 6 de una señal de reloj que genero en el testbench. Un divisor por 6 puedo gestionarlo más fácilmente.

El código que he probado es "divisor_50.v":

module divisor_50 
    (input clk, 
     output clk_o);
// localparam M = 12000000;
localparam M = 6;
//-- Calcular el numero de bits para almacenar M
localparam N = $clog2(M);
//-- Registro del divisor
reg [N-1:0] divcounter = 0;
//-- Temporal clock
reg clk_t = 0;
//-- Se usa un contador modulo M/2 para luego
//-- pasarlo por un biestable T y dividir la frecuencia
//-- entre 2, para que el ciclo de trabajo sea del 50%
always @(posedge clk)
    if (divcounter == M/2)
        begin clk_t <= 1;
        divcounter = 0;
        end 
    else 
        begin
        divcounter <=  divcounter + 1;
        clk_t = 0;
        end 
  
reg clk_o = 0;
    
// //-- Biestable T para obtener ciclo de trabajo del 50%
always @(posedge clk)
    if (clk_t)
    clk_o <= ~clk_o;
endmodule

idéntico al del bloque corazón. El testbench que he usado es "divisor_50_tb.v":

module divisor_50_tb();
//-- Registro para generar la señal de reloj
reg clk = 0;
//wire clk_out;
always #1 clk = ~clk;
    
//-- Instanciar el divisor
divisor_50 dut(
    .clk(clk),
    .clk_o(clk_o));
//-- Proceso al inicio
initial begin
    
  //-- Fichero donde almacenar los resultados
  $dumpfile("divisor_50_tb.vcd");
  $dumpvars(0, divisor_50_tb);
    
  # 60 $display("FIN de la simulacion");
  $finish;
end
endmodule

Esperaba tener un divisor x6 de la señal de reloj clk con un ciclo de trabajo del 50%, pero lo que sale es un divisor x8, tal y como se ve en la captura 1 que os adjunto.

He cambiado el código para que en vez de contar hasta M/2 se cuente hasta M/2 -1, y en el primer bloque "always" también sustiuyo "clk_t= 0" por "clk_t <= 0".  El nuevo código "divisor_50.v" queda como:

module divisor_50 
    (input clk, 
     output clk_o);
// localparam M = 12000000;
localparam M = 6;
//-- Calcular el numero de bits para almacenar M
localparam N = $clog2(M);
//-- Registro del divisor
reg [N-1:0] divcounter = 0;
//-- Temporal clock
reg clk_t = 0;
//-- Se usa un contador modulo M/2 para luego
//-- pasarlo por un biestable T y dividir la frecuencia
//-- entre 2, para que el ciclo de trabajo sea del 50%
always @(posedge clk)
    if (divcounter == M/2 -1) //Sustituyo M/2 por M/2 - 1
        begin
    clk_t <= 1;
        divcounter = 0;
        end 
    else 
        begin
        divcounter <=  divcounter + 1;
        clk_t <= 0; //Hago asignación "non-blocking". Si la dejo "blocking" no funciona.
        end 
  
reg clk_o = 0;
    
// //-- Biestable T para obtener ciclo de trabajo del 50%
always @(posedge clk)
    if (clk_t)
    clk_o <= ~clk_o;
endmodule

y con este sí que la simulación da una señal "clk_o" que es un divisor x6 de "clk", al 50%, como se observa en la captura 2, que también os adjunto. Si no se sustiuye "clk_t= 0" por "clk_t <= 0" la cosa no funciona. Tengo entendido que lo habitual es poner hacer una asignación "non-blocking" en los bloques "always", pero no acabo de entender la sutileza de que después de cambiar de "M/2" a "M/2 -1" si no se hace asignación con "non-blocking" la cosa no rule como se quiere.

Por favor, confirmarme si estoy en lo cierto y si entendéis la necesidad de la asignación "non-blocking" para "clk_t".

Saludos y muchas gracias,

Jorge

Captura 1.png
Captura 2.png

Democrito

unread,
Jan 31, 2021, 3:11:58 PM1/31/21
to FPGAwars: explorando el lado libre
Hola Jorge,

A riesgo de equivocarme te comento lo siguiente: (Mejor que lo confirme o desmienta otro compañero)

Las asignaciones "<=" (no bloqueantes) siempre es a registros porque ha sido declarado como "reg" de uno o más bits. En los demás casos siempre se asigna como "=" (bloqueantes).

Hay una excepción y es cuando declaramos el propio registro y le asignamos un valor, ahí sí puedes poner directamente el "=".

Ejemplo, declaramos "myvar" como un registro de un bit y lo iniciamos con el valor "1".

reg myvar = 1;

Sólo en la declaración del registro lleva el "=", porque se le inicializa con un valor; esto es como cuando en programación declaras una variable y le asignas un valor inicial (inicializar las variables a un valor es siempre recomendado, aquí pasa lo mismo).

Luego, en la descripción del circuito, como el registro es una memoria (que va a almacenar un valor) la asignación ha de ser no bloqueante, es decir, con "<=". Y además la pista la tienes en que ese registro será actualizado cada vez que haya un "always @(posedge clk)" es decir, le llegue un pulso del clock.

Ejemplos, ya tenemos declarado el registro y asignado un valor inicial, si operamos con él, cada vez que quieras "actualizar" su valor interno lo harás con "<=".

always @(posedge clk)  begin
   ...
   ...
   myvar <= ~input;
   ...
   ...
end

otro ejemplo:

always @(posedge clk)  begin
   ...
   ...
   muyvar <= 0;
   ...
   ...
end

El resto de casos, como son cables y no almacena ningún valor (por tanto no tiene valor inicial y no es necesario inicializar)  entonces ponemos  siempre el "=". Me invento un ejemplo.

wire [7:0] in;
wire [9:0] out;
wire [17:0] temp;

assign temp = (in * 1000) / 256;
assign out  = temp;

Hace una operación matemática y no utiliza registros, por tanto la asignación es siempre bloqueante "=".

En tu ejemplo hay un pequeño error que el sintetizador te lo ha dado por bueno porque se da cuenta de lo que quieres hacer, y está en:

always @(posedge clk)
    if (divcounter == M/2 -1) //Sustituyo M/2 por M/2 - 1
        begin
           clk_t <= 1;
           divcounter = 0;
        end 
    else 
        begin
           divcounter <=  divcounter + 1;
           clk_t <= 0; //Hago asignación "non-blocking". Si la dejo "blocking" no funciona.
        end 

Observa que divcounter es un registro, por tanto ahí falta el "<=". También sería bueno que los always tengan un begin con su correspondiente end. Te pongo un ejemplo de como normalmente se haría:

always @(posedge clk)
begin
    if (divcounter == M/2 -1) //Sustituyo M/2 por M/2 - 1
        begin
           clk_t <= 1;
           divcounter <= 0;
        end 
    else 
        begin
           divcounter <=  divcounter + 1;
           clk_t <= 0; //Hago asignación "non-blocking". Si la dejo "blocking" no funciona.
        end
end

Segunda pregunta:

Tienes puesto esto:

// localparam M = 12000000;
localparam M = 6;

Le estás diciendo que tienes un clock de 6 hercios en vez de 12 millones de hercios (ahí hay que poner la velocidad de tu clk). Por tanto no sé por qué te está funcionando o si el sintetizador te ha adivinado el pensamiento (a mí también me ha pasado esto alguna vez con otras cosas).

Pero te comento con palabras cómo se suele hacer:

Las señales de reloj rara vez son duty 50%, entonces, por ejemplo, para crear una frecuencia con duty del 50% lo que se hace es tomar una frecuencia el doble de la que necesitas y esa nueva señal la haces pasar por un divisor de 2, es decir, un flip-flop T por ejemplo. Entonces te queda a esa frecuencia y con duty 50%. El flip-flop T final es que hace que la frecuencia sea duty 50%, y ha de ser el doble de la que necesitas precisamente porque el flip-flop te la va a dividir entre 2.

Ánimo y un saludo!

Democrito

unread,
Jan 31, 2021, 3:34:27 PM1/31/21
to FPGAwars: explorando el lado libre
Ejemplo gráfico:
duty cicle and half frequency.PNG

Jorge Garcia Mateos

unread,
Jan 31, 2021, 5:52:20 PM1/31/21
to Democrito, fpga-wars-explora...@googlegroups.com, obijua...@gmail.com

Hola Demócrito,

Muchísimas gracias por tu contestación y por responder tan rápidamente.

El principal punto que quería comentar en el mail anterior, es que en mi opinión, para conseguir el divisor de frecuencia por M con duty 50%, hay que sumar a "divcounter" hasta M/2 -1, y no M/2. Me gustaría que me confirmarais ese punto. Además, como divcounter sólo llega a ser usado hasta M/2 -1 se podría calcular el parámetro N como $clog2(M/2-1) y así tenemos "divcounter" con una dimensión más ajustada y quizá se ahorra algún recurso en la fpga.

Por otra parte, y comentando las explicaciones que me das:

1. Por lo que sé de Verilog, una variable se define como "wire" o "reg" dependiendo de si aparece en un bloque "always" o no. Si aparece en un always es "reg" y si no aparece es un "wire". Nada que ver con que la variable esté relacionada con un DFF o sea pura combinacional.  Se puede hacer código totalmente combinacional y usar reg, por ejemplo:

```

module full adder

   (input a,b,cin,

    output sum,cout);

reg sum;

reg cout;

always@(a or b or cin)     //siempre que haya un cambio en cualquiera de ellas

    begin

        sum = a^b^cin;   

        cout=(a&b) | (a&cin) | (b&cin);

    end

endmodule

```

Como "sum" y "cout" se utilizan en un bloque always, se tienen que definir como "reg" aunque todo sea puramente combinacional. El full-adder se podría escribir sin necesidad de un always, cierto, pero a veces utilizar always permite introducir condicionales o hacer operaciones que justifica usar un "always", y por tanto las variables que están dentro, y no son las de entrada, tienen que ser reg.

Mis referencias de verilog es el curso de obijuan y el curso http://web.mit.edu/6.111/www/f2016/ (adjunto las transparencias relacionadas con verilog directamente y de donde he sacado el ejemplo anterior). En la transparencia 23 del L03.pdf se comenta el tema reg/wire.

2. Estoy de acuerdo contigo en que dentro de los dos always del bombeo se tiene que usar "<=", pero sólo comentarte que el fichero que he puesto en el mail anterior es exactamente el que se utiliza en el corazón de 1Hz. De hecho he añadido "reg[N-1:0] divcounter = 0;". En el original aparece "reg[N-1:0] divcounter;" y al hacer la simulación con iverilog, la variable "divcounter" no tomaba valores.

Para hacer las simulaciones desde línea de comando utilizo:

```

$ iverilog -o divisor_50_tb.out divisor_50.v divisor_50_tb.v   <---- requiere: reg[N-1:0] divcounter = 0;

```

3. Supongo que lo conocéis todos, pero existe una herramienta en yosys que permite ver el diagrama de bloques a partir del código verilog. Creas un fichero "show_rtl.ys" :

```

# read design, en mi caso divisor_50.v
read_verilog divisor_50.v  
hierarchy -check

# high-level synthesis
proc; opt; fsm; opt; memory; opt

# se genera el fichero divisr_50.ps en el mismo directorio de trabajo
show -format ps -prefix ./divisor_50

```

y ejecutas:

$ yosys show_rtl.ys

Se genera un fichero postcript divisor_50.ps que puedes visulizar con "gv" o cualquier lector de postscript.

En línea de comando:

$ gv divisor_50.ps

Os adjunto el resultado en el fichero Caputra RTL.png

Lo curioso, es que utilices asignaciones bloqueantes o no, el diagrama RTL que genera yosys sale siempre el mismo, el de la captura, sin embargo, al simular el testbench con iverilog en unos casos aparece correcto el funcionamiento como divisor por 6 y en otros no, según el tipo de asignación que se haga.

El resultado gráfico del diagrama RTL es un poco espeluznante, sin duda, pero si borras las líneas de clk y te lo redibujas a mi me sirve para aprender mucho de cómo se convierte en bloques el código verilog que escribo. Si alguno conocéis alguna otra herramienta que haga eso, por favor, comentármelo. Supongo que las herramientas propias de Lattice para las ice40 lo permitirán, pero no las he probado y no sé si son gratuitas. ¿Alguién las ha probado?

En cualquier caso, puestos a pedir, me encantaría que Icestudio tuviera la posibilidad de escribir verilog y que lo convirtiera en bloques RTL el solito, como hace la herramienta de yosys. Que el resultado sea más amigable será fácil :-). Contar conmigo en lo que pueda ayudar.

4. En el código tengo puesto M=6 pues quiero hacer un divisor por 6. El reloj de entrada no tiene porqué ser el de 12MHz. Si el reloj de entrada es de 12MHz y quiero un reloj de 1Hz, tengo que usar un divisor por 12 millones, claro, por eso M=12_000_000 en el código original. En el módulo, el reloj será el que se le ponga por su puerto de entrada. Al hacer la simulación con el testbench le pongo un reloj con periodo dos unidades de tiempo de la simulación, que por defecto son segundos, es decir el periodo del reloj de entrada de la simulación es de 2 sg.

He usado M=6 porque me permite fácilmente contar los periodos del divisor en gtkwave y ver si funciona correctamente.

5. Si te he entendido bien, el circuito que propones es para "limpiar" una señal de reloj y hacerla 50% duty, eso no es exactamente lo que buscaba, pero muchas gracias de todas formas por la contestación y el circuito.

Saludos,

Jorge

--
Has recibido este mensaje porque estás suscrito al grupo "FPGAwars: explorando el lado libre" de Grupos de Google.
Para cancelar la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a fpga-wars-explorando-el...@googlegroups.com.
Para ver esta conversación en el sitio web, visita https://groups.google.com/d/msgid/fpga-wars-explorando-el-lado-libre/c1d644a2-5f84-4e4d-a58b-905ad86fad94n%40googlegroups.com.
L03.pdf
L04.pdf
Captura RTL.png

Democrito

unread,
Jan 31, 2021, 6:18:03 PM1/31/21
to FPGAwars: explorando el lado libre
Hola de nuevo Jorge,

Mis conocimientos de verilog son muy limitados y viendo que tienes mucho control sobre el tema te respondo a lo que se interpretó de mi comentario.

Efectivamente, ese es otro caso cuando se hace operaciones lógicas dentro de un always y no me detuve en ello porque no sabía qué nivel tenías (era como liarla más de la cuenta). Sin embargo cuando ese always lleva la entrada del reloj (y lo especifiqué como "always @(posedge clk)") las asignaciones a los registros es siempre con "<=", y me alegro que todo esto lo conozcas a la perfección, pero al ver en tu diseño un registro dentro de un always con clk, y que asignabas una vez como bloqueante y luego al mismo registro, más abajo, otra asignación no bloqueante, pues lo tenía que señalar, pese a que el sintetizador lo suele tomar bien este tipo de cosas.

Con respecto al valor de M sigo pensando lo mismo, no le estas especificando la frecuencia de tu placa, ya que es a partir de ella con la que tiene que trabajar.

Saludos.


Democrito

unread,
Jan 31, 2021, 7:01:05 PM1/31/21
to FPGAwars: explorando el lado libre
localparam M = 12000000;    // Frecuencia de la placa.
localparam N = $clog2(M);    // Logaritmo en base 2, de 12000000 que resulta ser 23 "coma algo", pero sólo toma la parte entera, por tanto N vale 23. (Necesitamos 23 registros como contador para convertir 12MHz en 1Hz.
reg [N-1:0] divcounter = 0;    // Se crea un contador llamado divcounter de 23 bits (del bit 22 al 0 suman 23 bits).

Democrito

unread,
Jan 31, 2021, 8:12:48 PM1/31/21
to FPGAwars: explorando el lado libre
He de rectificar una cosa que puse antes:

Me he estado informando sobre clog2, y el resultado es siempre un bit mayor si da decimales. Entonces como es 23 "coma algo" (no da exacto) el resultado de clog2(12000000) es 24 y no 23 como puse antes.

Si en google ponéis log2(12000000) veréis que da 23.51653107. Supongo que la función clog2() lo que hace es que si da decimales por insignificantes que sea lo redondea al siguiente valor, es decir, en este caso es 24.

Manuel Pascual

unread,
Feb 1, 2021, 3:08:24 AM2/1/21
to fpga-wars-explora...@googlegroups.com
Democrito M es el valor maximo del contador. solo coincide la frecuencia de la placa cuando el reloj de entrada es de la placa y quieres una salida de 1 Hz. no es el caso. Jorge lo a echo bien.
a mayores, es incorrecto decir que 'clk_o' es un reloj. aunque pudiera parecerlo es un warning o error y una muy mala practica de diseño. recomiendo leer sobre "gated clock" por ejemplo https://m.eet.com/media/1157355/fpmm%20-%20part%202.pdf

Democrito

unread,
Feb 1, 2021, 3:32:33 AM2/1/21
to FPGAwars: explorando el lado libre
Ops! Ok, ok, este tipo de cosa me hace estallar la cabeza, lo desconocía por completo!

Gracias por aclararlo.
Reply all
Reply to author
Forward
0 new messages