Laboratório: Periférico Matricial

Laboratório: Periférico Matricial

Este laboratório propõe o desenvolvimento de periférico matricial. O objetivo principal é executar um projeto com nível de complexidade maior desde o planejamento até a implementação.

Ver histórico de mudanças

Changelog

Versão Data Descrição
v2025.1 28/mai/2025 Versão inicial
v2025.2 02/jun/2025 Adicionado exemplo de utilização da interface avalon
v2025.3 17/jun/2025 Removidas menções à cache

Disciplina

Este laboratório faz parte da disciplina MC613 - Laboratório de Circuitos Digitais. Ver oferecimento mais recente.

Entrega

  • A entrega deverá estar em único arquivo .ZIP contendo todos e apenas os arquivos listados abaixo.
  • O nome do arquivo ZIP deve ser RA<RA>.zip, onde <RA> é o RA do componente do grupo que fará a entrega. Por exemplo, RA123456.zip é a entrega do grupo do aluno com o RA 123456.
  • Os nomes dos arquivos dentro do ZIP devem ser seguidos.
  • Se mais do que um arquivo for recebido para a mesma entrega, apenas o último recebido dentro do prazo será considerado.
  • O cumprimento ou não dessas instruções fará parte dos critérios de avaliação.

Arquivos que fazem parte da entrega:

  • planejamento.pdf: Diagrama de planejamento da atividade.
  • relatorio.pdf: Relatório final e diagrama de sequência do funcionamento do projeto.
  • *.vhd: Descrição em VHDL de todos os módulos utilizados para a construção do seu acelerador.
  • *.py: O script em python utilizado para interagir com o acelerador.

Link para entrega: https://ic.unicamp.br/~isaias/mc613/entrega.

Operador de Sobel

O Operador de Sobel, também chamado de filtro de Sobel, é utilizado em processamento de imagem para algoritmos de detecção de bordas. O operador consiste em uma matriz 3x3 que, quando aplicada sobre uma imagem em escala de cinza, enfatiza as arestas da imagem.

O operador utiliza a convolução de dois kernels 3x3, apresentados abaixo, para calcular as derivadas aproximadas da intensidade da imagem, uma na horizontal e outra na vertical.

$$ G_x = \begin{bmatrix} -1 & 0 & +1 \\ -2 & 0 & +2 \\ -1 & 0 & +1 \end{bmatrix} $$ $$ G_y = \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ +1 & +2 & +1 \end{bmatrix} $$

Então, a magnitude do gradiente é obtido por:

$$ G = \sqrt{G_x^2 + G_y^2} $$

O resultado da aplicação do operador sobre uma imagem pode ser conferido abaixo:

Grayscale test image of brick wall and bike rack
Normalized gradient magnitude from Sobel–Feldman operator
Imagem de teste em escala de cinza de uma parede de tijolos e um bicicletário. Magnitude do gradiente normalizado do operador Sobel-Feldman.

Periférico Matricial

Operações com matrizes estão entre as principais ferramentas utilizadas na computação, estando presentes na manipulação de imagens digitais, execução de modelos de machine learning e inteligência artificial, dentre outros. De forma geral, multiplicação de matrizes é uma boa ferramenta para efetuar cálculos complicados.

Os processadores que usamos foram concebidos para a realização de múltiplas tarefas, sendo capazes de executar uma multiplicação de matrizes, porém não são as unidades mais rápidas para tal. Por isso, foram criadas outras unidades de processamento, como as GPUs, NPUs, TPUs etc.

O seu objetivo nesta atividade é projetar um periférico matricial, isto é, uma unidade de processamento capaz de efetuar multiplicação de matrizes que atua como coprocessador ou acelerador para aliviar o processador principal dessas operações.

Atividades do Laboratório

Parte I - Planejamento

Este laboratório é mais complexo que os anteriores, exigindo múltiplas partes para que tudo funcione corretamente. Podemos dividir o funcionamento do acelerador nas seguintes etapas:

  1. Receber uma imagem do computador
  2. Processar imagem aplicando o operador de sobel
  3. Enviar imagem processada para o computador

Além da implementação na placa, um script em python deverá ser utilizado para se comunicar com o periférico, enviando a imagem e a recebendo de volta no final. As etapas de funcionamento do seu script são:

  1. Redimensionar a imagem
  2. Converter para escala de cinza
  3. Adicionar padding na imagem
  4. Enviar tamanho da imagem e pixels pro acelerador
  5. Aguardar processamento da imagem
  6. Receber imagem do acelerador de volta
  7. Exibir imagem no computador

Considerando essas duas partes do seu trabalho, apresente sob forma de diagrama um planejamento para execução da atividade. Faça uma estimativa do tamanho de imagem máximo suportado pelo seu acelerador.

Importante: O planejamento deve ser apresentado durante a aula de 18 de junho.

Parte II - Comunicação Serial

Como a porta UART presente na DE1-SoC não está conectada à FPGA, iremos utilizar um recurso fornecido pela Intel para criar uma UART virtual que se comunica pela mesma interface JTAG que utilizamos para programar a placa.

Acesse o Platform Designer do Quartus pelo menu: Tools > Platform Designer, como ilustra a figura a seguir.

Localização do Platform Designer no Quartus.

Remova o componente clk que é criado automaticamente e no menu IP Catalog, na esquerda, procure por JTAG UART. Adicione o componente “JTAG UART Intel FPGA IP”, pesquisando e clicando duas vezes nele.

Procurando o componente JTAG UART.

Um popup irá aparecer com algumas configurações para este componente, apenas clique em Finish.

Popup de configuração do componente.

O nosso novo componente apareceu no centro da tela, perceba que a coluna Export indica Double-click to export para os sinais do nosso componente, clique duas vezes nos sinais clk, reset, e avalon_jtag_slave. Finalmente, clique eu Generate HDL no canto inferior direito da tela para criar o componente e salve na pasta do seu projeto. Agora podemos voltar para a janela principal do Quartus.

Estado final do Platform Designer

Adicione os arquivos .vhd e .qip dentro da pasta synthesis criada pelo Platform Designer. Um arquivo _inst.vhd também é criado para mostrar como instanciar o componente em seu projeto.

O módulo de UART criado pelo Platform Designer utiliza uma interface avalon, a qual é feita para operação com um processador. Assim, você precisará fazer pooling desta interface, isto é, enviar regularmente pedidos de leitura para receber os dados.

O padrão de comunicação está ilustrado na imagem abaixo. De forma simplificada, você deve definir os sinais de address e chipselect, e então esperar até que waitrequest abaixe para ler os dados.

Comunicação por interface avalon.

O pacote transmitido pela interface possui 32 bits e está dividido como na figura abaixo. O offset corresponde ao sinal address que indica o tipo de dado sendo acessado do periférico de UART. O valor 1 corresponde aos registradores de controle, e o valor 0 aos registradores de dados. Para esta atividade, somente os registradores de dados precisam ser acessados.

Intel JTAG UART Register Map

Os valores armazenados pelos registradores de dados correspondem a:

  • RAVAIL: Indica quantos dados ainda podem ser lidos.
  • RVALID: Indica se os dados em DATA são válidos.
  • DATA: Os dados transmitidos pela porta UART.

Implemente a interface de comunicação entre a UART e a sua unidade matricial.

Dica: múltiplas leituras serão necessários para enviar todas as informações.

Um exemplo de comunicação via periférico UART pode ser consultada nos arquivos abaixo, utilize-os para verificar que compreendeu o básico da implementação antes de continuar.

Dica: Você pode ver mais detalhes sobre a utilização da UART virtual nas referências no final do texto.

Parte III - Script em Python

A comunicação por meio da interface UART virtual é feita usando a ferramenta System Console do quartus, presente no menu Tools > System Debugging Tools > System Console.

Uma vez programada a placa, no menu lateral você deve ser capaz de encontrar a sua JTAG UART dentro de devices.

Quartus System Console

E então, a seguinte sequência de comandos permite enviar um byte para a placa, onde 114 no comando set payload corresponde à letra ‘r’ em ASCII

set bytestream [lindex [get_service_paths bytestream] 0]
set claimed_bytestream [claim_service bytestream $bytestream mylib]
set payload [list 114]
bytestream_send $claimed_bytestream $payload

Essa interface não é muito prática para nosso projeto, por isso você deverá escrever um script em python para essa comunicação usando o pacote intel-jtag-uart que fornece uma interface mais fácil de comunicação. O trecho de código abaixo exemplifica sua utilização.

#! /usr/bin/env python3
import time
import intel_jtag_uart

try:
  ju = intel_jtag_uart.intel_jtag_uart()

except Exception as e:
  print(e)
  sys.exit(0)

ju.write(b'r')
time.sleep(1)
print("read: ", ju.read())

Parte IV - Implementação do Acelerador

Conforme o diagrama desenvolvido na Parte I, implemente seu periférico matricial e apresente seu funcionamento.

Responda baseado em sua implementação:

  1. Qual o maior tamanho de imagem suportado pelo seu periférico?
  2. Ele divergiu da sua estimativa inicial? Explique.

Referências