Paso 02, decodificando la pantalla
Ahora que ya sabemos como generar la señal de vídeo, para generar una imagen solamente necesitamos saber que información de color enviar por la salida RGB para cada píxel.
Como tenemos una pantalla de 640x480 píxels y el Spectrum tiene una resolución de 256x192 píxels voy a hacer lo siguiente para llenar la pantalla. Voy a aplicar un zoom de x2 a la imagen quedando en 512x384 píxels. El resto lo mostramos como borde lo que nos da 640-512=128, es decir, 64 píxels a derecha e izquierda y 480-384=96, es decir, 48 píxels por encima y por debajo. Hacer el zoom x2 es muy sencillo, simplemente hay que dividir entre 2 el contador horizontal y el vertical a la hora de calcular las posiciones de memoria que contienen la información para cada píxel.
La información de cada píxels está en dos posiciones de memoria, una indica si se ve el color de tinta o de papel y la otra los atributos (tinta, papel, brillo y flash). Como sólo podemos leer una posición de memoria cada vez, vamos a usar dos registros para guardar estos datos. Esto hay que hacerlo para cada 8 píxels (un byte).
Además, necesitamos obtener la información antes de que se vaya a pintar cada bloque de 8 píxelsy hay que conservarla mientras se pintan y a la vez leemos la información de los 8 siguientes. Por lo que necesitamos otros dos registros más.
Aquí lo complicado es tener el dato en el momento justo. En una FPGA las transferencias de datos son síncronas, esto es, todo pasa en los flancos de la señal de reloj. Por ejemplo, en un flaco positivo del reloj asignas la dirección a leer y al siguiente flanco obtienes el dato. Igualmente, los registros que guardan la información hacen la transferencia en un flanco de reloj.
Por último, como mientras se pinta un bloque de 8 píxels hay que cargar los datos de color de los 8 siguientes, uso un contador adelantado 16 pulsos de reloj (8x2 del zoom) que solo cuenta las coordenadas de pantalla, 0 a 511 (resolución horizontal) y 0 a 383 (resolución vertical). De esta forma, cuando voy a pintar por ejemplo el píxel (23,5) que contador vale (7, 5), o en el caso especial de los primeros 8 píxels (3, 0) valdría (499, 383).
El procedimiento queda de la siguiente forma, para cada 16 incrementos del contador horizontal hacemos lo siguiente (cuento de 0 a 15):
- 0: asignamos al bus de direcciones la dirección de memoria que contiene que 8 píxels están encendidos o apagados del próximo byte.
- 1: la memoria devuelve el dato.
- 2: traspasamos el dato al registro que guarda la información sobre los siguientes 8 píxels.
- 8: asignamos al bus de direcciones la dirección de memoria que contiene los atributos.
- 9: la memoria devuelve el dato.
-10: traspasamos el dato al registro que guarda la información sobre los atributos de los siguientes 8 píxels.
-15: traspasamos los datos de los dos registros a los otros dos que mantendrán la información de color de los 8 píxels que se van a pintar mientras se lee la de los 8 siguientes.
- En el resto de 'slots' no hacemos nada.
Una vez que tenemos la información de bitmap y atributos ya es cuestión de ver si el píxel que se va a pintar está encendido o apagado y en función de eso, si tiene o no flash o brillo, escoger un color de la paleta.
Código: Seleccionar todo
process(clock25)
variable bpre : std_logic_vector(7 downto 0); -- precarga del bitmap
variable apre : std_logic_vector(7 downto 0); -- precarga de atributos
variable i, p : std_logic_vector(2 downto 0); -- i = INK, p = PAPER
variable b, c : integer; -- b = nº de píxel (de los 8 de cada byte) que se va a pintar
-- c = color resultante a pintar después de tener en cuenta si es INK o PAPER, el brillo y el flash
begin
if rising_edge(clock25) then
-- contadores de la señal VGA
if x < 799 then x <= x+1;
else
x <= (others => '0');
if y < 524 then y <= y+1;
else
y <= (others => '0');
f <= f+1; -- contador de flash
end if;
end if;
-- señales de sincronismo
if x >= 640+16 and x < 640+16+96 then hs <= '0'; else hs <= '1'; end if;
if y >= 480+10 and y < 480+10+ 2 then vs <= '0'; else vs <= '1'; end if;
-- zona visible
if x >= 64 and x < 64+512 and y >= 48 and y < 48+384 then
-- contador de coordenadas de pantalla adelantado 16 pulsos
if x = 64+512-16 and y = 48+383 then xy <= (others => '0'); else xy <= xy+1; end if;
-- proceso de captura de datos desde la memoria
if xy(3 downto 0) = "0000" then va <= xy(17 downto 16)&xy(12 downto 10)&xy(15 downto 13)&xy(8 downto 4); end if;
if xy(3 downto 0) = "1000" then va <= "110"&xy(17 downto 13)&xy( 8 downto 4); end if;
if xy(3 downto 0) = "0010" then bpre := vd; end if;
if xy(3 downto 0) = "1010" then apre := vd; end if;
if xy(3 downto 0) = "1110" then bmap <= bpre; attr <= apre; end if;
-- calculamos en indice dentro de la paleta de colores
-- valores de INK y PAPER
b := 7-to_integer(unsigned(x(3 downto 1)));
i := attr(2 downto 0);
p := attr(5 downto 3);
-- color del píxel en función de si está encendido o apagado y el flash
if attr(7) = '1' then
if f(5) = '1' then
if bmap(b) = '0' then c := to_integer(unsigned(i)); else c := to_integer(unsigned(p)); end if;
else
if bmap(b) = '0' then c := to_integer(unsigned(p)); else c := to_integer(unsigned(i)); end if;
end if;
else
if bmap(b) = '1' then c := to_integer(unsigned(i)); else c := to_integer(unsigned(p)); end if;
end if;
-- atributo de brillo
if attr(6) = '1' then c := c+8; end if;
-- color definitivo
rgb <= palette(c);
elsif x < 640 and y < 480 then
-- zona del borde
rgb <= x"700";
else
-- zona no visible
rgb <= x"000";
end if;
end if;
end process;