este artigo está focado em aprender como um núcleo de microcontrolador é projetado e destina-se apenas ao uso educacional. Por favor visite www.zilog.com e verifique a linha de produtos do fabricante para selecionar um microcontrolador que atenda às suas necessidades de projeto (de Encores Z8 de oito bits! e eZ80 aclama ao ZNEO32 baseado em ARM Cortex-M3 de 32 bits! que inclui recursos avançados de controle do motor).Meu caso de amor com microcontroladores e microprocessadores começou em 1988, quando eu estava trabalhando em direção a um diploma técnico no CEFET-PR (uma escola secundária/técnica brasileira de quatro anos e universidade localizada em Curitiba). Comecei aprendendo o básico enquanto explorava o clássico Zilog Z-80 (figura 1a).
figura 1A. O Zilog Z-80A (cortesia do Wikimedia Commons).
avançar através de uma carreira de programação que incluiu a criação de alguns livros sobre a programação de microcontroladores (consulte Recursos), iniciando uma pequena casa de design (ScTec), e o acabamento de um programa de pós-graduação no CEFET-SC (outra universidade Brasileira, localizada em Florianópolis). Isso foi em 2008, quando tive mais contato com lógica programável e VHDL e minha curiosidade atingiu o pico. Anos depois, em 2016, encontrei um kit FPGA (Field-Programmable Gate Array) muito acessível e decidi dar uma chance e comecei a aprender mais sobre a tecnologia FPGA.
o que seria melhor do que projetar um softcore para aprender mais sobre os próprios núcleos VHDL (VHSIC hardware description language), FPGAs e microprocessadores? Acabei escolhendo um parente moderno do Z-80: o Zilog Z8 Encore! (t. c. p., eZ8; figura 1b).
figura 1b.Zilog eZ8.
é um núcleo de microcontrolador de oito bits com um conjunto de instruções simples — mas poderoso-e um depurador on-chip muito bom. Com seu IDE leve (ambiente de desenvolvimento integrado) e compilador ANSI C gratuito, é um excelente projeto para aprender (e também ensinar) sobre sistemas embarcados.
Antes de mergulhar nas profundezas da operação principal, VHDL e FPGAs, vamos dar uma olhada no Encore Zilog Z8! recurso.
figura 1C. FPz8 em um FPGA.
Zilog Z8 Encore!
o eZ8 é uma família de microcontroladores de oito bits baseada na bem-sucedida família Z8 da Zilog e na grande herança Z-80. Possui uma máquina Harvard CISC com até 4.096 bytes de RAM (registro de arquivos e área de registros de função especial), até 64 KB de memória do programa (geralmente memória Flash) e até 64 KB de memória de dados (RAM). O núcleo eZ8 também inclui um controlador de interrupção vetorizado com prioridade programável e um depurador on-chip que se comunica com o computador host usando comunicação serial assíncrona. Esses microcontroladores são embalados com um conjunto periférico muito agradável, variando de temporizadores versáteis de 16 bits a Temporizadores de controle do motor, de vários UARTs (IrDA ready) a dispositivos USB e muito mais (visite www.zilog.com para verificar a linha de produtos completa).
uma das principais características do modelo de programação eZ8 é a falta de um acumulador fixo. Em vez disso, qualquer um dos 4.096 endereços RAM possíveis pode funcionar como acumuladores. A CPU trata sua RAM principal (o arquivo e SFRs-special function registers-area) como um grande conjunto de registros de CPU. Para conseguir isso, a RAM é dividida em grupos de registro (existem 256 grupos de 16 registros de trabalho cada). Uma instrução geralmente funciona dentro de um único grupo de registro de trabalho, que é selecionado por um SFR chamado RP (register pointer). Observe que todos os SFRs estão localizados na última página da RAM (endereços a partir de 0xF00 até 0xFFF).
em relação ao conjunto de instruções, existem 83 instruções diferentes divididas em duas páginas opcode. Ele compreende instruções usuais para Operações básicas, como adição, subtração, operações lógicas, instruções de manipulação de dados, instruções de deslocamento, instruções de mudança de fluxo, algumas instruções de 16 bits, teste e manipulação de bits, multiplicação 8×8, etc.
a área de memória do programa é organizada para que os primeiros endereços sejam dedicados a fins especiais. Os endereços 0x0000 e 0x0001 são dedicados às opções de configuração; os endereços 0x0002 e 0x0003 armazenam o vetor de redefinição; e assim por diante. A tabela 1 mostra a organização da memória do programa.
0x0000 | Option bytes |
0x0002 | Reset vector |
0x0004 | WDT vector |
0x0006 | Illegal instruction vector |
0x0008 to 0x0037 | Interrupt vectors |
0x0038 to 0xFFFF | User program memory area |
TABLE 1. Simplified program memory organization.
alguns dispositivos também incluem um segundo espaço de dados (até 65.536 endereços) que só pode ser acessado usando instruções LDE/LDEI. Esta área pode ser usada para armazenar menos dados usados (Como Ler/Gravar para ele é mais lento do que a área RAM/SFR).
FPz8
a primeira implementação do FPz8 usa uma abordagem de design muito conservadora e com fio com dois barramentos principais: um para memória de programa e outro para memória de registro. Como optei por não incluir uma área de memória de dados, as instruções LDE/LDEI não são implementadas.
os barramentos de memória do programa compreendem um barramento de endereço de instrução de 16 bits( IAB), um barramento de dados de instrução de oito bits (IDB para leitura de dados da memória do programa), um barramento de dados de gravação de instrução de oito bits (IWDB para gravar dados na memória do programa) e um sinal PGM_WR O FPz8 inclui 16.384 bytes de memória do programa implementada usando RAM de bloco síncrono (o que significa que o conteúdo da memória do programa é perdido quando o dispositivo é desligado).
os cinco barramentos de área de registro compreendem três para a área de registro de arquivos (RAM do Usuário) e outros dois dedicados a registros de funções especiais. Há um barramento de endereço de registro de arquivo principal de 12 bits (FRAB), um barramento de dados de entrada de registro de arquivo de oito bits (FRIDB), um barramento de dados de saída de registro de arquivo de oito bits (FRADB), um barramento de dados de entrada de registro de oito bits (RIDB) e, finalmente, um barramento de dados de O FPz8 inclui 2.048 bytes de memória RAM do Usuário implementados usando RAM de bloco síncrono.
a Figura 2 mostra um diagrama de blocos do FPz8; você pode ver a CPU, duas unidades de memória (uma para armazenamento de programas e outra para armazenamento de dados) e também um módulo de temporizador externo.
Figura 2. Diagrama de blocos FPz8.
observe que não estou usando barramentos bidirecionais para nenhuma interconexão neste projeto. Os ônibus unidirecionais são mais simples de usar, embora sejam menos eficientes em termos de espaço.
a descrição VHDL do FPz8 é grande e um pouco complexa, então vou dividir sua operação em alguns módulos para facilitar a compreensão:
- Instrução mecanismo de enfileiramento
- Instrução de decodificação
- processamento de Interrupção
- Depurador
Instrução Mecanismo de Enfileiramento
Busca de instruções é uma tarefa primordial para qualquer CPU. A arquitetura Harvard do FPz8 permite busca simultânea e acesso a dados (devido a barramentos separados para instrução e dados). Isso significa que a CPU pode buscar uma nova instrução enquanto outra está lendo ou gravando na memória de dados.
o eZ8 tem uma palavra de instrução de comprimento variável (o comprimento da instrução varia de um byte até cinco bytes); algumas instruções são longas, mas correm mais rápido do que outras. Dessa forma, uma instrução BRK tem um comprimento de um byte e é executada em dois ciclos,enquanto um LDX IM, ER1 tem quatro bytes de comprimento e é executado em dois ciclos de clock.
então, como podemos decodificar com sucesso todas essas instruções? Com uma fila de instruções; isto é, um mecanismo que mantém a busca de bytes da memória do programa e armazená-los em uma matriz de oito bytes:
if (CAN_FETCH=’1′) então
if (iQueue.Como baixar e instalar Minecraft no minecraft 1.1.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.2.WRPOS: = 0;
IQUEUE.RDPOS: = 0;
IQUEUE.CNT := 0;
IQUEUE.Como baixar e instalar Minecraft no Minecraft 1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.Completo = ‘0’) então
IQUEUE.FILA(IQUEUE.COMO BAIXAR E INSTALAR O WINDOWS 10 NO WINDOWS 10, VOCÊ PODE BAIXAR E INSTALAR O WINDOWS 10 NO WINDOWS 10.WRPOS: = IQUEUE.WRPOS + 1;
IQUEUE.CNT: = IQUEUE.CNT + 1;
end if;
end if;
end if;
se (IQUEUE.CNT = 7) então IQUEUE.Completo:= ‘1’; mais IQUEUE.Completo: = ‘0’;
terminar se;
Listagem 1. Motor da fila da instrução.
a busca é controlada por um sinal de ativação principal (CAN_FETCH) que pode ser desativado em alguns casos especiais (processamento de interrupção, por instruções LDC/LDCI ou acesso ao depurador). Há também uma estrutura (IQUEUE) que armazena vários parâmetros internos (estado de busca, leitura e gravação de ponteiros, matriz de fila em si, um contador e um indicador completo).
o contador de filas (CNT) é usado para identificar o número de bytes disponíveis para uso (leitura) na fila. O estágio decodificador usa esse número para verificar se o número desejado de bytes para a instrução já está disponível na fila.
decodificação de instruções
é aqui que a magia real acontece. O decodificador de instruções lê opcodes da fila de instruções e os traduz em operações correspondentes.
o design do decodificador De Instruções começou descobrindo a relação entre todas as instruções e modos de endereçamento. À primeira vista, é fácil ver que algumas instruções (Figura 3) são agrupadas por coluna (DJNZ, JR cc,X, LD r1,IM, JP cc,da E INC R1). Decodificar uma instrução INC R1 é simples: nessas instruções de byte único, a mordidela alta especifica o registro de origem / destino e a mordidela inferior especifica a própria instrução (0xE).
Figura 3. Opcodes por grupos.
a maioria das instruções pode ser classificada de acordo com algumas regras básicas:
- colunas (a mordidela inferior de um opcode) geralmente especificam um modo de endereçamento: As instruções da coluna 0x9, por exemplo, usam principalmente o modo de endereçamento IM,ER1 e têm quatro bytes de comprimento (o segundo byte é o operando imediato e os dois últimos bytes são o endereço estendido de destino).
- as linhas (a mordidela mais alta de um opcode) geralmente especificam uma operação: as instruções da linha 0x0 são principalmente operações de adição; as instruções da linha 0x2 são principalmente operações de subtração e assim por diante.
se olharmos para a linha 0x1, podemos ver que as colunas 0x0 e 0x1 São instruções RLC e as colunas 0x2 até 0x9 São instruções ADC. Então, podemos projetar um ALU que leva um mordidela como entrada (o mordidela superior do opcode) e decodifica-o de acordo. Embora isso funcione para as colunas 0x2 a 0x9, precisaríamos de outra abordagem para as duas primeiras colunas.
é por isso que acabei escrevendo duas unidades: uma ALU que se concentra na maioria das instruções aritméticas e lógicas; e uma segunda unidade (unidade lógica 2, ou LU2) que executa outras operações mostradas nas colunas 0x0 e 0x1 (nem todas as operações vistas nessas colunas são realizadas por LU2). Os códigos de operação para ALU e LU2 foram escolhidos para corresponder às linhas do opcode mostradas na Figura 3.
outro detalhe importante é que todas as instruções dentro da mesma coluna e grupo são do mesmo tamanho em bytes, portanto, podem ser decodificadas na mesma seção do decodificador.
o design do decodificador faz uso de uma grande máquina de estado finito (FSM) que avança em cada marca de relógio. Cada instrução começa na estatística CPU_DECOD. É aqui que o decodificador realmente decodifica os opcodes, prepara ônibus e sinais de suporte internos e passos para outros estados de execução. Entre todos esses estados, dois são amplamente utilizados por muitas instruções: CPU_OMA e CPU_OMA2. Consegues adivinhar porquê? Se você disse porque eles estão relacionados com ALU e LU2, você está absolutamente certo!
OMA é curto para Uma Memória de Acesso e este é o último estado para todas as JANTES de instruções (ADICIONAR, ADC, ADDX, ADCX, SUB, SBC, SUBX, SBCX, OU, ORX, E, ANDX, XOR, XORX, CP, CPC, TCM, TCMX, TM, TMX, e algumas variantes de LD e LDX). Por outro lado, CPU_OMA2 é o último estado para todas as instruções relacionadas ao LU2 (RLC, INC, DEC, da, com, RL, CLR, RRC, SRA, SRL, RR e SWAP).
agora, vamos dar uma olhada dentro do estado CPU_DECOD. Consulte a Figura 4.
Figura 4. Estado CPU_DECOD.
dentro do estado CPU_DECOD, podemos ver que muita ação ocorre. No início, algumas variáveis temporárias são inicializadas para uma condição padrão. Observe que NUM_BYTES é muito importante, pois controla quantos bytes foram consumidos pelo decodificador de instruções. Seu valor é usado na última parte deste estágio para incrementar o PC( contador de programas), avançar o ponteiro de leitura da fila e diminuir o número de bytes disponíveis na fila.
seguindo a seção de inicialização, podemos ver a seção de processamento de interrupção. É responsável por detectar quaisquer interrupções pendentes e prepara a CPU de acordo. Vou cobrir isso na próxima seção.
o bloco de decodificação de instruções real verifica se um modo de baixa potência não está ativo e também se o modo depurador está desligado (OCDCR.DBGMODE=0). Ou, enquanto no modo de depuração, um comando de depuração de etapa única foi emitido (OCDCR.DBGMODE=1 e TOC.SINGLE_STEP = 1). Em seguida, verifica os bytes disponíveis na fila e prossegue com a decodificação.
algumas instruções (principalmente as singlebyte) são concluídas dentro do estado CPU_DECOD, enquanto outras precisam de vários estados até serem totalmente concluídas.
Note que algumas instruções de decodificação pode fazer uso de várias funções e procedimentos escritos especialmente para o FPz8:
- DATAWRITE — Este procedimento prepara ônibus para uma operação de escrita. Ele seleciona se o destino é um SFR interno, um SFR externo ou um local de RAM do Usuário.
- DATAREAD-esta é uma função recíproca para DATAWRITE. Ele é usado para ler um endereço de origem e escolhe automaticamente se é um SFR interno, um SFR externo ou um local de RAM do Usuário.
- CONDITIONCODE-usado para instruções condicionais (como JR e JP). Ele pega um código de condição de quatro bits, testa e retorna o resultado.
- ADDRESSER4, ADDRESSER8 e ADDRESSER12 – essas funções retornam um endereço de 12 bits de uma fonte de quatro, oito ou 12 bits. Eles usam o conteúdo do registro RP para gerar o endereço final de 12 bits. ADDRESSER8 e ADDRESSER12 também verificar se há qualquer modo de endereçamento escapado.
- ADDER16-este é um adder de 16 bits para cálculo de deslocamento de endereço. Leva um operando assinado de oito bits, o sinal o estende, o adiciona ao endereço de 16 bits e retorna o resultado.
- ALU e LU2-estes foram discutidos anteriormente, e realizar a maioria das operações aritméticas e lógicas.
processamento de interrupção
como eu disse antes, eZ8 tem um controlador de interrupção vetorizado com prioridade programável. No início, pensei que esta seção não seria tão difícil porque as interrupções não são grandes coisas, certo? Bem, quando comecei a descobrir como fazer todas as tarefas necessárias (salvar contexto, vetorizar, gerenciar prioridades, etc.), Percebi que seria mais difícil do que eu pensava. Depois de algumas horas, criei o design atual.
o sistema de interrupção do FPz8 acabou sendo simples. Possui oito entradas (INT0 a INT7); uma ativação de interrupção global (bit IRQE localizado no registro IRQCTL); dois registros para configuração de prioridade (IRQ0ENH e IRQ0ENL); e um registro para sinalizadores de interrupção (IRQ0). O design faz uso de uma cadeia if aninhada que gera um endereço vetorial após a detecção de um evento de interrupção em relação a uma interrupção habilitada.
a Figura 5 mostra uma vista comprimida do sistema de interrupção. Nota Há uma primeira instrução IF com um símbolo ATM_COUNTER. Este é um contador simples usado pela instrução ATM (desativa interrupções para três ciclos de instrução, permitindo operações atômicas).
Figura 5. Sistema de interrupção FPz8.
um último comentário sobre interrupções: As amostras do registro de sinalização de interrupção (IRQ0) interrompem as entradas a cada borda ascendente do relógio do sistema. Existem também duas variáveis de buffer (IRQ0_LATCH e OLD_IRQ0) que armazenam o estado atual e último dos sinalizadores. Isso permite a detecção de borda de interrupção e também sincroniza as entradas externas com o relógio interno (FPGAs não funcionam bem com sinais internos assíncronos).
depurador On-Chip
esta é provavelmente a característica mais legal deste softcore, pois permite um ambiente de desenvolvimento integrado comercial (IDE; como o ZDS-II da Zilog) para comunicar, programar e depurar software em execução no FPz8. O depurador on-chip (OCD) é composto por um UART com capacidade autobaud e um processador de comando conectado a ele. O UART executa a comunicação serial com um PC host e entrega comandos e dados à máquina de Estado do depurador que processa comandos de depuração (o FSM de processamento de comando do depurador está localizado dentro do estado CPU_DECOD).
Figura 6. Depurador on-chip UART (observe o sincronizador DBG_RX).
meu design de OCD implementa quase todos os comandos disponíveis no hardware real, exceto aqueles relacionados à memória de dados (comandos de depuração 0x0C e 0x0D); o contador de tempo de execução de leitura (0x3); e a memória de programa de leitura CRC (0X0E).
uma coisa que gostaria de destacar é que é necessário cuidado ao lidar com sinais assíncronos dentro de FPGAs. Meu primeiro projeto não foi responsável por isso durante o processamento do sinal de entrada DBG_RX. O resultado foi absolutamente estranho. Meu design funcionou perfeitamente na simulação. Eu baixei para um FPGA e comecei a brincar com a interface serial de depuração usando um terminal serial (minha placa FPGA tem um conversor serial-USB integrado).
para minha surpresa, enquanto na maioria das vezes eu poderia enviar comandos com sucesso e receber os resultados esperados, às vezes o design simplesmente congelava e parava de responder. Uma reinicialização suave faria as coisas voltarem ao seu funcionamento adequado, mas isso me intrigou. O que estava a acontecer?
depois de muitos testes e algumas pesquisas no Google, descobri que possivelmente estava relacionado às bordas assíncronas do sinal de entrada serial. Em seguida, incluí uma trava em cascata para sincronizar o sinal com o meu relógio interno e todos os problemas desapareceram! Essa é uma maneira difícil de aprender que você deve sempre sincronizar sinais externos antes de alimentá-los em lógica complexa!
devo dizer que depurar e refinar o código do depurador foi a parte mais difícil deste projeto; principalmente porque ele interage com todos os outros subsistemas, incluindo barramentos, o decodificador e a fila de instruções.
sintetizando e testando
uma vez totalmente compilado (usei Quartus II V9.1 sp2), o núcleo FPz8 usou 4.900 elementos lógicos, 523 registros, 147.456 bits de memória no chip e um multiplicador de nove bits incorporado. No geral, o FPz8 usa 80% dos recursos disponíveis do EP4CE6. Embora isso seja muito, ainda existem cerca de 1.200 elementos lógicos disponíveis para periféricos (meu temporizador simples de 16 bits adiciona cerca de 120 elementos lógicos e 61 registros). Ele até se encaixa no menor Cyclone IV FPGA-o EP4CE6-que é o montado na mini placa de baixo custo que usei aqui (Figura 7).
Figura 7. Altera Cyclone IV EP4CE6 mini board.
as mini Características da placa (junto com o dispositivo EP4CE6): uma memória de configuração serial EPCS4 (montada no lado inferior); um chip conversor serial para USB FTDI, bem como um módulo oscilador de cristal de 50 MHz; alguns botões; LEDs; e cabeçalhos de pinos para acessar pinos FPGA. Não há USB-Blaster integrado( para programação FPGA), mas o pacote que comprei também incluiu um dongle de programação externo.
quanto aos testes do mundo real, escusado será dizer que o FPz8 não funcionou pela primeira vez! Depois de pensar um pouco e ler mensagens de saída do compilador, descobri que provavelmente era um problema de tempo. Este é um dilema muito comum ao projetar com lógica programável, mas como este foi meu segundo design FPGA de todos os tempos, não prestei atenção suficiente a ele.
verificando as mensagens de análise de tempo, pude ver um aviso de que o relógio máximo deve estar em torno de 24 MHz. No início, tentei usar um divisor por 2 para gerar um relógio de CPU de 25 MHz, mas não era confiável. Eu então usei um divisor por 3. Tudo começou a funcionar perfeitamente!
é por isso que o FPz8 atualmente funciona a 16.666 MHz. É possível atingir velocidades mais altas usando um dos PLLs internos para multiplicar / dividir o relógio principal para obter um relógio resultante inferior a 24 MHz, mas superior a 16.666 MHz.
programação e depuração
usar o FPz8 é muito simples e direto. Assim que o design for baixado para o FPGA, a CPU começará a executar qualquer programa carregado na memória. Você pode fornecer um arquivo hex e usar o Gerenciador de Plug-in MegaWizard para alterar o arquivo de inicialização da memória do programa. Dessa forma, o código do aplicativo começará a ser executado após um sinal de redefinição.
Você pode usar o Zilog ZDS-II IDE para escrever Assembly ou código C, e gerar a necessária hex arquivos (eu costumo selecionar o Z8F1622 como meu dispositivo de destino, como também tem 2 KB de RAM e 16 KB de memória de programa). Graças ao depurador no chip, também é possível usar o IDE ZDS-II para baixar o código para o FPz8 usando uma conexão de depuração serial (USB, no nosso caso).
Antes de ligar, certifique-se de que as definições do depurador são as mesmas da figura 8. Desmarque a opção ” Usar apagamento de página antes de piscar “e selecione” SerialSmartCable ” como a ferramenta de depuração atual. Não se esqueça de verificar também se a porta COM virtual do FTDI está selecionada corretamente como a porta de depuração (use o botão de configuração). Você também pode definir a velocidade de comunicação desejada; 115.200 bps funcionam muito bem para mim.
figura 8. Configurações do depurador.
observe que, ao se conectar ao FPz8, o IDE ZDS-II mostrará uma mensagem de aviso informando que o dispositivo de destino não é o mesmo que o projeto. Isso acontece porque eu não implementei algumas áreas de memória ID. Basta ignorar o aviso e prosseguir com a sessão de depuração.
uma vez que o código é baixado com sucesso, você pode iniciar o aplicativo (botão GO), instruções passo, inspecionar ou editar registros, definir pontos de interrupção, etc. Como acontece com qualquer outro bom depurador, você pode, por exemplo, selecionar o registro PAOUT (em grupo de portas) e até mesmo alterar o estado dos LEDs conectados ao PAOUT.
alguns exemplos simples de código C podem ser encontrados nos downloads.
basta ter em mente que o FPz8 tem uma memória de programa Volátil. Assim, qualquer programa baixado para ele é perdido quando o FPGA é desligado.
encerramento
este projeto levou algumas semanas para ser concluído, mas foi delicioso pesquisar e projetar um núcleo de microcontrolador.
espero que este projeto possa ser útil para qualquer pessoa que queira aprender sobre noções básicas de computação, microcontroladores, programação incorporada e/ou VHDL. Acredito que — se emparelhado com uma placa FPGA de baixo custo-o FPz8 pode fornecer uma fantástica ferramenta de aprendizagem (e ensino). Diverte-te! NV
CEFET-PR:
www.utfpr.edu.br
ScTec:
www.sctec.com.br
HCS08 Unleashed:
https://www.amazon.com/HCS08-Unleashed-Designers-Guide-Microcontrollers/dp/1419685929
Zilog eZ8 CPU Manual (UM0128):
www.zilog.com/docs/UM0128.pdf
Zilog Z8F64xx Product Specification (PS0199):
www.zilog.com/docs/z8encore/PS0199.pdf
Zilog ZDS II IDE User Manual (UM0130):
www.zilog.com/docs/devtools/UM0130.pdf
Zilog ZDS-II Software Download:
https://www.zilog.com/index.php?option=com_zcm&task=view&soft_id=7&Itemid=74
Zilog Microcontroller Product Line:
http://zilog.com/index.php?option=com_product&task=product&businessLine=1&id=2&parent_id=2&Itemid=56
Project Files available at:
https://github.com/fabiopjve/VHDL/tree/master/FPz8
FPz8 at Opencores.org:
http://opencores.org/project,fpz8