MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX
1
Implementación Implementación de Simon Says por Medio de Máquinas de Estados en FPGA Spartan 3 Utilizando el Controlador VGA (Noviembre 2009) Nataly Medina, Francisco J. Díaz, Alejandro Jiménez, CITEDI-IPN, Sistemas Digitales
Resumen ²El siguiente documento presenta la implementación de un juego clásico llamado en inglés Si mon mon Says, mismo que muestra en pantalla los colores del juego por medio del puerto VGA. Por otra parte, se llevó a cabo el proyecto utilizando FPGA de XILINX Spartan 3 mediante el uso del lenguaje VHDL para su estructura.
² FPGA, SPARTAN, VGA, VHDL ice de Térmi nos nd ic nos ² Í nd
I. I NTRODUCCIÓN IMON Says Says es un juego electrónico electrónico creado creado en la década década de los 80¶s por Milton Bradley, el cual, su objetivo principal es recordar secuencias de colores y originalmente sonidos que el computador presenta. Después de esperar, el usuario debe ir introduciendo la secuencia mostrada en el orden correcto, ayudándose de su memoria visual y sonora. Si lo consigue, éste responderá con una secuencia más larga, y así sucesivamente. Si falla, el usuario debe volver a empezar. Los distintos niveles de dificultad van aumentando la velocidad de la secuencia a repetir.
S
II. I NTRODUCCIÓN AL CONTROLADOR VGA DE SPARTAN 3 Para implementar un controlador VGA, es necesario tomar en cuenta las siguientes consideraciones consideraciones técnicas: a)
Operación básica de un monitor CRT
Para operar, la parte denominada como electron gun genera un destello de electrones y eventualmente se topan contra una pantalla fosforescente. La luz es emitida en el instante en el que los electrones despliegan un punto en la pantalla fosforescente. La intensidad del destello del electrón y el brillo del punto son determinados por el nivel de voltaje de la señal externa de entrada de video, llamada como mono, la cual es una señal analógica en el que su voltaje se encuentra entre 0 - 0.7V.
Una bobina de deflexión vertical y una de deflexión horizontal fuera del tubo, producen campos magnéticos para controlar cómo los destellos de los electrones se dispersarán a través de la pantalla; actualmente en los monitores, los electrones se dispersan mediante un patrón determinado, de izquierda a derecha, de arriba hacia abajo. La siguiente figura muestra el diagrama básico de un monitor CRT y el patrón de escaneo de electrones en pantalla.
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX Los osciladores internos del monitor y amplificadores generan forma de ondas del tipo diente de sierra, para controlar las dos bobinas de deflexión. Se tienen dos señales externas de sincronización h sync y vsync, que controlan las señales diente de sierra, estas señales son digitales. La operación básica de un monitor CRT es similar, excepto en el que los destellos de electrones son proyectados como puntos fosforescentes rojo, verde y azul (RGB) en pantalla.
2 c)
Controlador de Video
El controlador de video genera las señales de sincronización y da salida a los datos de los pixeles de manera seriada. El controlador VGA contiene un circuito de sincronización y un circuito generador de pixeles, como se muestra a continuación:
Los tres puntos se combinan para formar un pixel; se puede ajustar los niveles de voltaje de las tres señales de entrada de video para obtener el color de pixel deseado. b)
Puerto VGA en la tarjeta Spartan 3
El puerto VGA tiene cinco señales activas, incluyendo las señales de sincronización horizontal y vertical h sync y vsync, más tres señales de video para los destellos rojo, verde, y azul como se muestran a continuación, además de su representación por cada terminal: Las señales de h sync y vsync son decodificadas por contadores internos, los cuales sus salidas son señales pixel_x y pixel_y, que indican la posición relativa del escaneo y esencialmente la ubicación del pixel actual. El circuito vga_sync genera la señal de video_on para indicar si se habilita o deshabilita el display. El circuito generador de pixeles genera las señales de video relacionadas con las señales RGB. d)
Una señal de video es una señal analógica y el controlador de video utiliza un controlador analógico-digital. Si una señal de video es representada por una palabra con N-bits, puede ser convertida a × . Las tres señales de video pueden generar combinaciones de colores, por lo que los posibles colores a generar se muestran en la siguiente tabla:
Sincronización del VGA
El circuito de sincronización de video genera la señal h sync, la cual especifica el tiempo requerido para atravesar (escanear) una fila, y la señal vsync que especifica el tiempo requerido para recorrer la pantalla entera. El proyecto se ha basado en un tamaño de pantalla de 640x480 pixeles a 25MHz, lo que significa que 25M pixeles son procesados en un segundo. -
Sincronización horizontal
Un periodo de la señal h sync contiene 800 pixeles y pueden ser divididos en cuatro regiones: Región en donde los pixeles actualmente se despliegan en pantalla. El tamaño de esta región es de 640 px. Retrace: Región en el que los destellos de los electrones regresan al lado izquierdo para volver a trazar la fila. Margen Derecho e Izquierdo: La señal de video se deshabilita en esta región, el tamaño de la misma es de 16px. Display:
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX
3
green_color : out bit; red_color : out bit; yellow_color : out bit; blue_color :out bit; sec_sel1 : in bit; sec_sel2 : in bit; start : in bit); end SimonSays;
es el reloj principal, mantiene el flujo del programa constante. Las definiciones para utilizar VGA están relacionadas con las variables red_out hasta vs_out , definen el RGB y las frecuencias de deflexión horizontal y vertical para los destellos de los electrones. clk50_in
es una variable externa que decide qué tan rápido corre el programa; a su vez tenemos botones de entrada que permite al usuario mantener el juego, los botones son green_in hasta blue_in. Se tiene además un botón de inicio start. mode
-
Sincronización Vertical
Un periodo de la señal vsync contiene 525 pixeles y pueden ser divididos en cuatro regiones: Región en donde los pixeles actualmente se despliegan en pantalla. El tamaño de esta región es de 480 px. Retrace: Región en el que los destellos de los electrones regresan a la parte superior de la pantalla para volver a trazar la columna. Margen Inferior y Superior:La señal de video se deshabilita en esta región, el tamaño de la misma es de 10px y 33px respectivamente. Display:
III. IMPLEMENTACIÓN EN VHDL Definiendo la entidad, se compone de lo siguiente: entity SimonSays is port ( clk50_in : in std_logic; red_out : out std_logic; green_out : out std_logic; blue_out : out std_logic; hs_out : out std_logic; vs_out : out std_logic; mode : in bit; green_in : in bit; red_in : in bit; yellow_in : in bit; blue_in : in bit;
La arquitectura de modo comportamiento es como sigue: architecture Behavioral of SimonSays is signal clk25 : std_logic; signal hcounter : integer range 0 to 800; signal vcounter : integer range 0 to 521; -- Color actual signal color: std_logic_vector (2 downto 0); -- Tipo para las secuencias type secuence is array(0 to 31) of character; -- Tipo de estados type state_type is (s0, s1, s2, s3, s4, s5, s6); -- Estados signal next_state, curr_state: state_type; -- Asignacion de los colores para VGA -RGB constant black : std_logic_vector (2 downto 0) := "000"; constant blue: std_logic_vector (2 downto 0) := "001";
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX
4
constant green: std_logic_vector (2 downto 0) := "010"; constant red: std_logic_vector (2 downto 0) := "100"; constant yellow: std_logic_vector (2 downto 0) := "110"; constant white: std_logic_vector (2 downto 0) := "111";
begin if clk25'event and clk25='1' then case curr_state is
signal sec0: secuence := "yrgybrygrygrbryrbyrybgrbrgbrgbgy";
Se declara la señal de color, que es el color a desplegarse en el momento actual, las secuencias que son de un nuevo tipo, con un arreglo de treinta y dos espacios, definen la secuencia de caracteres del juego; además, se declaran las variables que definen los estados en el que se encuentra el juego, y los sucesivos estados. Se definen los colores de forma constante, los mismos se asignan a la señal de control. Por otra parte, la siguiente función requiere como parámetro de entrada un char que identifica al color, y a su vez regresa el color correspondiente: function char_to_color(c: character) return std_logic_vector is begin case c is when 'g' => return green; when 'r' => return red; when 'y' => return yellow; when 'b' => return blue; when others => return black; end case; end char_to_color;
Los siguientes, son los procesos de los estados y el proceso de visualización por medio del VGA: -- Generar clock de 25Mhz process (clk50_in)
when s0 => -- Loop hasta "start" switch if start = '1' then level_cnt := 0; global_cnt := 0; aux_cnt := 0; input := "000"; curr_state <= s1; else color <= white; curr_state <= s0; end if; global_cnt es una variable global encargada de contar
desde 0 hasta los 32 colores del arreglo de caracteres, misma que comienza a contar hasta un tope, llamado level_cnt , el mismo indica hasta el nivel en el cual se encuentra el jugador. input es la variable donde guarda las entradas de los botones, y curr_state almacena el estado de la máquina
de estados. Por cada ciclo de clk25 se recorre la máquina de estados. Esperando START start := '1'
Despliega pantalla blanca y luego pantalla negra
s5
Botón correcto pero no se ha completado secuencia
Comprobar botón
s0 d o n a s i o o p r e r r e c t n o ó t c B o e s i n
s4
Botón secuencia y secuencia se ha completado
START start = '1'
s6
s1 Desplegando patrón de colores global_cnt /= level_cnt
Se desplegó patrón completo
Botón presionado input /= "000"
global_cnt >= level_cnt
s3 Esperando lectura de botón
begin
s2 Despliega pantalla blanca y luego pantalla negra
input := "000"
if clk50_in'event and clk50_in='1' then clk25 <= not clk25; end if; end process; -- Clock de 1 Hz (modo normal) o 3 Hz (modo rapido) p1: process (clk25, curr_state) variable variable variable variable variable variable downto 0);
global_cnt: integer; level_cnt: integer; aux_cnt: integer; cnt: integer; fail: boolean := false; input: std_logic_vector (2
El estado s0 se encuentra dentro del ciclo hasta que el botón de inicio start tenga el valor de 1, de lo contrario, decimos que curr_state es él mismo, y se manda a pantalla el color blanco por medio del VGA. Cuanto start se encuentra en 1, se inicializan todas las variables en 0, y hacemos curr_state igual al estado 1(s1). when s1 => if mode = '0' then cnt := cnt + 1; elsif mode = '1' then cnt := cnt + 3;
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX end if; if cnt >= 25000000 then
5 aux_cnt := 0; curr_state <= s3; end if; end if;
cnt := 0; color <= char_to_color(sec0(global_cnt)); if global_cnt >= level_cnt then global_cnt := 0; input := "000"; curr_state <= s2; else global_cnt := global_cnt + 1; end if; end if;
El estado s1 es el encargado de desplegar el patrón de colores, el mismo cuenta con dos modos para desplegar: un color por segundo o tres colores por segundo, mismo que se denota con la variable mode que a su vez es un botón de entrada en la tarjeta Spartan; el modo decide si a la variable contador cnt se le suma +1 o +3. Cuando esta variable llega a 25000000 (que sería equivalente a un Segundo si la suma corresponde a +1), se despliega el primer color por medio de la siguiente línea de código: color <= char_to_color(sec0(global_cnt));
en donde sec0 es la secuencia declarada anteriormente correspondiente a los colores. El contador global se encarga de realizar las iteraciones de ellos mismos, y la función char_to_color regresa el color en forma de señal, y a su vez se asigna al color por desplegar.
El estado s2 realiza la lógica para que la pantalla cambie de color blanco al negro, misma que sirve para tener condición visual de que es turno del usuario para jugar. El contador auxiliar permite saber si es la primera o segunda vez que se iteró ese estado, y de esta manera despliega diferente color. when s3 => if green_in = '1' then input := green; elsif red_in = '1' then input := red; elsif yellow_in = '1' then input := yellow; elsif blue_in = '1' then input := blue; else input := "000"; -- Nothing end if; if input /= "000" then curr_state <= s4; end if;
El estado s3 es encargado de leer las entradas realizadas por los botones. Este estado va a ciclar en sí mismo hasta que alguno de los botones sea seleccionado, en caso contrario, si ninguno de los botones es seleccionado se tendrá que input := "000";, en el momento que input /= "000", es decir, que la entrada sea diferente a cero, se procede a seguir con el estado s4.
Cuando global_cnt >= level_cnt , se define que se ha llegado al límite máximo correspondiente al nivel actual del juego, lo cual significa que las variables contadoras se reinician en 0, ya que no deben desplegar más allá de los when s4 => colores del arreglo. when s2 => if mode = '0' then cnt := cnt + 1; elsif mode = '1' then cnt := cnt + 3; end if;
cnt := cnt + 1; if cnt >= 10000000 then cnt := 0;
if input = char_to_color(sec0(aux_cnt)) then color <= input;
cnt := 0;
if aux_cnt >= level_cnt then aux_cnt := 0; level_cnt := level_cnt + 1;
if aux_cnt = 0 then color <= white; aux_cnt := 1; else color <= black;
curr_state <= s5; elsif aux_cnt < level_cnt then aux_cnt := aux_cnt + 1; curr_state <= s3; elsif aux_cnt >= 31 then
if cnt >= 6000000 then
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX curr_state <= s6; end if; else curr_state <= s0; end if; end if;
6 Otra de las consideraciones está relacionada con la condición del estado s4, si el contador auxiliar continúa siendo menor que el contador de nivel, se debe de seguir aumentando el contador para que así el jugador trate de recordar toda la secuencia.
Por otra parte, se considera la última condición relacionada determinado retraso, esto evita el con la comparación del contador auxiliar, si es mayor a 31 rebote que se pueda presentar entre los botones, además de significa que el jugador ha logrado recordar los 32 colores y se que es necesario un momento para que el usuario deje de manda al estado s6 , el mismo genera un patrón de colores que presionar el botón, sino se leen múltiples entradas. hace que se repita indefinidamente para expresar que el jugador ha ganado el juego. Cuando el retraso ha finalizado, el siguiente paso es comprobar si el jugador ha seleccionado el botón correcto, when s6 => -- Jugador gana para ello se hace la comparación entre la entrada y el contador auxiliar, encargado de ciclar cada posición de la secuencia; si if mode = '0' then cnt := cnt + 1; el jugador se ha equivocado, el programa se reinicia al estado s0, lo cual significa que el usuario ha perdido el juego y deberá elsif mode = '1' then cnt := cnt + 3; comenzar de nuevo; en caso contrario, si ha acertado, se end if; asigna el color de la entrada del usuario para que el mismo tenga retroalimentación visual. El estado s4 cuenta con un
if cnt >= 12000000 then
Para ello, se requiere comprobar algunas consideraciones, primeramente verificando que el si el contador auxiliar ha llegado a ser equivalente al contador de nivel, significa que el jugador ha terminado satisfactoriamente esta ronda de juego, por lo que se incrementa en +1 el contador de nivel, es decir, se tiene un color más que ciclar en todo el programa; se reinicia el contador auxiliar y se procede al estado siguiente s5. El estado s5 realiza la misma tarea que el estado s2, el mismo tiene la tarea de realizar una transición para que el usuario pueda presenciar un destello en pantalla, finalmente, este estado regresa al estado s1. when s5 => if mode = '0' then cnt := cnt + 1; elsif mode = '1' then cnt := cnt + 3; end if; if cnt >= 6000000 then cnt := 0; if aux_cnt = 0 then color <= black; aux_cnt := 1; elsif aux_cnt = 1 then color <= white; aux_cnt := 2; else color <= black; aux_cnt := 0; curr_state <= s1; end if; end if;
cnt := 0; if aux_cnt = 0 then color <= green; aux_cnt := 1; elsif aux_cnt = 1 then color <= blue; aux_cnt := 2; elsif aux_cnt = 2 then color <= red; aux_cnt := 3; elsif aux_cnt = 3 then color <= yellow; aux_cnt := 4; elsif aux_cnt = 4 then color <= white; aux_cnt := 5; elsif aux_cnt = 5 then color <= yellow; aux_cnt := 6; elsif aux_cnt = 6 then color <= red; aux_cnt := 7; elsif aux_cnt = 7 then color <= blue; aux_cnt := 8; elsif aux_cnt = 8 then color <= green; aux_cnt := 9; else color <= black; aux_cnt := 0; curr_state <= s0; end if; end if; end case;
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX
7
end if; end process;
green_out <= black(1); blue_out <= black(0); end if;
El siguiente proceso denominado como p2 realiza la tarea para desplegar en pantalla y hace uso del controlador de video del FPGA.
green_color <= '0'; red_color <= '0'; yellow_color <= '1'; blue_color <= '0';
p2: process (clk25, hcounter, vcounter) variable x: integer range 0 to 639; variable y: integer range 0 to 479; begin x := hcounter - 144; y := vcounter - 31; if clk25'event and clk25 = '1' then if color = green then if x < 640/2 and y < 480/2 then red_out <= color(2); green_out <= color(1); blue_out <= color(0); else red_out <= black(2); green_out <= black(1); blue_out <= black(0); end if; green_color <= '1'; red_color <= '0'; yellow_color <= '0'; blue_color <= '0'; elsif color = red then if (x > 640/2 and x < 640) and y < 480/2 then red_out <= color(2); green_out <= color(1); blue_out <= color(0); else red_out <= black(2); green_out <= black(1); blue_out <= black(0); end if; green_color <= '0'; red_color <= '1'; yellow_color <= '0'; blue_color <= '0'; elsif color = yellow then if x < 640/2 and (y > 480/2 and y < 480 )then red_out <= color(2); green_out <= color(1); blue_out <= color(0); else red_out <= black(2);
elsif color = blue then if (x > 640/2 and x < 640) and (y > 480/2 and y < 480 ) then red_out <= color(2); green_out <= color(1); blue_out <= color(0); else red_out <= black(2); green_out <= black(1); blue_out <= black(0); end if; green_color <= '0'; red_color <= '0'; yellow_color <= '0'; blue_color <= '1'; else if x < 640 and y < 480 then red_out <= color(2); green_out <= color(1); blue_out <= color(0); else red_out <= black(2); green_out <= black(1); blue_out <= black(0); end if; end if;
Las variables hcounter y vcounter se encuentran relacionadas con la definición antes mencionada h sync y vsync respectivamente. (Ver inciso d) El proceso permite la división de la pantalla en cuatro, para desplegar los colores del juego. Esto se hace haciendo las siguientes comparaciones, para verde, se ubica en la esquina superior izquierda con la siguiente comparación: x < 640/2 and y < 480/2, para los demás colores, se realizan las comparaciones similares a esta. Además de posicionar el color correspondiente, a su vez trae consigo mismo su representación digital en tres bits, la cual representa el color en la posición adecuada, y a su vez se asigna dicho bit a la señal de color correspondiente, tomando en cuenta que cada bit representa un color dentro del rango del RGB de tres bits. Cuando se hacen las siguientes asignaciones green_color <= '0'; red_color <= '1'; yellow_color <= '0'; blue_color <= '0';
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX
se asigna el valor digital para ser interpretados en los leds correspondientes al botón de cada color en específico. Para obtener la sincronización tanto vertical como horizontal con el VGA, tenemos lo siguiente: if hcounter > 0 and hcounter < 97 then hs_out <= '0'; else hs_out <= '1'; end if;
Los tiempos requeridos para ambas sincronizaciones se muestran en la siguiente tabla (Ver p.24, Xilinx, Spartan-3 Starter Kit Board User Guide)
8 end process;
end Behavioral;
Los tiempos requeridos para la sincronización vertical son las siguientes: -- Pulse width: Tpw = 1600 cycles (2 lines) @ 25 MHz -- Back porch: Tbp = 23200 cycles (29 lines) -- Display time: Tdisp = 38400 cycles (480 lines) -- Front porch: Tfp = 8000 cycles (10 lines) -- Sync pulse time (total cycles) Ts = 416800 cycles (521 lines) IV. CONCLUSIONES Simon Says es un juego que requiere de múltiples retroalimentaciones sensoriales para estimular la memoria del jugador. En nuestro caso fueron implementadas retroalimentación visual y de posición. Este juego sirvió como un buen ejercicio para el desarrollo de habilidades en el lenguaje VHDL, debido a sus múltiples consideraciones al momento de recrear su funcionamiento: lectura de señales de entrada implementación de arreglos en memoria manipulación de cadenas de caracteres manipulación de señales para el desarrollo de controlador VGA implementación de maquinas de estados y y y y
y
Siendo los tiempos requeridos para la sincronización horizontal los siguientes: -- Pulse width: Tpw = 96 cycles @ 25 MHz -- Back porch: Tbp = 48 cycles -- Display time: Tdisp = 640 cycles -- Front porch: Tfp = 16 cycles -- Sync pulse time (total cycles) Ts = 800 cycles if vcounter > 0 and vcounter < 3 then vs_out <= '0'; else vs_out <= '1'; end if; -- horizontal counts from 0 to 799 hcounter <= hcounter+1; if hcounter = 800 then vcounter <= vcounter+1; hcounter <= 0; end if; -- vertical counts from 0 to 519 if vcounter = 521 then vcounter <= 0; end if; end if;
En nuestro caso, se decidió implementar la lógica básica por medio de una maquina de estados finitos que se encargaba de realizar cada una de los diferentes pasos en la dinámica del juego, ya que en la práctica resulto ser lo más adecuado debido a la estructura del juego. Gracias a la implementación de este proyecto se h an aprendido numerosas técnicas de implementación y desarrollo en el lenguaje VHDL para FPGAs que será de gran ayuda en proyectos futuros. V. R EFERENCIAS [1] Pong. P. C hu, FPGA Prototyping by VHDL Examples. Cleveland State University.Wiley Insterscience 2008. [2] http://cse.spsu.edu/clo/research/DigilentSpartan3/VGATest.htm [3] Douglas L. Perry , VHDL.Programming by Example 4th Ed, McGraw.Hill. [4] VHDL Handbook, Hardi Electronics LAB
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX VI. APÉNDICE A
9
MÁQUINAS DE ESTADOS, VGA, SPARTAN 3, XILINX
10
VII. APÉNDICE B Esperando START start := '1'
Despliega pantalla blanca y luego pantalla negra
s5 o a d i o n t o s p r e r e c t ó n i n c o r o B s e
Botón correcto pero no se ha completado secuencia
Comprobar botón
s0
s4
Botón secuencia y secuencia se ha completado
START start = '1'
s6
s1 Desplegando patrón de colores global_cnt /= level_cnt
Se desplegó patrón completo
Botón presionado input /= "000"
global_cnt >= level_cnt
s3 Esperando lectura de botón input := "000"
s2 Despliega pantalla blanca y luego pantalla negra