El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Diseño HDL con este lenguaje. Módulos y testbenchs. Estilos y trucos de codificación, etc. NOTA: dado que hay entornos como ISE que soportan Verilog pero no SystemVerilog, señalad dentro de un post que de lo que se va a tratar es SystemVerilog si es el caso.
Responder
Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por mcleod_ideafix » 24 Ago 2018, 20:01

Este hilo viene como consecuencia de este otro:
viewtopic.php?f=33&t=5#p103

Verilog invita a modularizar el diseño. Lo que en Handel-C serían bloques del tipo while(1).... lo que sea, aquí lo haremos con módulos. Muchos de ellos se podrán reusar para otras cosas. Los módulos son:
- Un generador de sincronismos que en base a los timings de la VGA (ver código fuente para links a la web sobre esto) y al reloj que usaremos (25 MHz) genera los pulsos de sincronismo horizontal y vertical, las coordenadas X,Y del pixel que se requiere pintar en ese momento, y un flag (display_enable) que indica que hay que poner un valor de color de pixel, o el color negro.
- Un generador de fondo: en función de display_enable, y los valores X,Y de las coordenadas del pixel actual, genera un color que hace que en pantalla se vea un patrón estilo cuadrado de ajedrez de colorines. Cambiando este módulo por otro podemos cambiar el fondo de nuestra pantalla.
- Un generador de sprites: en función de la posición X,Y actual en pantalla, y de la posición X,Y de la esquina superior izquierda del sprite, decide si debe empezar a pintar el sprite (usando el color que corresponda en la tabla del sprite), o el color de fondo.
- Un módulo que actualiza la posición del sprite: una vez por frame, exactamente cuando ocurre el pulso de sincronismo vertical, se actualizan las posiciones X,Y del sprite, se detecta si ha llegado a alguna esquina y se hace el "rebote".

Pues bien, estos son los módulos en el mismo orden en que los he nombrado:

videosyncs.v

Código: Seleccionar todo

module videosyncs (
  input wire clk,        // reloj de 25 MHz (mirar abajo en el "ModeLine")
  output reg hs,         // salida sincronismo horizontal
  output reg vs,         // salida sincronismo vertical
 	output wire [10:0] hc, // salida posicion X actual de pantalla
	output wire [10:0] vc, // salida posicion Y actual de pantalla
  output reg display_enable // hay que poner un color en pantalla (1) o hay que poner negro (0)
  );
	
  // Visita esta URL si pretendes cambiar estos valores para generar otro modo de pantalla. Atrevete!!!
  // https://www.mythtv.org/wiki/Modeline_Database#VESA_ModePool
  // El que he usado aquí es:
  // ModeLine "640x480" 25.18 640 656 752 800 480 490 492 525 -HSync -VSync
  //                      ^
  //                      +---- Frecuencia de reloj de pixel en MHz
  parameter HACTIVE = 640;
  parameter HFRONTPORCH = 656;
  parameter HSYNCPULSE = 752;
	parameter HTOTAL = 800;
  parameter VACTIVE = 480;
  parameter VFRONTPORCH = 490;
  parameter VSYNCPULSE = 492;
  parameter VTOTAL = 525;
  parameter HSYNCPOL = 0;  // 0 = polaridad negativa, 1 = polaridad positiva
  parameter VSYNCPOL = 0;  // 0 = polaridad negativa, 1 = polaridad positiva

  reg [10:0] hcont = 0;
  reg [10:0] vcont = 0;
	
  assign hc = hcont;
  assign vc = vcont;

  always @(posedge clk) begin
      if (hcont == HTOTAL-1) begin
         hcont <= 11'd0;
         if (vcont == VTOTAL-1) begin
            vcont <= 11'd0;
         end
         else begin
            vcont <= vcont + 11'd1;
         end
      end
      else begin
         hcont <= hcont + 11'd1;
      end
  end
   
  always @* begin
    if (hcont>=0 && hcont<HACTIVE && vcont>=0 && vcont<VACTIVE)
      display_enable = 1'b1;
    else
      display_enable = 1'b0;

    if (hcont>=HFRONTPORCH && hcont<HSYNCPULSE)
      hs = HSYNCPOL;
    else
      hs = ~HSYNCPOL;

    if (vcont>=VFRONTPORCH && vcont<VSYNCPULSE)
      vs = VSYNCPOL;
    else
      vs = ~VSYNCPOL;
  end
endmodule   
fondo.v

Código: Seleccionar todo

module fondo (
  input wire [10:0] hc,
  input wire [10:0] vc,
  input wire display_enable,
  output reg [7:0] r,
  output reg [7:0] g,
  output reg [7:0] b
  );

  always @* begin
    if (display_enable == 1'b1) begin
      r = hc[7:0] ^ vc[7:0];
      g = hc[7:0] ^ vc[7:0];
      b = {hc[7], vc[7], 6'b000000};
    end
    else begin
      r = 8'h00;
      g = 8'h00;
      b = 8'h00;
    end
  end
endmodule
sprite.v

Código: Seleccionar todo

module sprite (
  input wire clk,
  input wire [10:0] hc,   // posicion X de pantalla
  input wire [10:0] vc,   // posicion Y de pantalla
  input wire [10:0] posx, // posicion X inicial del sprite
  input wire [10:0] posy, // posicion Y inicial del sprite
  input wire [7:0] rin,   // color de pantalla
  input wire [7:0] gin,   // proveniente del
  input wire [7:0] bin,   // modulo anterior (el fondo, por ejemplo)
  output reg [7:0] rout,  // color de pantalla
  output reg [7:0] gout,  // actualizado
  output reg [7:0] bout   // segun haya que pintar o no el sprite
  );

  localparam
    TRANSPARENTE = 24'h00FF00,  // en nuestro sprite el verde es "transparente"
    TAM          = 11'd16;      // tamaño en pixeles tanto horizontal como vertical, del sprite

  reg [23:0] spr[0:255];   // memoria que guarda el sprite (16x16 posiciones de 24 bits cada posicion)
  initial begin
    $readmemh ("datos_sprite.hex", spr);  // inicializamos esa memoria desde un fichero con datos hexadecimales
  end
  
  wire [3:0] spr_x = hc - posx; // posicion X dentro de la matriz del sprite, en función de la posicion X actual de pantalla y posicion inicial X del sprite
  wire [3:0] spr_y = vc - posy; // posicion Y dentro de la matriz del sprite, en función de la posicion Y actual de pantalla y posicion inicial Y del sprite
  
  reg [23:0] color;  // color del pixel actual del sprite
  
  always @(posedge clk) begin
    color <= spr[{spr_y,spr_x}];  // leemos el color del pixel y lo guardamos (la posición del pixel podría ser completamente erronea aquí)
    if (hc >= posx && hc < (posx + TAM) && vc >= posy && vc < (posy + TAM) && color != TRANSPARENTE) begin  // si la posicion actual de pantalla está dentro de los márgenes del sprite, y el color leido del sprite no es el transparente....
      rout <= color[23:16];  // en ese caso, el color de salida
      gout <= color[15:8];   // es el color del pixel del sprite
      bout <= color[7:0];    // que hemos leido
    end
    else begin
      rout <= rin;           // si no toca pintar el sprite
      gout <= gin;           // o bien el color que hemos leido es el transparente
      bout <= bin;           // entonces pasamos a la salida el color que nos dieron a la entrada
    end  
  end  
endmodule
update.v

Código: Seleccionar todo

module update (
  input wire clk,
  input wire vsync,
  output reg [10:0] posx,
  output reg [10:0] posy
  );
  
  parameter
    XMAX = 11'd640,
    YMAX = 11'd480,
    TAM  = 11'd16;
    
  initial begin
    posx = 11'd320;  // incialmente, centro de la pantalla
    posy = 11'd240;  // para una pantalla de 640x480, claro.
  end
  
  reg vsync_prev = 1'b0;
  reg dx = 1'b0;  // 0: mover a la derecha. 1: mover a la izquierda
  reg dy = 1'b0;  // 0: mover hacia abajo. 1: mover hacia arriba
  
  always @(posedge clk)
    vsync_prev <= vsync;    
  wire actualizar_ahora = vsync_prev & ~vsync;  // momento en el que vsync pasa de 1 a 0 (flanco de bajada)
  
  always @(posedge clk) begin
    if (actualizar_ahora == 1'b1) begin
      if (posx == XMAX-TAM && dx == 1'b0 || posx == 11'd1 && dx == 1'b1)  // si llegamos al borde izquierdo o derecho, cambiar sentido de movimiento horizontal
        dx <= ~dx;
      if (posy == YMAX-TAM && dy == 1'b0 || posy == 11'd1 && dy == 1'b1)  // si llegamos al borde inferior o superior, cambiar sentido de movimiento vertical
        dy <= ~dy;
      posx <= posx + {{10{dx}}, 1'b1};  // si dx=0, esto hace que se sume 00000000001 a posx. Si dx=1, esto hace que se sume 11111111111 a posx.
      posy <= posy + {{10{dy}}, 1'b1};  // lo mismo, pero con dy y posy
    end
  end
endmodule
Y la pinta que tiene todo esto puesto en marcha es ésta (mi pantalla está estirada)
photo5767013031593750091.jpg
photo5767013031593750091.jpg (190.17 KiB) Visto 12473 veces

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por mcleod_ideafix » 24 Ago 2018, 20:30

Ahora, los ejercicios (he dejado versiones para ZX-UNO, ZX-DOS y UnAmiga). Abrid uno u otro según convenga. Necesitareis obviamente el cable programador de Xilinx, o algo equivalente, para mandar los .BIT a la FPGA, y el USB-Blaster en el UnAmiga para enviar el .SOF

EJERCICIO 1. Habeis visto que uso el modo de 640x480, 60 Hz, para lo cual genero un reloj de 25 MHz y genero los sincronismos según unos ciertos valores que se pueden encontrar en la página web que acompaña al código fuente. Pues bien, el primer ejercicio consiste en modificar el diseño para que genere otro modo de pantalla. Sugerencias:
- Modo de 800x600, 60 Hz. ModeLine "800x600" 40.00 800 840 968 1056 600 601 605 628 +HSync +VSync
- Modo de 1024x768, 60 Hz. ModeLine "1024x768" 65.00 1024 1048 1184 1344 768 771 777 806 -HSync -VSync
Para cada sugerencia, hay que cambiar:
- En videosyncs.v , los parámetros ofrecidos en el ModeLine. Copiarlos cada uno en su sitio y ya está. En la polaridad, poned 0 o 1 según que lo que diga en el ModeLine sea + o -.
- En update.v, XMAX e YMAX deben actualizarse según lo que hayais puesto: 800x600, o 1024x768 para que el sprite sepa dónde termina ahora la pantalla (si no cambiais esto, el fantasmita seguirá rebotando al llegar al final de una pantalla de 640x480 pixeles)
- En reloj.v (esto sólo si estais usando Xilinx, obviamente), hay que cambiar los valores del reloj que se va a generar ahora para la VGA. En la primera sugerencia, debe ser de 40 MHz. En la segunda, de 65 MHz. Para ello, abrid ese fichero y buscar este párrafo de código:

Código: Seleccionar todo

   DCM_SP #( .CLK_FEEDBACK("1X"), .CLKDV_DIVIDE(2.0), .CLKFX_DIVIDE(28), 
         .CLKFX_MULTIPLY(14), .CLKIN_DIVIDE_BY_2("FALSE"), 
         .CLKIN_PERIOD(20.000), .CLKOUT_PHASE_SHIFT("NONE"), 
         .DESKEW_ADJUST("SYSTEM_SYNCHRONOUS"), .DFS_FREQUENCY_MODE("LOW"), 
         .DLL_FREQUENCY_MODE("LOW"), .DUTY_CYCLE_CORRECTION("TRUE"), 
         .FACTORY_JF(16'hC080), .PHASE_SHIFT(0), .STARTUP_WAIT("FALSE") ) 
Los valores interesantes son: CLKFX_MULTIPLY y CLKFX_DIVIDE. La frecuencia final se obtiene multiplicando 50 por el valor de CLKFX_MULTIPLY, y el resultado se divide (con decimales) por el valor de CLKFX_DIVIDE. Con los valores que veis ahí, la frecuencia generada es de 50*14/28 = 25 MHz.
Para obtener 40 MHz, puede optarse por multiplicar por 16 y dividir entre 20 (¿y por qué no multiplicar por 8 y divdir entre 10? Pues porque para que el módulo que genera los relojes funcione, la frecuencia que se obtiene al multiplicar el reloj externo por el valor de CLKFX_MULTIPLY debe estar entre un mínimo y un máximo).
Para 65 Mhz, los valores válidos (creo) son: multiplicar por 13 y dividir entre 10.

Una vez que le cojais el truco a esto de cambiar de modo, probad con algún modo esotérico de los que se listan en esa web (siempre y cuando vuestro monitor lo admita)

EJERCICIO 2. Cuando no se pinta el fantasmita, se pinta un fondo de pantalla, que en la versión actual es un entramado de cuadrados de distintos colores. Esto se genera en fondo.v . El ejercicio consiste en cambiar este módulo para que el fondo sea otro distinto. Sugerencias:
- Un fondo de un único color (esto es fácil, ¿verdad?)
- Un fondo con un color diferente por linea (se puede usar el valor de vc que se incrementa en cada línea)
- Un fondo con un color diferente por cada columna vertical (se puede usar el valor de hc que se incrementa en cada columna)
- Un fondo con rejilla cuadriculada (color de fondo, color de rejilla, y tamaño de rejilla a elegir)
- Un fondo con cuadrados de ajedrez blancos y negros de un determinado tamaño (por ejemplo, 32x32). Fijarse en cómo se genera el fondo con el código dado, porque es parecido a lo que pido.
- Un fondo leido de una trama en plan mosaico, que se almacenaría en una memoria en este módulo, y que se leería de una forma similar a cómo se lee el sprite. Se aconseja que la trama sea un cuadrado con dimensiones potencia de 2. En este caso, el módulo fondo deberá tener un reloj, ya que se necesita para poder usar la memoria interna de la FPGA.

EJERCICIO 3. Si se estudia el módulo fantasma_rebotando.v que es como el contenedor de todos los módulos comentados, se verá que la estructura para pintar la pantalla es:
- Se genera para el pixel actual, un color que será el color de fondo.
- El color de fondo se usa como entrada al módulo sprite. La salida del módulo sprite será el color a pintar (que será el del sprite, o el del fondo, eso lo decide el propio módulo sprite)
Pues bien, esto se puede extender a más sprites. Se puede instanciar (usar) el módulo sprite tantas veces como queramos (traducido: tantas veces como espacio tengamos en la FPGA), y conectarlos en una suerte de daisy chain. El color de fondo generado va como entrada al módulo del primer sprite, la salida de éste sería la entrada del módulo del segundo sprite, la salida de éste sería la entrada al módulo del tercer sprite, y así, hasta la salida del último módulo de sprite que hayamos definido, que iría a la pantalla. La prioridad de los sprites vendría dada por este daisy chain: el módulo del sprite más "cercano" a la salida de pantalla tendrá más prioridad que un módulo que esté más "lejos" de la salida. El fondo (el primer módulo y por tanto el más lejando) sería el que tendría menor prioridad.
El ejercicio consistiría entonces en conseguir tener tres fantasmas rebotando en pantalla. Cada fantasma necesita:
- Un módulo sprite independiente (e idealmente, con un dibujo del sprite diferente, para distinguirlos en pantalla -no usar el verde- )
- Un módulo update independiente (con valores iniciales diferentes) para cada sprite

Para ayudar en este último ejercicio, se pueden usar estas versiones ligeramente modificadas de los módulos en sprite.v y update.v

sprite.v modificado

Código: Seleccionar todo

module sprite (
  input wire clk,
  input wire [10:0] hc,   // posicion X de pantalla
  input wire [10:0] vc,   // posicion Y de pantalla
  input wire [10:0] posx, // posicion X inicial del sprite
  input wire [10:0] posy, // posicion Y inicial del sprite
  input wire [7:0] rin,   // color de pantalla
  input wire [7:0] gin,   // proveniente del
  input wire [7:0] bin,   // modulo anterior (el fondo, por ejemplo)
  output reg [7:0] rout,  // color de pantalla
  output reg [7:0] gout,  // actualizado
  output reg [7:0] bout   // segun haya que pintar o no el sprite
  );

  parameter
    DEFSPRITE = "fantasma_rojo.hex",
    TRANSPARENTE = 24'h00FF00;  // en nuestro sprite el verde es "transparente"

  localparam
    TAM          = 11'd16;      // tamaño en pixeles tanto horizontal como vertical, del sprite

  reg [23:0] spr[0:255];   // memoria que guarda el sprite (16x16 posiciones de 24 bits cada posicion)
  initial begin
    $readmemh (DEFSPRITE, spr);  // inicializamos esa memoria desde un fichero con datos hexadecimales
  end
  
  wire [3:0] spr_x = hc - posx; // posicion X dentro de la matriz del sprite, en función de la posicion X actual de pantalla y posicion inicial X del sprite
  wire [3:0] spr_y = vc - posy; // posicion Y dentro de la matriz del sprite, en función de la posicion Y actual de pantalla y posicion inicial Y del sprite
  
  reg [23:0] color;  // color del pixel actual del sprite
  
  always @(posedge clk) begin
    color <= spr[{spr_y,spr_x}];  // leemos el color del pixel y lo guardamos (la posición del pixel podría ser completamente erronea aquí)
    if (hc >= posx && hc < (posx + TAM) && vc >= posy && vc < (posy + TAM) && color != TRANSPARENTE) begin  // si la posicion actual de pantalla está dentro de los márgenes del sprite, y el color leido del sprite no es el transparente....
      rout <= color[23:16];  // en ese caso, el color de salida
      gout <= color[15:8];   // es el color del pixel del sprite
      bout <= color[7:0];    // que hemos leido
    end
    else begin
      rout <= rin;           // si no toca pintar el sprite
      gout <= gin;           // o bien el color que hemos leido es el transparente
      bout <= bin;           // entonces pasamos a la salida el color que nos dieron a la entrada
    end  
  end  
endmodule
Esta versión permite usar el módulo con dos parámetros, uno para especificar el fichero desde el cual se cargará la definición del sprite, y otro para indicar cuál es el color considerado transparente, que ahora podrá ser diferente para cada instancia de sprite.

update.v modificado

Código: Seleccionar todo

module update (
  input wire clk,
  input wire vsync,
  output reg [10:0] posx,
  output reg [10:0] posy
  );
  
  parameter
    XMAX = 11'd640,
    YMAX = 11'd480,
    INITX = 11'd320,
    INITY = 11'd240,
    INITDX = 1'b0,
    INITDY = 1'b0,
    TAM  = 11'd16;
    
  initial begin
    posx = INITX;  // incialmente, centro de la pantalla
    posy = INITY;  // para una pantalla de 640x480, claro.
  end
  
  reg vsync_prev = 1'b0;
  reg dx = INITDX;  // 0: mover a la derecha. 1: mover a la izquierda
  reg dy = INITDY;  // 0: mover hacia abajo. 1: mover hacia arriba
  
  always @(posedge clk)
    vsync_prev <= vsync;    
  wire actualizar_ahora = vsync_prev & ~vsync;  // momento en el que vsync pasa de 1 a 0 (flanco de bajada)
  
  always @(posedge clk) begin
    if (actualizar_ahora == 1'b1) begin
      if (posx == XMAX-TAM && dx == 1'b0 || posx == 11'd1 && dx == 1'b1)  // si llegamos al borde izquierdo o derecho, cambiar sentido de movimiento horizontal
        dx <= ~dx;
      if (posy == YMAX-TAM && dy == 1'b0 || posy == 11'd1 && dy == 1'b1)  // si llegamos al borde inferior o superior, cambiar sentido de movimiento vertical
        dy <= ~dy;
      posx <= posx + {{10{dx}}, 1'b1};  // si dx=0, esto hace que se sume 00000000001 a posx. Si dx=1, esto hace que se sume 11111111111 a posx.
      posy <= posy + {{10{dy}}, 1'b1};  // lo mismo, pero con dy y posy
    end
  end
endmodule
Esta versión permite parametrizar los valores iniciales de la posición X,Y del sprite, así como los valores iniciales de sus vectores de movimiento.
Adjuntos
fantasma_pacman_rebotando.zip
(59.15 KiB) Descargado 490 veces

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por mcleod_ideafix » 24 Ago 2018, 23:12

Un ejemplo de cómo quedaría el diseño aplicando los tres ejercicios:
- Pantalla a 1024x768
- Tres fantasmitas independientes
- Fondo hecho con un mosaico de 128x128 pixeles
IMG_9594.JPG
IMG_9594.JPG (4.93 MiB) Visto 12463 veces

Avatar de Usuario
yombo
Veroboard
Mensajes: 18
Registrado: 17 Ago 2018, 10:51

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por yombo » 25 Ago 2018, 14:47

Genial! Esto me vendrá muy bien, seguro. Espero hacer experimentos molones con una cámara OV7670.
ERROR:NgdBuild:455 - logical net 'yombo' has multiple driver(s):
ERROR:NgdBuild:924 - input pad net 'yombo' is driving non-buffer primitives:

Avatar de Usuario
neuro_999
PLA
Mensajes: 28
Registrado: 17 Ago 2018, 10:49

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por neuro_999 » 25 Ago 2018, 15:03

Ya te digo que esta cojonudo.
Lo de generar diferentes resoluciones a mi me viene genial para aprender.

Enviado desde mi ONE A2003 mediante Tapatalk


Avatar de Usuario
yombo
Veroboard
Mensajes: 18
Registrado: 17 Ago 2018, 10:51

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por yombo » 25 Ago 2018, 17:01

Comprobado, a 1024x768 son los valores correctos los que has puesto.
ERROR:NgdBuild:455 - logical net 'yombo' has multiple driver(s):
ERROR:NgdBuild:924 - input pad net 'yombo' is driving non-buffer primitives:

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por mcleod_ideafix » 25 Ago 2018, 23:08

Pues esperad que estoy preparando algo que a alguno lo va a sentar de culo :D : simulación de este diseño viendo el resultado en pantalla gráfica, sin FPGA ni ná, y todo con herramientas libres. En un ratito...

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por mcleod_ideafix » 26 Ago 2018, 03:36

Hoy ya se me fue el tiempo. Lo que sí he podido hacer es actualizar el .ZIP del proyecto e incluir al UnAmiga.

Avatar de Usuario
jepalza
Spartan 3
Mensajes: 226
Registrado: 14 Ago 2018, 18:51

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por jepalza » 26 Ago 2018, 06:12

Lo bajo y pruebo, a ver que tal.

Edito: probado y funcionando. (con la 17.1 de quartus) :D

Hark0
Veroboard
Mensajes: 7
Registrado: 17 Ago 2018, 10:34

Re: El fantasmita rebotando, en Verilog (y ejercicios para quien se atreva a hacerlos)

Mensaje por Hark0 » 30 Ago 2018, 20:14

ohhhhhhhhhhh

Que wapo!!!!

(tengo que acabar algunas cosas antes que tengo a medias... y me meto a hacer pruebas... pero esto me está gustando muuuuucho)

;-)

Responder

Volver a “Verilog / SystemVerilog”