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
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
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
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