Trabalho Nº 5
uP1: gerando sinal de vídeo
1. Instruções Gerais
- Grupos definidos no AVA;
- Ler o documento sobre Normas para entrega dos Relatórios;
- Ler atentamente todo o enunciado deste trabalho antes de realizá-lo;
- Consultar as referências adicionais e manuais sempre que necessário;
- Tornar todo o código disponível em um repositório ou pasta compartilhada com instruções de reprodução do experimento.
2. Objetivos da Prática
- Familiarizar-se com linguagens de descrição de hardware, como Verilog [1, 2];
- Conhecer o funcionamento do sinal de vídeo VGA [3, 4];
- Projetar ou modificar um processador simples em Verilog [5, 6, 7, 8];
- Descrever as atividades gerais desenvolvidas no trabalho em um relatório.
3. Materiais e Equipamentos
- Simulador EDA Playground (online);
- Laboratório Remoto de Lógica Digital - DC/UFSCar (online).
4. Fundamentos teóricos
Neste trabalho serão usadas duas abordagens para a geração de sinal de vídeo, comparando suas vantagens e limitações.
4.1. Hardware vs. software
Inicialmente, serão retomados os conceitos abordados na aula com o mesmo título desta subseção. O software é essencialmente temporal, enquanto o hardware é essencialmente espacial, embora possa haver nuances nessas associações.
Na abordagem de hardware, podem ser desenvolvidas soluções especializadas para um determinado problema, obtendo, em geral, melhor desempenho. Porém, dependendo do problema, a solução pode demandar alto esforço de projeto.
Na abordagem de software, adota-se um processador — um hardware de propósito geral — e desenvolve-se um programa usando suas instruções. A complexidade da programação e o desempenho obtido dependem do conjunto de instruções da arquitetura-alvo e da linguagem adotada. Em geral, são inversamente proporcionais: para obter melhor desempenho, é necessário aprofundar o conhecimento da linguagem e da arquitetura utilizadas.
4.2. Sinal de vídeo VGA
VGA significa Matriz de Gráficos de Vídeo (Video Graphics Array) e refere-se especificamente ao hardware de exibição introduzido com o computador IBM PS/2, em 1987. Com sua adoção generalizada, o nome passou a se referir aos padrões de exibição de computador analógico (por exemplo, monitores CRT), ao conector DB-15 (comumente conhecido como conector VGA) ou à resolução de 640 × 480 usada neste trabalho [3].

Figura 1: Pinos de interesse no conector VGA (DB-15) [3].
Serão considerados apenas cinco dos 15 pinos, apresentados na Figura 1: Vermelho (Red), Verde (Grn), Azul (Blue), Sincronismo Horizontal (HS) e Sincronismo Vertical (VS). Red, Grn e Blue são três sinais analógicos que especificam a cor de um ponto na tela, enquanto HS e VS fornecem uma referência de posição para sua exibição. Gerando adequadamente esses cinco sinais de acordo com a especificação de temporização VGA, é possível exibir imagens em um monitor [3].
Desenvolvido inicialmente para monitores CRT (tubos de raios catódicos), o padrão tem particularidades inerentes a esses monitores. Boa parte dos monitores LCD digitais também suporta o padrão VGA em diversas resoluções. A Figura 2 destaca as posições limítrofes para a resolução 640 × 480 que será gerada. Cada quadro inicia na posição 0,0 e segue na horizontal até concluir a linha. Um pulso em HS sinaliza o início da próxima linha, e assim sucessivamente. Ao final do quadro, um pulso em VS indica seu término e o início do próximo. Há intervalos específicos, que variam com a resolução, entre o último pixel da linha ou coluna e o pulso de sincronismo horizontal ou vertical.
![]()
Figura 2: Posição dos pixels na imagem gerada (adaptado de [3]).
Na Figura 3 estão indicadas as faixas de temporização para a resolução 640 × 480, detalhadas na Tabela 1.

Figura 3: Especificação de tempo para resolução de 640 × 480 a 60 Hz [3].
| Descrição | Notação | Tempo | Largura/Frequência |
|---|---|---|---|
| Pixel Clock | \(t_{clk}\) | 39,7 ns (± 0,5%) | 25,175 MHz |
| H Sync Time | \(t_{hs}\) | 3,813 µs | 96 pixels |
| H Back Porch | \(t_{hbp}\) | 1,907 µs | 48 pixels |
| H Front Porch | \(t_{hfp}\) | 0,636 µs | 16 pixels |
| H Address Video Time | \(t_{haddr}\) | 25,422 µs | 640 pixels |
| V Sync Time | \(t_{vs}\) | 0,064 ms | 2 linhas |
| V Back Porch | \(t_{vbp}\) | 1,048 ms | 33 linhas |
| V Front Porch | \(t_{vfp}\) | 0,318 ms | 10 linhas |
| V Address Video Time | \(t_{vaddr}\) | 15,253 ms | 480 linhas |
Tabela 1: Especificação de tempo para resolução de 640 × 480 a 60 Hz [3].
4.3. Primeiro exemplo: hardware
No primeiro exemplo, puramente em hardware, são usados os contadores CounterX e CounterY para gerar os sinais de sincronismo nos momentos corretos e garantir que os valores de RGB sejam apresentados somente na área visível (inDisplayArea):
CounterXmaxed e CounterYmaxed assumem valores maiores do que os da resolução, pois incluem os tempos adicionais de sincronismo já descritos:
Os valores dos contadores são comparados para calcular as variáveis vga_HS e vga_VS, que, depois de invertidas, dão origem aos pulsos de sincronismo. A variável inDisplayArea indica a área visível, na qual os valores dos pixels podem ser enviados; fora dela, os valores devem ser zerados. O sinal gerado é apresentado na Figura 4.


Figura 4: Sinal de vídeo VGA gerado via hardware.
O adaptador de vídeo possui 4 bits para cada canal de cor, possibilitando \(2^{12} = 4096\) combinações de cores [3, 4]. Para o canal vermelho, a tela é dividida ao meio verticalmente: a intensidade máxima é exibida do lado esquerdo e nenhuma intensidade, do lado direito. Para o canal verde, são usados os bits 6 a 3 de CounterX, gerando um gradiente de 128 pixels na horizontal, que cabe cinco vezes na largura da tela. Para o canal azul, o exemplo usa os bits 7 a 4 de CounterY, gerando um gradiente de 256 pixels na vertical. Esse gradiente cabe quase duas vezes na altura, com 224 pixels na parte inferior. Ao usar os bits 6 a 3 do mesmo contador, obtém-se um padrão quadrado, cuja última linha é cortada.
Este exemplo mostra que gerar imagens de acordo com a posição dos pixels na tela é relativamente simples, pois não é necessário armazenar informações adicionais.
4.4. Segundo exemplo: framebuffer
Um framebuffer é uma porção de memória usada para armazenar uma imagem. No segundo exemplo, essa técnica é usada com o uP1, ou seja, em uma abordagem por software. A memória foi modificada e agora possui portas adicionais de endereço (vaddr) e dados (vdata). Na resolução máxima e com a profundidade de cores adotada, seriam necessários 1,2 MB de memória, sem alinhamento. Além do espaço insuficiente, isso complicaria o acesso, pois dois canais de cor (4 bits) ficariam em uma palavra e o terceiro canal na palavra seguinte, misturado com o pixel seguinte.
A solução envolve reduzir a resolução e a profundidade de cores. No hardware, os dois bits menos significativos de cada canal são fixados em zero e usam-se apenas os outros dois. Isso permite armazenar um pixel nos seis bits menos significativos da palavra de memória, deixando os outros dois sem uso para facilitar o alinhamento. A intensidade máxima de cada canal cai de 15 (1111) para 12 (1100), resultando em \(2^6 = 64\) cores.
Para reduzir a resolução, o máximo divisor comum entre 640 e 480 é 160, que não é potência de 2. Considera-se então 128, a potência de 2 imediatamente inferior, e obtém-se 32 para a dimensão do pixel virtual. Se cada endereço de memória representar 32 × 32 pixels, a resolução seria 20 × 15. Como o padrão VGA não suporta essa resolução tão baixa, a resolução do hardware permanece em 640 × 480 e são criados “pixels virtuais” de 32 × 32, cada um armazenado em um endereço de memória. Ainda assim, seriam necessárias 300 posições de memória, mas há apenas 256. A solução é usar “pixels virtuais” de 64 × 64. Assim, há dez pixels na largura e 7,5 na altura, e a última linha é desenhada pela metade. A imagem pode ser representada com 80 palavras de memória na resolução 10 × 8.
No módulo de vídeo, são feitas modificações para: (i) obter um endereço de memória (vaddr) a partir da posição da tela; e (ii) receber os dados da memória (vdata) e enviá-los para o vídeo. As duas portas são adicionadas ao código, respectivamente como saída e entrada, junto com duas variáveis para calcular a linha e a coluna:
Para obter esses valores, os contadores são deslocados seis bits para a direita (divididos por 64) e combinados para obter a posição de memória desejada. O bit mais significativo é fixado em 1, usando as posições de memória 128 em diante. A parte mais baixa do endereço é composta pela linha:
Os valores recebidos da memória são atribuídos aos respectivos canais de cor nos bits mais altos, mantendo os bits mais baixos em zero:
O novo arranjo de memória é informado no arquivo tables.py do montador. São usadas 80 posições das 112 anteriormente não usadas:
As restrições de endereçamento continuam e se agravam, pois há um número limitado de bits para endereçar memória de código, dados e vídeo. Foram criadas instruções diferentes para acessar a memória de vídeo, mantendo as demais instruções como estavam. LDV e STVI não têm argumentos e fazem, respectivamente, carga e armazenamento em uma posição preestabelecida da memória de vídeo. A posição inicial é 128 e a final, 207. LDV carrega da posição corrente e STVI armazena e incrementa o endereço de vídeo para a próxima posição.
O programa a seguir busca cada endereço do vídeo, decrementa o valor em uma unidade e salva-o de volta. O montador reconhece o rótulo especial .video para inicializar a memória de vídeo com as palavras correspondentes às cores. No exemplo real, são adicionados dois laços aninhados para atrasar o programa e permitir a visualização, o que também exige um salto condicional.
Para suportar o adaptador de vídeo, é criado o endereço de vídeo vaddr, que aponta para o início do framebuffer após o reset do processador:
No estado de decodificação da instrução, é necessário diferenciar o endereço de acesso à memória, pois LOAD e STORE o codificam na instrução, enquanto LDV e STVI usam o novo ponteiro:
Por fim, LDV executa exatamente como LOAD, mas STVI incrementa o ponteiro e verifica se ele chegou ao final do framebuffer, retornando-o ao início:
A Figura 5 apresenta o quadro inicial e o obtido após algumas iterações:


Figura 5: Sinal de vídeo VGA gerado via hardware e software.
5. Procedimentos
Para a realização deste trabalho, o grupo deverá implementar obrigatoriamente no mínimo três configurações diferentes de hardware e/ou software e testá-las no laboratório virtual. As duas primeiras implementações serão realizadas por todos os grupos. A terceira pode ser escolhida livremente a partir do número 3 da lista a seguir e deverá ser informada com antecedência no fórum de dúvidas da disciplina. Cada grupo deverá escolher uma opção diferente:
- (obrigatório) Modificar o exemplo de hardware para gerar uma imagem que não seja baseada apenas nos contadores X e Y. Pode-se usar, por exemplo, uma função simples desses contadores;
- (obrigatório) Usando o uP1 atual, implementar uma animação significativamente diferente daquela fornecida. Por exemplo, deslocar uma imagem inteira na tela;
- Adaptar uma implementação do algoritmo de Mandelbrot para rodar no hardware. Será necessário usar uma biblioteca de ponto flutuante ou gerar IP cores no Vivado;
- Modificar o adaptador de vídeo atual do uP1 para funcionar com um mapa de caracteres (modo texto). Nesse caso, uma ROM é usada para armazenar o mapa e a memória de vídeo armazena qual caractere está em cada posição;
- Implementar ou adaptar o processador IAS [7], SAP-1 [8] ou outro qualquer [6] para funcionar com o adaptador de vídeo fornecido ou modificado;
- Modificar o framebuffer para usar a resolução 20 × 15 (com “pixel virtual” de 32 × 32) e executar o programa do item 2. Será necessário aumentar o tamanho da memória e o conjunto de instruções;
- Implementar uma animação com funções trigonométricas usando software e/ou hardware;
- Implementar um jogo simples usando software e/ou hardware.
Referências Bibliográficas
- [1] Playlist sobre Verilog
- [2] Verilog Tutorial
- [3] VGA Display Controller
- [4] Pmod VGA Reference Manual
- [5] uP1
- [6] Simple microprocessor using Verilog
- [7] IAS: instruções e simulador.
- [8] SAP-1: instruções e simulador.