Portal - FPGA para Todos

Pong com VGA

Introdução

Freeway and Pong 

Figura 1 - Um dos primeiros consoles a serem lançados

Fonte: Creative Commons

Jogos eletrônicos são um bem de consumo de custo médio que se popularizou rapidamente na primeira década do século XXI. Nos dias de hoje, a palavra “videogame” é utilizada para referenciar uma plataforma de processamento de jogos, como (as mais populares): Playstation3 e Xbox One. Entretanto, nem sempre foi assim, em 1972 o primeiro videogame lucrativo da história foi desenvolvido pela Atari Inc¹, e a este foi dado o nome de Pong. O jogo Pong era formado apenas por um monitor e o console de jogo (portanto um jogo era um videogame). Quanto a funcionalidade, baseava-se em duas paletas (denominadas paddle), controladas por dois jogadores diferentes, que “rebatiam” uma bola para impedir que a mesma alcançasse suas respectivas dead zones. Muito similar aos populares fliperamas da época. O objetivo deste artigo criar uma variação singleplayer deste jogo e utilizá-lo como exemplo de aplicação didática de desenvolvimento de projetos em VHDL para o kit de CPLD MAXV, utilizando o padrão VGA² como IHM (Interface Homem-Máquina) do jogo. O vídeo abaixo mostra um pequeno teste do jogo:

 

  

Lógica de Funcionamento do Sistema

A variação do Pong que será criada será do tipo singleplayer (apenas um jogador/paddle), portanto, o objetivo do jogo muda. Em vez de fazer com que a bola vá para a dead zone do adversário, o jogador deverá apenas brincará com ela, impedindo-a de cair em sua própria dead zone. Se a bola entrar em contato com sua dead zone, então a mesma volta a posição de início com uma nova cor. O movimento do paddle será controlado pelo comando de dois botões do kit, que o moverão para a direita e esquerda, e a bola (que não será uma bola, mas continuaremos a nos referir a ela como tal) terá seu movimento baseado no deslocamento do paddle. Se o paddle estiver em repouso, a bola é somente refletida. Se não, dependendo da direção do deslocamento do paddle (direita ou esquerda), o ângulo de reflexão da bola será incrementado ou subtraído.

 

Estrutura do Projeto para CPLD/FPGA

O projeto foi desenvolvido de modo hierárquico, com um arquivo de topo que incorpora dois blocos básicos:

  • sincr_vga.vhd, que gera os sinais de sincronismo (necessários para o padrão VGA) Sinais de posição do pixel;
  • pong.vhd, que gera os sinais de cor de saída (RGB) baseado na informação de posição gerada pelo sincronismo, definindo o formato e a posição da bola e do paddle.

 

Sincronismo

pong bloco quartus

Para padronizar e portanto facilitar o desenvolvimento de projetos que utilizam VGA, o componente sincr_vga.vhd foi desenvolvido. Basicamente ele funciona gerando dois sinais de sincronismo, Horiz_sync e Vert_sync, que deverão ser baseados em um clock fornecido e em outros dois sinais de saída denominados CONT_X e CONT_Y. Para entender a aplicação deste componente, deve-se compreender os princípios básicos do padrão VGA

 

no VGA como um “varredor”, que vasculha toda a área do monitor tratando-o como uma matriz, linha por linha e coluna por coluna. Portanto, através da observação dos sinais CONT_X e CONT_Y é possível inserir imagens e figuras nos locais desejados. Vale destacar que, tendo o monitor como parâmetro, os valores de CONT_X crescem da esquerda para a direita e de CONT_Y de cima para baixo.

Para mais informações sobre o componente: Padrão VGA.

Outros projetos que utilizam o componente Sincronismo: Memória RAM com Megafunção, Animação VGA e VGA para FPGA.

 

Imagens no Monitor

Para exibir imagens no monitor utilizando o sincr_vga.vhd, foi criado o componente pong.vhd. Basicamente, este trata CONT_X e CONT_Y como indicadores de coordenadas XY, ou seja, toda vez que o par CONT_X e CONT_Y indicarem uma posição pertencente a área da figura desejada, atribui-se ao sinal RGB o valor da cor do respectivo ponto. Por definição deste projeto, sempre que não houver figura para desenhar, a cor da tela deve ser preta. É essencial que se tenha conhecimento de como “desenhar” figuras no monitor com base em uma referência, pois como veremos adiante, será preciso mover estes objetos sobre o Monitor. Os objeto que necessitam ser desenhados são o paddle e a bola, e para padronizar o método de inserção de figuras utilizaremos o seguinte processo implícito, que com base nos sinais gerados por cada figura, as desenhará:

-- Processo Implícito de ajuste das cores
ajusta_RGB: RGB <=
RGB_paddle when desenha_paddle = '1' else
RGB_bola when desenha_bola = '1' else
"000";

Um breve esclarecimento sobre a constante e os sinais envolvidos:

RGB: Representa a cor de saída, que será mostrada no monitor.

RGB_paddle : É a constante de cor do paddle (vermelho).

desenha_paddle : Sinal que representa quando desenhar o paddle.

RGB_bola : É o sinal que representa a cor da bola (varia).

desenha_bola : Sinal que representa quando desenhar a bola.

 Portanto, quando desenha_paddle indicar para desenhar, então o valor de cor de saída será a constante RGB_paddle.

Se desenha_paddle não indicar a cor, e o sinal desenha_bola indicar, então o valor de cor de saída será o sinal RGB_bola.

Se não houver figuras a serem desenhadas, a saída de cor será “000” (preto).

 

Paddle

Para este projeto, o paddle pode ser representado como um simples retângulo vermelho. Os sinais e generics associados a ele são: A_paddle e L_paddle, generics que representam altura e largura do paddle respectivamente, e X_paddle e Y_paddle, sinais que juntos representam o vértice superior esquerdo do paddle (utilizado como referência para desenhar o restante da figura). Conhecendo os limites da iamgem para os respectivos eixos XY, é possível desenhá-la. O paddle pode ser definido da seguinte forma:

-- Desenha paddle
if(CONT_X < X_paddle + L_paddle) and (CONT_X > X_paddle)
and (CONT_Y < Y_paddle + A_paddle) and (CONT_Y > Y_paddle)then
desenha_paddle <= '1';
else
desenha_paddle <= '0';
end if;

Basicamente, o sinal desenha_paddle representa quando o par CONT_X e CONT_Y indica uma posição contida dentro da área desejada. Portanto a interpretação deste código é bastante simples: O domínio (área) do paddle é, no eixo X, de X_paddle até X_paddle + L_paddle, e no eixo Y, de Y_paddle até Y_paddle + A_paddle.

 

Bola (Losango)

Até o momento, viemos tratando erroneamente, o objeto a ser rebatido pelo paddle, simplesmente por “bola”. Entretanto, este não possui a forma de uma bola. Durante o desenvolvimento do projeto observou-se ser impossível tratar a área de uma bola sem usar uma das seguintes operações: Divisão, multiplicação ou raiz quadrada. Mas para fazer o uso destas operações é necessário utilizar funções que demandam de um grande número de elementos lógicos, e como o objetivo deste projeto é minimizar o uso de elementos lógicos para que este seja portável para o kit de CPLD MAX V, propôs-se que a figura tivesse, apesar de ser comumente uma bola, o formato de um losango. A área de um losango pode ser representada no plano cartesiano pela seguinte expressão:

Sendo R seu raio, X_0 e Y_0 a coordenada do centro do losango e X e Y o ponto a ser checado. Tendo a fórmula citada como parâmetro, e o centro da figura como referência, é possível definir a bola da seguinte forma:

-- Desenha bola na tela
if CONT_X > X_bola then
modulo_X := CONV_INTEGER(CONT_X - X_bola);
else
modulo_X := CONV_INTEGER(X_bola - CONT_X);
end if;
if CONT_Y > Y_bola then
modulo_Y := CONV_INTEGER(CONT_Y - Y_bola);
else
modulo_Y := CONV_INTEGER(Y_bola - CONT_Y);
end if;
if (modulo_X + modulo_Y) <= Raio then
desenha_bola <= '1';
else
desenha_bola <= '0';
end if;

Observe que a estrutura deste código é muito semelhante a presente no paddle. Primeiro, trata-se o operador de módulo, em seguida realiza-se as operações de subtração e soma, conforme a precedência, e por fim verifica-se se o valor encontrado é menor ou igual ao do que o raio. Gerando assim, o sinal desenha_bola, que informa quando CONT_X e CONT_Y indicam um ponto pertencente a área da “bola”.

 

Movendo as imagens sobre o plano

Como foi comentado no tópico anterior, para mover as figuras pelo plano, é preciso utilizar uma coordenada de referência. Portanto agora, deve-se deslocar estas coordenadas, para mover a figura:

 

Paddle

Durante o jogo, a posição do paddle no monitor é controlada por dois botões. Estes permitem ao jogador, deslocar o paddle horizontalmente (direita e esquerda). E por este motivo a referência utilizada foi um vértice pertencente ao paddle, observe quando o paddle é desenhado, sua área é definida em função do valor de X_paddle e Y_paddle, portanto se este par de valores forem alterados, a figura inteira estará sendo movida. A seguir a implementação do comando dos botões que movem o paddle:

if IncXN = '0' and X_paddle < LIM_X - L_paddle then
X_paddle <= X_paddle + variacao_paddle;
vel_paddle <= "01";
 
elsif DecXN = '0' and X_paddle > 0 then
X_paddle <= X_paddle - variacao_paddle;
vel_paddle <= "10";
else
vel_paddle <= "00";
end if;
 
end if;

IncXN e DecXN são botões ativos em baixo, X_paddle e Y_paddle são juntos o ponto de referência, variacao_paddle é a constante que representa quanto a posição do paddle variará horizontalmente e vel_paddle indica a direção de deslocamento do paddle. Como veremos adiante, para alterar o ângulo de reflexão da bola, será necessário consultar vel_paddle. Observe que antes de a posição ser incrementada/decrementada os limites do monitor são verificados. Sabendo que CONT_X varia de 0 a LIM_X, o resultado desta operação não pode “desobedecer” esta faixa.

É importante observar que enquanto o botão IncXN é pressionado o valor de vel_paddle é “01”, indicando que o paddle se desloca para a direita, e quando DecXN é pressionado, vel_paddle é “10”, indicando que o paddle se desloca para esquerda. Caso nenhum botão seja pressionado vel_paddle é “00” (indicando repouso).

 

Bola

Para mover a bola sobre o plano, será utilizado o mesmo princípio aplicado no paddle. Modifica-se o valor do ponto de referência da bola, que é seu centro (representado pelo par X_bola e Y_bola). Porém, diferentemente do paddle a bola não se deslocará baseando-se no comando de botões, esta deve se mover depois de um curto intervalo de tempo simulando o efeito de “velocidade”, na prática (em VHDL), significa que teremos dois sinais Vel(0) e Vel(1), que representarão o deslocamento horizontal e vertical respectivamente. Toda vez que a bola encontrar os limites do monitor, ou o paddle, tais velocidades serão modificadas, dando uma nova direção ao deslocamento da bola. Para este projeto, selecionamos a frequência de 10Hz, para ser o clock do deslocamento da bola, portanto, divide-se por seis o clock FIM_TELA (de origem do sincronismo), que por definição é de 60Hz. Observe que apenas acrescendo esse pequeno número de linhas depois de dividir o clock, o movimento da bola estará implementado:

-- Move a bola
X_bola := X_bola + CONV_STD_LOGIC_VECTOR(Vel(0),12);
Y_bola := Y_bola + CONV_STD_LOGIC_VECTOR(Vel(1),12);

 

Rebatimento da bola

Como citado anteriormente, a bola não deverá simplesmente avançar e ultrapassar os limites do monitor. Por este motivo, quando a bola se chocar com o paddle ou com um dos limites do monitor, para permanecer dentro da área permitida, ela será “rebatida”. A seguir será listado uma série de casos e suas respectivas soluções, que simularão todos as possíveis maneiras de a bola sair do monitor. Observe que todos casos ocorrem em função de vel_paddle (que indica o sentido da direção de deslocamento do paddle) e Vel (que é composto por uma componente horizontal, Vel(0), e uma vertical, Vel(1), que juntos indicam o ângulo de direção de deslocamento da bola).

 

Casos

1. Se a bola deslocar-se diagonalmente em direção a parede esquerda ou a parede direita e se chocar com uma delas, então, a bola deverá ser rebatida com o mesmo ângulo de reflexão.

2. Se a bola se chocar com a parede superior (teto), a componente vertical da velocidade deverá ser invertida.

3. Se a bola se chocar com o paddle, o ângulo de reflexão da bola dependerá do sentido do deslocamento horizontal do paddle.

    3.1. Se a bola se chocar com o paddle e este estiver em repouso (parado, vel_paddle = “00”), o ângulo de reflexão da bola deverá ser igual ao de entrada.

    3.2. Se a bola se chocar com o paddle e este estiver se deslocando para a direita (vel_paddle = “01”), o ângulo de reflexão da bola deverá receber um incremento na componente horizontal e inverter a componente vertical.

    3.3. Se a bola se chocar com o paddle e este estiver se deslocando para a esquerda (vel_paddle = “10”), o ângulo de reflexão da bola deverá receber um decremento na componente horizontal e inverter a componente vertical.

4. Se a bola se chocar com a parede inferior do monitor (chão), o jogo deverá reiniciará automaticamente, apenas mudando a cor da bola.

 

Soluções

Caso 1-) Para simular o rebatimento da bola na horizontal, basta inverter o sinal da componente horizontal da velocidade, Vel(0). Em VHDL:

-- Verifica parede esquerda
if X_bola <= CONV_STD_LOGIC_VECTOR(Raio,12) and (Vel(0) < 0) then
Vel(0) := -Vel(0);
end if;
-- Verifica parede direita
if X_bola >= CONV_STD_LOGIC_VECTOR(604-Raio,12) and Vel(0) > 0 then
Vel(0) := -Vel(0);
end if;

 

É possível observar que apenas são testados os limites horizontais do monitor e a posição da bola, e se o choque for identificado, a velocidade horizontal é invertida. Exemplo gráfico:

Antes

direita baixo-parado meio

Neste caso, a bola desloca-se em direção a parede direita.

 

Depois

esquerda baixo-meio parado

Após o rebatimento horizontal, o resultado acima foi obtido.

 

Caso 2-) Para simular o rebatimento da bola na vertical, basta inverter o sinal da componente vertical da velocidade, Vel(1). Em VHDL:

--Verifica teto
if Y_bola <= CONV_STD_LOGIC_VECTOR(Raio,12) and Vel(1) < 0 then
Vel(1) := -Vel(1);
end if;

Novamente, fica claro que apenas é identificado, ou não, o choque entre o teto e a posição da bola. Se identificado, ocorrerá a inversão da componente vertical. Exemplo gráfico:

 Antes

 teto cima

 Neste caso, a bola desloca-se em direção ao teto.

 

Depois

 paddle parado-baixo

Após o rebatimento vertical, o resultado acima é obtido. 

 

Caso 3-) Este é um caso especial que têm resultados variados e derivados da direção do deslocamento do paddle, os seguintes sub-casos demonstram as possíveis situações:

        Caso 3.1-) Para simular o rebatimento vertical, aplicamos o mesmo método utilizado no Caso 2, inverte-se a componente vertical da velocidade. Exemplo gráfico:

Antes

paddle parado-baixo

Neste caso, a bola desloca-se em direção ao paddle, estando este em repouso.

 

Depois

teto cima2

Após o rebatimento vertical, o resultado acima é obtido.

 

        Caso 3.2-) Para este caso, além da inversão da componente vertical, incrementaremos um pequeno valor da componente horizontal, para simular uma “abertura” no ângulo de reflexão no sentido horário. Exemplo gráfico: 

Antes

paddle direita-baixo esquerda

Neste caso, a bola desloca-se em direção ao paddle, estando este se deslocando para a direita.

 

Depois

paddle direita-cima reto

Após o rebatimento vertical e o incremento horizontal, é visível a mudança de ângulo para 90º.

 

        Caso 3.3-) Este caso, parece-se muito com o Caso 3.2, a única diferença é a abertura do ângulo (que é contrário ao Caso 3.2, no sentido anti-horário). Portanto em vez de um incremento, decrementaremos a componente horizontal. Exemplo Gráfico:

Antes

paddle esquerda-baixo

Neste caso, a bola desloca-se em direção ao paddle, estando este se deslocando para a direita.

 

Depois

paddle direita-direita cima

Após o rebatimento vertical e o decremento horizontal, é visível a mudança do ângulo de 90º para um valor superior.

 

Lembrando que o sinal responsável por indicar a direção do deslocamento do paddle, ou se o mesmo está em repouso, é o vel_paddle, se “00”, está em repouso, se “01”, se desloca para a direita e se “10”, se desloca para a esquerda. Em VHDL:

 

-- Verifica se a bola bate no paddle
if (X_bola + CONV_STD_LOGIC_VECTOR(Raio,12) >= X_paddle and X_bola - CONV_STD_LOGIC_VECTOR(Raio,12) <= X_paddle + L_paddle)
and (Y_bola + CONV_STD_LOGIC_VECTOR(Raio,12) >= Y_paddle) and Vel(1) > 0 then
Vel(1) := -Vel(1);
 
if vel_paddle = "10" then -- Caso paddle se deslocando para esquerda
 
if Vel(0) >= 0 then -- Bola indo para direita
Vel(0) := Vel(0) - 3; -- diminui Vx
else -- Bola indo para esquerda
Vel(0) := Vel(0) + 3; -- aumenta Vx
end if;
elsif vel_paddle = "01" then -- Caso paddle se deslocando para a direita
if Vel(0) >= 0 then -- Bola indo para direita
Vel(0) := Vel(0) + 3; -- aumenta Vx
else -- Bola indo para esquerda
Vel(0) := Vel(0) - 3; -- diminui Vx
end if;
 
end if;

 

 

Caso 4-) Neste caso, quando a bola se chocar com o chão, o jogo acaba. E por especificação do projeto, o jogo reinicia com a bola mudando de cor. Em VHDL:

-- Verifica o chão (Dead Line)
if Y_bola >= CONV_STD_LOGIC_VECTOR(480 - Raio,12) then
X_bola := CONV_STD_LOGIC_VECTOR(302,12);
Y_bola := CONV_STD_LOGIC_VECTOR(25,12);
cont_telas_bola := 0;
Vel(0) := 0;
Vel(1) := 10;
var_RGB_bola := var_RGB_bola + "001";
if var_RGB_bola = "000" then
var_RGB_bola := "001";
end if;
end if;

Observe que esta parte do código é muito similar ao RESET do sistema, e é exatamente isso, a única diferença é a mudança de cor da bola.Exemplo gráfico:

Antes

verde down

Neste caso, a bola desloca-se em direção ao paddle, estando este em repouso.

 

Depois

amarelin

Após o reinicio do jogo, o resultado acima é obtido.

 

Conclusão

Pela leitura do tópico sincronismo, fica claro que os sinais utilizados pelo conector VGA dependem diretamente do clock do kit. Portanto o projeto feito para o kit de CPLD MAXV possui algumas diferenças do desenvolvido para a DE0. Além disso, a DE0 permite que o usuário utilize até doze bits de cor para a saída VGA (no CPLD eram apenas três). Para deixar as versões do projeto mais idênticas possível, optou-se por criar um componente de sincronismo que possuí-se as constantes relacionadas ao componente VGA, portanto para solucionar o problema do clock basta alterar estas constantes. Já a questão dos bits de cor, é tratada na arquitetura de topo, visto que toda a parte de processamento dos bits de cor é feita no componente pong, a arquitetura de topo apenas transfere esses três bits para a saída de doze bits. Dessa forma se a saída do pong for "101" a arquitetura de topo passa o valor "111100001111" para a saída VGA.

 

Arquivos para Gravação

A seguir, os links para os projetos feitos em VHDL com o Quartus II:

Projeto para o kit de CPLD MAXV

Arquivo pof para teste e gravação (MAXV)

Projeto para a DE0

Arquivo pof para teste e gravação (DE0)

 

 

 

Quem está Online

Temos 38 visitantes e Nenhum membro online

Apoio

Login Form

 

Artigos Relacionados