Eduardo Frimer
Rio de Janeiro
Agosto de 2015
PROJETO E SIMULAÇÃO DE UM
MICROCONTROLADOR COMO EXEMPLO DE
IMPLEMENTAÇÃO DE UM SISTEMA DIGITAL
Eduardo Frimer
Autor:
_________________________________________________
Eduardo Frimer
Orientador:
_________________________________________________
Prof. José Paulo Brafman, M.Sc.
Examinador:
_________________________________________________
Prof. Aloysio de Castro Pinto Pedrosa, Ph.D.
Examinador:
_________________________________________________
Prof. Joarez Bastos Monteiro, D. Sc.
Agosto de 2015
ii
UNIVERSIDADE FEDERAL DO RIO DE JANEIRO
Escola Politécnica – Departamento de Eletrônica e de Computação
Centro de Tecnologia, bloco H, sala H-212, Cidade Universitária.
Rio de Janeiro – RJ CEP 21949-900
iii
AGRADECIMENTO
A Deus por ter me criado e ter dado saúde e força para superar todas as
dificuldades.
Aos meus pais, minha irmã e minha noiva, pelo amor, incentivo e apoio
incondicional.
A esta universidade, seu corpo docente, direção e administração pelo ambiente
criativo е pela oportunidade de cursar Engenharia Eletrônica e de Computação.
Ao meu orientador José Paulo Brafman, pelo suporte no pouco tempo que lhe
coube, pelas suas correções e incentivos.
E a todos que direta ou indiretamente fizeram parte da minha formação.
iv
RESUMO
v
ABSTRACT
vi
SIGLAS
A/D – Analógico/Digital
ALU – Unidade Aritmética Lógica
CI – Circuito Integrado
CISC – Complex Instruction Set Computer
CLB – Bloco de Lógica Configurável (Configurable Logic Block)
CMOS – Complementary Metal Oxide Semiconductor
CPU – Unidade Central de Processamento
E / S – Entrada/Saída
EEPROM – Electrically-Erasable Programmable Read-Only Memory
FPGA – Field-Programmable Gate Array
GPR – General Purpose Registers
HDL – Linguagem de Descrição de Hardware (Hardware Description Language).
MOSFET –Metal Oxide Semiconductor Field Effect Transistor
PC – Program Counter
PIC – Controlador de Interface Programável (Programmable Interface Controller)
POR – Power-on Reset
PWM – Pulse-Width Modulation
RAM – Random Access Memory
RISC – Reduced Instruction Set Computer
ROM – Read Only Memory
RTL – Nível de Transferência de Registrador (Register Transfer Level)
SFR – Special Function Registers
UART – Universal Asynchronous Receiver/Transmitter
UFRJ – Universidade Federal do Rio de Janeiro
USB – Universal Serial Bus
vii
Sumário
1 Introdução 1
2 Revisão Tecnica 5
3 Revisão Bibliográfica 14
14
3.1 - RISCuva1 .......................................
viii
4 Modelagem do Sistema 17
17
4.1 - Geral ...........................................
4.3.2 - EEPROM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
5 Implementação e Resultados 38
6 Conclusões 44
Bibliografia 45
ix
A Código em Verilog 47
65
B.1 - Código de Teste 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
x
Lista de Figuras
2.1 – Estrutura de uma FPGA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4.4 – Timer 0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
4.8 – Pre-Escalador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
xi
Lista de Tabelas
3.1 – Comparação de núcleos abertos do PIC16F84 . . . . . . . . . . . . . . . . . . . . . . . 16
xii
Capítulo 1
Introdução
Durante a primeira metade do século XX, os circuitos eletrônicos utilizavam
válvulas, o que tornava o desenvolvimento de sistemas digitais proibitivo. Em 1947,
John Bardeen e Walter Brattain construíram o primeiro transistor nos Laboratórios Bell.
Dez anos mais tarde, Jack Kilby na Texas Instruments percebeu o potencial de
miniaturização onde vários transistores poderiam ser construídos em uma única peça de
silício e em 1958, ele construiu o primeiro circuito flip-flop integrado com dois
transistores. Bardeen, Brattain e William Shockley ganharam o Prêmio Nobel de Física
em 1956 pela invenção do transistor. Kilby recebeu o Prêmio Nobel de Física em 2000
pela invenção do circuito integrado. [1]
Na década de 1960, MOSFETs começaram a entrar em produção. MOSFETs
oferecem uma grande vantagem, pois consomem quase zero de potência enquanto não
estão chaveando. Em 1963, Frank Wanlass, na empresa Fairchild Semicondutor,
descreveu as primeiras portas lógicas usando MOSFETs. Os gates da Fairchild
ganharam o nome de Complementary Metal Oxide Semiconductor (CMOS). Os
circuitos utilizavam transistores discretos, mas consumiam apenas nanowatts de
potência.
Processos CMOS foram amplamente adotados e substituíram essencialmente
processos bipolares para quase todas as aplicações em sistemas digitais e tornaram o seu
processo de implementação mais simples.
1.1 – Tema
1
forma, em havendo um sistema digital implementado em sua concepção e a nível lógico
(descrito em uma linguagem de descrição de hardware ou esquemático) será possível
realiza-lo tanto de maneira temporária em uma FPGA como em uma forma permanente
em um circuito integrado. [2]
1.2 – Delimitação
1.3 – Justificativa
2
1.4 – Objetivos
1.5 – Metodologia
3
1.6 – Descrição
4
Capítulo 2
Revisão Técnica
2.1 – FPGAs
5
A maioria dos fabricantes de dispositivos de lógica programável produz uma
série de FPGAs que variam em densidade, consumo de energia, tensão de alimentação,
tempo de resposta, e até certo ponto variam em arquitetura.
FPGAs são reprogramáveis e usam as tecnologias SRAM (volátil) ou antifuse
(não volátil) para programar as interconexões [10]. A densidade pode variar de centenas
de CLBs a centenas de milhares CLBs. A tensão de alimentação contínua varia
tipicamente entre 1.2V a 2.5V, dependendo do dispositivo.
6
2.1.2 – Núcleo da FPGA
7
2.1.3.1 – Projeto
2.1.3.2 – Simulação
8
Existem três níveis de simulação: RTL, funcional e nível de porta. Cada um
ocorre em um lugar específico no processo de desenvolvimento. Geralmente, o estímulo
desenvolvido para a simulação RTL é reutilizável sem modificações para cada nível de
simulação.
RTL - A simulação inicial realizada imediatamente após a fase de projeto é a
simulação RTL. Ela envolve diretamente a aplicação dos estímulos ao projeto. A
simulação RTL apenas permite que os projetistas verifiquem se a lógica está
correta.
Funcional - A aplicação de estímulo de teste ao projeto sintetizado é uma
simulação funcional. Nesta simulação é verificado se o processo de síntese não
alterou o projeto.
Nível de Porta - Na simulação em nível de porta, todos os atrasos de tempo
internos são incluídos, tornando as saídas da simulação mais precisas.
2.1.3.3 – Síntese
2.1.3.4 – Implementação
9
2.2 – Microcontroladores
10
Quanto à estrutura de instruções, na arquitetura RISC, cada instrução tem o
mesmo tamanho e a mesma duração, com exceção das instruções que realizam desvio.
Por possuir poucas instruções, o programa torna-se mais complexo. Um modelo de
arquitetura RISC pode ser visto na Figura 2.4 e é evidenciado pela simplicidade do
sistema de controle que pode ser implementado com uma máquina de estados que
decodifica as instruções em sinais de controle para a CPU.
11
Figura 2.5 – Arquitetura Von Neumann
12
maioria dos microprocessadores são Harvard-RISC. Exemplos de microcontroladores e
microprocessadores Harvard-RISC são: o I7 da Intel e a família de microcontroladores
PIC da Microchip Technology.
13
Capítulo 3
Revisão Bibliográfica
3.1 – RISCuva1
3.2 – PicoBlaze
14
O núcleo PPX16 em [14] foi desenvolvido por Daniel Wallner e é uma
implementação em VHDL que possui duas versões, uma para o PIC16F84 e outra para
o PIC16C55. O núcleo foi publicado no site OpenCores em maio de 2002. A memória
ROM não foi especificada pelo autor. Enquanto a memória RAM foi especificada
pelo autor do núcleo implementada através de array.
O núcleo Risc16f84 e m [15] é uma implementação em Verilog, de um
microcontrolador PIC16F84 e possui quatro versões: risc16f84, risc16f84_lite,
risc16f84_small, risc16f84_clk2x. A única versão testada pelo autor é a versão
risc16f84_clk2x que não possui interface com EEPROM, interrupções ou PORTS. Um
barramento de dados auxiliar foi implementado para acesso de 64Kbytes de
registradores, PORTS e periféricos. As memórias RAM e ROM não foram
especificadas. O núcleo foi desenvolvido por John Clayton e é uma tradução para
Verilog do núcleo CQPIC publicado no site OpenCores em maio de 2002.
O núcleo CQPIC em [16] foi desenvolvido por Sumio Morioka e é uma
implementação em VHDL publicada em dezembro de 1999 na revista Transistor
Gijutsu, uma revista do Japão relacionada a hardware de computadores. O autor mantém
um site onde a última versão dos arquivos (v 1.00d) pode ser encontrada. A última
atualização foi feita em julho de 2004. O autor especificou as memórias RAM e ROM,
do tipo LPM [19], que somente são suportadas por FPGAs da Altera [9].
O núcleo UPEM e m [18] é uma simplificação do núcleo CQPIC em VHDL.
A memória ROM utilizada pelo autor foi implementada através de um case. Enquanto a
memória RAM foi especificada pelo autor do núcleo implementada através de array.
No escopo do presente trabalho os núcleos de código aberto são interessantes,
pois apresentam a existência de implementações abertas em HDL do modelo proposto
de microcontroladores para FPGAs.
15
seção 3.3 foram implementados baseados em modelos do PIC16F84. Na tabela abaixo
é feita uma comparação entre as implementações estudadas na seção 3.3.
16
Capítulo 4
Modelagem do Sistema
4.1 – Geral
17
Figura 4.2 – Pinagem do PIC16F84
Fonte: Manual PIC16F84 [3]
18
Tabela 4.1 – Instruções do PIC16F84
14-Bit Opcode Flags
Mnemonicos Operandos Descrição Ciclos
MSb LSb Afetados
4.1.1 – Timer 0
19
O Timer 0 é um contador/temporizador pre-setavel de 8 bits com seleção de
clock (interno ou externo) e pre-escalador programável de 8 bits como visto na Figura
4.4. Durante a execução do programa o valor do Timer 0 pode ser lido ou escrito a
qualquer momento. A figura 4.5 mostra o diagrama de blocos do Timer 0.
20
4.1.2 – Timer Watch Dog
21
Durante o funcionamento normal, o estouro do WDT cria um RESET do PIC. O
bit TO (Registrador STATUS[4]) é zerado quando ocorre o estouro.
Se o PIC está no modo SLEEP, o estouro do WDT tem como consequência o
despertar do PIC e o retorno ao modo de funcionamento normal. As instruções
CLRWDT e SLEEP zeram o WDT e impedem-no de estourar gerando uma condição de
RESET.
O WDT pode ser desabilitado, permanentemente, programando o bit de
configuração WDTE como '0'.
4.1.3 – Pre-Escalador
22
Figura 4.9 – Pre-Escalador dentro do diagrama de blocos do Timer 0 e WDT
Fonte: Manual PIC16F84 [3]
23
4.1.4 – Interrupções
24
Figura 4.11 – Logica de Interrupção
4.1.5 – Portas
O PIC16F84 tem duas portas de E/S PORTA e PORTB. Alguns pinos destas
portas são multiplexados com funções alternativas, que quando configuradas como
ativas não permitem o uso desses pinos para E/S. Ao se ler o registrador da porta na
verdade se lê o status dos pinos, mas ao se escrever, escreve-se somente no registrador.
Então a escrita implica na leitura por software do estado atual da porta, modificação
deste estado e gravação no registrador.
A PORTA é uma porta bidirecional com cinco bits e seu registrador de direção
correspondente é o TRISA. O pino RA4 é multiplexado com a entrada de clock do
Timer 0 e forma o pino RA4/T0CKI.
25
A PORTB é uma porta bidirecional com oito bits e seu registrador de direção
correspondente é o TRISB. Cada pino da PORTB quando configurado como entrada
tem um pull-up interno, que são controlados pelo bit RBPU (OPTION[7]). O pull-up é
desabilitado quando ocorre o Power-on Reset. Quatro pinos da PORTB, RB7:RB4, se
configurados como entrada geram uma interrupção quando alteradas.
O ato de setar um bit de TRISA/B fará com que o bit correspondente da
respectiva porta funcione como uma entrada e zerando-o fará com que ele funcione
como uma saída.
O PIC16F84 diferencia entre cinco tipos de RESET: (1) Power-on Reset (POR);
(2) MCLR durante a operação normal; (3) MCLR durante o modo SLEEP; (4) Reset por
estouro do WDT (durante a operação normal); (5) WDT Wake-up (durante o modo
SLEEP).
Alguns registradores não são afetados em nenhum tipo de RESET, porém a
maioria dos registradores mudam seus valores para os valores do “Estado de RESET”
nos resets dos tipos 1, 2, 3 e 4, e não são alterados no reset do tipo 5, pois esse é
simplesmente a volta ao modo normal de operação. A tabela 4.2 descreve a informação
no PC e no registrador STATUS para cada tipo de reset, enquanto a tabela 4.3 descreve
a informação nos principais registradores para cada tipo de RESET.
26
Tabela 4.3 – Condições de RESET para os Principais Registradores
Registrador Endereço 1 2, 3 e 4 5
W — xxxx xxxx uuuu uuuu uuuu uuuu
TMR0 01h xxxx xxxx uuuu uuuu uuuu uuuu
PCL 02h/82h 0000 0000 0000 0000 PC +1
STATUS 03h/83h 0001 1xxx 000q quuu uuuq quuu
FSR 04h/84h xxxx xxxx uuuu uuuu uuuu uuuu
PORTA 05h ---x xxxx ---u uuuu ---u uuuu
PORTB 06h xxxx xxxx uuuu uuuu uuuu uuuu
EEDATA 08h xxxx xxxx uuuu uuuu uuuu uuuu
EEADR 09h xxxx xxxx uuuu uuuu uuuu uuuu
PCLATH 0Ah/8Ah ---0 0000 ---0 0000 ---u uuuu
OPTION 81h 1111 1111 1111 1111 uuuu uuuu
TRISA 85h ---1 1111 ---1 1111 ---u uuuu
TRISB 86h 1111 1111 1111 1111 uuuu uuuu
EECON1 88h ---0 x000 ---0 q000 ---0 uuuu
Legenda: u = não modificado; x = desconhecido; - = não implementado; q = valor
depende de uma condição.
27
4.2 – CPU
28
especificação não é necessária. A figura 4.13 apresenta de forma simplificada a máquina
de estados do sub-sistema de controle da CPU.
29
4.2.2 – Sub-Sistema de Dados
4.3 – Memórias
30
(128 bytes), os doze primeiros bytes de cada banco são reservados para os SFRs e o
resto é composto por GPRs. A Figura 4.14 mostra a organização do arquivo de
registradores.
Cada GPR tem oito bits e pode ser acessado diretamente ou indiretamente,
através do FSR. Os GPRs podem ser acessados através dos dois bancos, então, por
exemplo, 0Ch e 8Ch acessam o mesmo GPR.
31
4.3.1.1 – Registradores Especiais
Os registradores especiais são usados pela CPU e pelos periféricos para fazer o
controle do PIC. Esses registradores são implementados no Arquivo de Registradores.
As tabelas 4.2-4.6 especificam esses registradores.
32
Tabela 4.3 – Registrador STATUS (Endereços 03h, 83h)
U U R/W-0 R-1 R-1 R/W-x R/W-x R/W-x
IRP RP1 RP0 TO PD Z DC C
bit 7 bit 0
bit 5 RP0: Bits de seleção do Banco de Registradores (endereçamento direto)
01= Banco 1 (80h - FFh)
00= Banco 0 (00h - 7Fh)
bit 4 TO: Bit de Estouro
1= Depois de power-up ou das instruções CLRWDT ou SLEEP
0= Um Estouro de WDT aconteceu
bit 3 PD: Bit de Power-down
1 = Depois do power-up ou pela instrução CLRWDT
0 = Pela execução da instrução SLEEP
bit 2 Z: Flag de Zero
1 = O resultado de um calculo ou operação logica é zero
0 = O resultado de um calculo ou operação logica não é zero
bit 1 DC: Flag de Carry/Borrow Digital [ADDWF, ADDLW,SUBLW,SUBWF]
(o borrow tem polaridade revertida)
1 = Houve Carry no bit 4 do resultado do somador
0 = Não houve Carry no bit 4 do resultado do somador
bit 0 C: Flag de Carry/Borrow [ADDWF, ADDLW,SUBLW,SUBWF]
(o borrow tem polaridade revertida)
1 = Houve Carry no bit mais significativo do resultado do somador
0 = Não houve Carry no bit mais significativo do resultado do somador
Fonte: Manual PIC16F84 [3]
33
bit 3 PSA: Bit de designação do Pre-Escalador
34
bit 0 RBIF: Bit de Flag da interrupção de variações nas Portas RB
1 = Pelo menos um dos valores dos pinos RB7:RB4 se modificou (deve ser zerado por
software)
0 = Nenhum dos valores dos pinos RB7:RB4 se modificou
Fonte: Manual PIC16F84 [3]
4.3.2 – EEPROM
35
EEDATA guarda 8 bits de dados para leitura/escrita, e EEADR guarda o
endereço da EEPROM a ser acessado.
Para ler os dados de uma posição da memória o programa deve escrever seu
endereço no registrador EEADR e setar o bit RD (EECON1[0]). Os dados estarão
disponíveis no próximo ciclo de clock no registrador EEDATA e será mantido lá até
que ocorra uma nova operação utilizando a EEPROM.
Para escrever dados em uma posição da memória EEPROM o programa deve
inicialmente setar o bit WREN no EECON1 para habilitar a escrita, esse bit não é
zerado automaticamente por hardware, em seguida deve escrever o endereço no
registrador EEADR, escrever os dados no registrador EEDATA, escrever 55h no
EECON2, escrever AAh no EECON2 e finalmente setar o bit WR (EECON1[1]).
Zerar o bit WREN no meio do ciclo de escrita não afeta o ciclo. O bit WR não é
setavel a menos que o bit WREN esteja setado. Quando a operação de escrita é
concluída o bit WR é zerado por hardware e o flag EEIF é setado.
Essa complexidade de escrita existe para impedir escritas acidentais na
EEPROM, pois escrever na EEPROM, automaticamente, sobrescreve o que estava
escrito anteriormente naquele endereço. Pelo mesmo motivo deve-se, sempre que não
estiver ocorrendo escrita na EEPROM, manter o bit WREN zerado.
4.3.3 – FLASH
36
Figura 4.15 – Gravação da memória de programa não volátil Flash
37
Capítulo 5
Implementação e Resultados
5.1 – Implementação
38
seus sinais e conectores foram implementados na CPU. Uma solução seria implementar
a EEPROM como uma RAM e para tanto seria necessário uma pequena seção de código
no arquivo top.v que fizesse a interface entre a CPU e a memória pela necessidade de
não mudar a estrutura dos sinais de acesso à EEPROM da CPU.
Todas as memórias foram implementadas usando o Block Memory Generator
que é parte integrante do ISE [9] da Xilinx para ser possível otimizar o espaço ocupado
pelo código no circuito da FPGA. A figura 5.1 mostra uma das telas do Block Memory
Generator.
5.2 – Simulação
39
corretamente. Não foram testadas todas as funções e instruções, pois seria necessária
uma bateria de testes exaustiva que testasse todas as condições possíveis, portanto
foram feitos testes para exemplificar o funcionamento da implementação. Os programas
de teste utilizados podem ser encontrados no Anexo B.
O programa na seção B.1 testa as instruções GOTO, MOVLW, MOVWF, INCF,
SUBWF e BTFSS e a função de E/S da PORTB.
O programa na seção B.2 testa as instruções GOTO, MOVLW, CLRF, INCF,
ANDWF e BTFSS e a função de E/S da PORTB.
O programa na seção B.3 testa as funções do controlador de interrupções, do
Timer 0 e E/S da PORTB.
O programa na seção B.4 testa as instruções GOTO, MOVLW, MOVWF,
CLRF, BCF, BSF e RLF e a função de E/S da PORTB.
No MPLAB deve ser selecionado como microcontrolador alvo, o PIC16F84. Ao
compilar o programa de verificação no MPLAB, é gerado um arquivo .hex, no formato
Intel HEX 32.
No Block Memory Generator, na definição dos atributos da memória ROM a ser
gerada, é possível associar à memória um arquivo contendo as instruções que estarão na
memória. Este arquivo deve ser de extensão .coe que possui uma formatação específica.
Para que a CPU execute o programa de verificação, é preciso converter o
arquivo .hex, gerado na compilação em um arquivo .coe, e então associar este arquivo à
memória ROM. A conversão foi feita através do programa hex2coe [22] escrito em
linguagem C.
Os Test Bench utilizados foram basicamente os mesmos para todos os quatro
programas de teste.
Os resultados da etapa de analise serão descritos na seção 5.3.
5.3 – Resultados
40
tanto as operações logicas como as aritméticas da CPU. Olhando externamente a
indicação que o programa, com todos os seus componentes, funcionou é constatando
que o conteúdo do registrador W foi transferido para a PORTB.
Na figura 5.3 podem-se ver os principais bits que evidenciam esse sucesso do
terceiro programa (B.3). Esse programa tinha como objetivo testar as funções
periféricas do sistema e, portanto, utilizava a interrupção do Timer 0 para alterar os
dados no PORTB. Olhando externamente a indicação que o programa, com todos os
seus componentes, funcionou é que o conteúdo do PORTB passou a oscilar com uma
frequência fixa após um tempo de inicialização.
Na figura 5.4 podem-se ver os principais bits que evidenciam esse sucesso do
quarto programa (B.4). Esse programa tinha como objetivo testar as operações de
rotação (RRF/RLF) da CPU. Olhando externamente a indicação que o programa, com
todos os seus componentes, funcionou é que o conteúdo do PORTB rotacionou através
do carry a uma taxa fixa após um tempo de inicialização.
41
Figura 5.2 – Resultado códigos de teste B.1 e B.2
42
Figura 5.4 – Resultado código de teste B.4
43
Capítulo 6
Conclusões
44
Bibliografia
[1] HARRIS, David Money; WESTE, Neil H. E.. “CMOS VLSI Design - A Circuits
and Systems Perspective”. 4th Edition, Addison-Wesley Pearson, 2011.
[2] WILSON, Peter R.. “Design recipes for FPGAs: Field programmable gate arrays –
Design and construction”. Elsevier Linacre House, 2011.
[4] SILVA JUNIOR, Vidal Pereira da. “Microcontroladores PIC: Teoria e Prática”.
2000.
[11] PABLO, S. de; CEBRIÁN, J.A.; HERRERO L.C.. A very simple 8-bit RISC
processor for FPGA. 3rd FPGAworld Conference, Estocolmo, Suecia, Novembro 2006.
Disponível em <http://www.dte.eis.uva.es/Datos/Congresos/FPGAworld2006a.pdf>>.
Acesso em: 01/08/2014.
45
[15] risc16f84. Disponível em
<http://www.opencores.org/?do=project&who=risc16f84>. Acesso em: 21/01/2015.
46
Apêndice A
Código em Verilog
//-------------------------------------------------------------------------------------------------------//
//
// UNIVERSIDADE FEDERAL DO RIO DE JANEIRO
// ESCOLA POLITÉCNICA
// DEPARTAMENTO DE ENGENHARIA ELETRÔNICA E DE COMPUTAÇÃO
//
//-------------------------------------------------------------------------------------------------------//
//
// PROJETO DE GRADUAÇÃO
//
// Projeto e Simulação de um Microcontrolador como Exemplo
// de Implementação de um Sistema Digital
//
// Aluno: Eduardo Frimer
// efrimer@poli.ufrj.br
//
// Orientador: José Paulo Brafman
//
//-------------------------------------------------------------------------------------------------------//
// Descrição
//-------------------------------------------------------------------------------------------------------//
// núcleo do PIC16F84
//-------------------------------------------------------------------------------------------------------//
module Core (progDatI, progAdrO, ramDatI, ramDatO, ramAdrO, readramO,
writeramO, existeepromI, eepDatI, eepDatO, eepAdrO,
rdEepReqO, rdEepAckI, wrEepReqO, wrEepAckI, portAI,
portAO, portADirO, portBI, portBO, portBDirO, rbpuO,
int0I, int4I, int5I, int6I, int7I, t0ckiI, wdtEnaI,
wdtClkI, wdtFullO, powerdownO, startclkO, ponRstNI,
mclrNI, clkI, clkO
);
//-------------------------------------------------------------------------------------------------------//
// A) Inicializações
//-------------------------------------------------------------------------------------------------------//
// Definição de parâmetros
parameter TAMANHO_PILHA = 8; // Tamanho da pilha do PC
parameter BITS_PILHA = 3; // Bits necessários para a pilha do PC
parameter WDT_TAMANHO = 255; // Tamanho do timer de watch dog (WDT)
parameter WDT_BITS = 8; // Bits necessários para o timer de watch dog (WDT)
parameter BITS_AUTOMATO = 3; // Tamanho do registrador do autômato finito (bits)
parameter QRESET = 3'b100; // Estado de Reset do autômato
parameter Q1 = 3'b000; // Estado Q1 do autômato
47
parameter Q2 = 3'b001; // Estado Q2 do autômato
parameter Q3 = 3'b011; // Estado Q3 do autômato
parameter Q4 = 3'b010; // Estado Q4 do autômato
48
input clkI; // Clock de entrada
output clkO; // Clock de saída (clkI/4)
// Sinais internos
// Registradores do usuário
reg [7:0] wReg; // W
reg [7:0] tmr0Reg; // TMR0
reg [12:0] pcReg; // PCH/PCL
reg [7:0] statusReg; // STATUS
reg [7:0] fsrReg; // FSR
reg [4:0] portAISyncReg; // PORTA IN
reg [4:0] portAOReg; // PORTA OUT
reg [7:0] portBISyncReg; // PORTB IN
reg [7:0] portBOReg; // PORTB OUT
reg [7:0] eepDatReg; // EEPROM DATA
reg [7:0] eepAdrReg; // EEPROM ADDRESS
reg [4:0] pclathReg; // PCLATH
reg [7:0] intconReg; // INTCON
reg [7:0] optionReg; // OPTION
reg [4:0] trisaReg; // TRISA
reg [7:0] trisbReg; // TRISB
reg [4:0] eecon1Reg; // EECON1
// Registradores internos para controle da execução das instruções
reg [13:0] instReg; // Guarda o opcode/operando
reg [7:0] aluinp1Reg; // Origem do dado (1/2)
reg [7:0] aluinp2Reg; // Origem do dado (2/2)
reg cIn; // Carry de entrada da ALU
reg [7:0] aluoutReg; // resultado dos calculos
reg execOpReg; // Se 0, execução de um ciclo impedido (desvio)
reg intstartReg; // Se 1, execução de um ciclo impedido (interrupção)
reg sleepflagReg; // Se 1, modo SLEEP
// Pilha
reg [12:0] stackReg [TAMANHO_PILHA-1:0]; // pilha
reg [BITS_PILHA-1:0] stackPntReg; // ponteiro da pilha
// Timer Watch Dog (WDT)
reg [WDT_BITS-1:0] wdtReg; // contador WDT
reg wdtFullReg; // WDT->CPU; guarda o sinal de WDT cheio até o reset da CPU
reg wdtFullNode;
wire wdtInit; // Inicializa o WDT
reg [2:0] wdtFullSyncReg; // Sincronizador da CPU para o wdtFullReg
reg wdtClrReg; // Pedido para zerar o wdtReg
reg wdtClrReqholdReg; // Guarda os pedidos para zerar o WDT
reg [1:0] wdtClrReqReg; // Sincronizador para o wdtClrReg
wire wdtClrAck; // Acknolege para o wdtClrReg
reg wdtClrAckSyncReg; // Sincronizador da CPU para o wdtClrAck
reg wdtFullClrReg; // Pedido para zerar o wdtFullReg
reg [1:0] wdtFullclrReqReg; // Sincronizador da CPU para wdtFullClrReg
// Timer 0 (TMR0)
wire psClk; // clock para o pre-escalador
reg [7:0] pscaleReg; // pre-escalador (range 0 to 255)
reg psFullReg; // clock para o TMR0, do pre-escalador
49
wire incTmrClk; // clock para o TMR0
reg incTmrHoldReg; // Guarda o pedido de increment do TMR0
reg [7:0] rateval; // Guarda configuração do pre-escalador
// Registradores/nós de interrupção
reg [4:0] intriseReg; // detecta a borda positiva da entrada da PORT-B
reg [4:0] intdownReg; // detecta a borda negativa da entrada da PORT-B
// Triggers de interrupção
wire rbint; // trigger do RB4-7
wire inte; // trigger do RB0
reg [4:0] intclrReg; // zera intriseReg e intdownReg
// Autômato
reg [BITS_AUTOMATO-1:0] stateReg;
// Outros registradores
reg writeramReg; // Strobe de escrita da RAM
reg [8:0] ramAdrReg; // Endereço da RAM
reg clkOReg; // clock de saída
// Sincronizadores
reg inteSyncReg;
reg rbintSyncReg;
reg [1:0] incTmrSyncReg;
reg rdEepSyncReg;
reg wrEepSyncReg;
reg mclrSyncReg;
reg poweronSyncReg;
// Nós intermediários do autômato
reg [7:0] ramINode; // resultado da leitura da RAM ou registradores especiais
reg [12:0] incPcNode; // valor da predição do PC
wire [7:0] maskNode; // mascara de bits para operações lógicas
reg [8:0] addNode; // resultado do somador de 8 bits
reg [4:0] addlowNode; // resultado dos 4 bits menos significativos do somador
reg aluoutZeroNode; // 1 se a saída da ALU for 0
reg writewNode; // 1 se o dado for para o registrador W
reg writeramNode; // 1 se o dado for para a RAM ou registradores especiais
reg intNode; // 1 se há uma requisição de interrupção
reg wdtRstNode; // 1 se há uma requisição de reset pelo WDT
reg resetCond; // 1 se há qualquer requisição de reset
//Conexões auxiliares
wire [8:0] ramAdrNode;
wire addrTmr0, addrPcl, addrStat, addrFsr, addrPortA, addrPortB, addrEepDat;
wire addrEepAdr, addrPclath, addrIntcon, addrOption, addrTrisa, addrTrisb;
wire addrEecon1, addrEecon2;
reg aux;
//-------------------------------------------------------------------------------------------------------//
// B) Funções e Tarefas
//-------------------------------------------------------------------------------------------------------//
// 1) Sincronizadores da CPU
always @(posedge clkI)
begin
inteSyncReg = inte;
rbintSyncReg = rbint;
50
wdtClrAckSyncReg = wdtClrAck;
mclrSyncReg = mclrNI;
poweronSyncReg = ponRstNI;
rdEepSyncReg = rdEepAckI;
wrEepSyncReg = wrEepAckI;
incTmrSyncReg[0] = incTmrClk;
incTmrSyncReg[1] = incTmrSyncReg[0];
if (~poweronSyncReg || ~mclrSyncReg)
wdtFullSyncReg = 3'b0;
else
begin
wdtFullSyncReg[0] = wdtFullReg;
wdtFullSyncReg[1] = wdtFullSyncReg[0];
wdtFullSyncReg[2] = wdtFullSyncReg[1];
end
end
// 2) Decodifica o OPcode, somente 1 sinal terá valor logico '1'
assign instCall = (instReg[13:11] == 3'b100 ); //CALL
assign instGoto = (instReg[13:11] == 3'b101 ); //GOTO
assign instBcf = (instReg[13:10] == 4'b0100 ); //BCF
assign instBsf = (instReg[13:10] == 4'b0101 ); //BSF
assign instBtfsc = (instReg[13:10] == 4'b0110 ); //BTSFC
assign instBtfss = (instReg[13:10] == 4'b0111 ); //BTFSS
assign instMovlw = (instReg[13:10] == 4'b1100 ); //MOVLW
assign instRetlw = (instReg[13:10] == 4'b1101 ); //RETLW
assign instSublw = (instReg[13:9] == 5'b11110 ); //SUBLW
assign instAddlw = (instReg[13:9] == 5'b11111 ); //ADDLW
assign instIorlw = (instReg[13:8] == 6'b111000 ); //IORLW
assign instAndlw = (instReg[13:8] == 6'b111001 ); //ANDLW
assign instXorlw = (instReg[13:8] == 6'b111010 ); //XORLW
assign instSubwf = (instReg[13:8] == 6'b000010 ); //SUBWF
assign instDecf = (instReg[13:8] == 6'b000011 ); //DECF
assign instIorwf = (instReg[13:8] == 6'b000100 ); //IORWF
assign instAndwf = (instReg[13:8] == 6'b000101 ); //ANDWF
assign instXorwf = (instReg[13:8] == 6'b000110 ); //XORWF
assign instAddwf = (instReg[13:8] == 6'b000111 ); //ADDWF
assign instMovf = (instReg[13:8] == 6'b001000 ); //MOVF
assign instComf = (instReg[13:8] == 6'b001001 ); //COMF
assign instIncf = (instReg[13:8] == 6'b001010 ); //INCF
assign instDecfsz = (instReg[13:8] == 6'b001011 ); //DECFSZ
assign instRrf = (instReg[13:8] == 6'b001100 ); //RRF
assign instRlf = (instReg[13:8] == 6'b001101 ); //RLF
assign instSwapf = (instReg[13:8] == 6'b001110 ); //SWAPF
assign instIncfsz = (instReg[13:8] == 6'b001111 ); //INCFSZ
assign instMovwf = (instReg[13:7] == 7'b0000001 ); //MOVWF
assign instClrw = (instReg[13:7] == 7'b0000010 ); //CLRW
assign instClrf = (instReg[13:7] == 7'b0000011 ); //CLRF
assign instRet = (instReg[13:0] == 14'b00000000001000); //RET
assign instRetfie = (instReg[13:0] == 14'b00000000001001); //RETFIE
assign instSleep = (instReg[13:0] == 14'b00000001100011); //SLEEP
51
assign instClrwdt = (instReg[13:0] == 14'b00000001100100); //CLRWDT
// 3) Calcula o endereço de acesso a RAM
// O endereço da RAM é BANK+FSR se "d"=0, se não, BANK+"d"
assign ramAdrNode = (instReg[6:0]==0)?
{statusReg[7],fsrReg[7:0]}:{statusReg[6:5],instReg[6:0]};
// 4) Verifica se é um acesso a RAM externa
assign addrSram = (ramAdrNode[6:0] > 7'b0001011); //0CH-7FH,8CH-FFH
// 5) Verifica se é um acesso a um registrador especial, somente 1 sinal terá valor logico
// '1'
assign addrTmr0 = (ramAdrNode[7:0] == 8'b00000001); // 01H
assign addrPcl = (ramAdrNode[6:0] == 7'b0000010); // 02H, 82H
assign addrStat = (ramAdrNode[6:0] == 7'b0000011); // 03H, 83H
assign addrFsr = (ramAdrNode[6:0] == 7'b0000100); // 04H, 84H
assign addrPortA = (ramAdrNode[7:0] == 8'b00000101); // 05H
assign addrPortB = (ramAdrNode[7:0] == 8'b00000110); // 06H
assign addrEepDat = (ramAdrNode[7:0] == 8'b00001000); // 08H
assign addrEepAdr = (ramAdrNode[7:0] == 8'b00001001); // 09H
assign addrPclath = (ramAdrNode[6:0] == 7'b0001010); // 0AH, 8AH
assign addrIntcon = (ramAdrNode[6:0] == 7'b0001011); // 0BH, 8BH
assign addrOption = (ramAdrNode[7:0] == 8'b10000001); // 81H
assign addrTrisa = (ramAdrNode[7:0] == 8'b10000101); // 85H
assign addrTrisb = (ramAdrNode[7:0] == 8'b10000110); // 86H
assign addrEecon1 = (ramAdrNode[7:0] == 8'b10001000); // 88H
assign addrEecon2 = (ramAdrNode[7:0] == 8'b10001001); // 89H
// 6) Constroi a mascara de bits para operações logicas e testes de bit
assign maskNode = 1 << instReg[9:7];
//-------------------------------------------------------------------------------------------------------//
// C) Nós intermediarios
//-------------------------------------------------------------------------------------------------------//
// 5) Determina o destino
52
|| instIncfsz || instComf) ? instReg[7] :
0;
// 1) Leitura da RAM ou outras fontes de dado
assign ramINode = (addrSram) ? ramDatI : // dados da RAM externa
(addrEepDat) ? eepDatReg : // dados da EEPROM externa
(addrTmr0) ? tmr0Reg : // dados do tmr0
(addrPcl) ? pcReg[7:0] : // dados do pcl
(addrStat) ? statusReg : // dados do status
(addrFsr) ? fsrReg : // dados do fsr
(addrPortA) ? {3'b0,((~trisaReg[4:0] & portAOReg[4:0])
|| ( trisaReg[4:0] & portAISyncReg[4:0]))}: // dados da portA
(addrPortB) ? ((~trisbReg[7:0] & portBOReg[7:0])
|| ( trisbReg[7:0] & portBISyncReg[7:0])): // dados da portB
(addrEepAdr) ? eepAdrReg : // dados do eeprom
(addrPclath) ? {3'b0,pclathReg} : // pclath (5 bits)
(addrIntcon) ? intconReg : // dados do intcon
(addrOption) ? optionReg : // dados do option
(addrTrisa) ? {3'b0,trisaReg} : // trisa (5 bits)
(addrTrisb) ? trisbReg : // dados do trisb
(addrEecon1) ? {3'b0,eecon1Reg} : // eecon1 (5 bits)
8'bZ; // Alta impedância
// 2) PC + 1
incPcNode = pcReg + 1;
// 3) Somador (ALU)
// Somador completo de 8 bits com carry
{addNode,aux} = {1'b0,aluinp1Reg, 1'b1} + {1'b0,aluinp2Reg, cIn};
// 4 bits menos significativos do Somador
{addlowNode,aux} = {1'b0,aluinp1Reg[3:0],1'b1} + {1'b0,aluinp2Reg[3:0],cIn};
// 4) Testa se aluout = 0
assign aluoutZeroNode = (aluoutReg == 0)?1:0;
// 6) Requisição de interrupção
intNode = intconReg[7] // GIE
&& ((intconReg[3] && intconReg[0]) // RBIE,RBIF
|| (intconReg[4] && intconReg[1]) // INTE,INTF
|| (intconReg[5] && intconReg[2]) // T0IE,T0IF
|| (intconReg[6] && eecon1Reg[4]));// EEIE,EEIF(EECON1)
// 7) Condições de reset
assign resetCond = (~poweronSyncReg || ~mclrSyncReg ||
(wdtFullSyncReg[1] && ~wdtFullSyncReg[2]))? 1 :0;
//-------------------------------------------------------------------------------------------------------//
// D) Autômato Finito
//-------------------------------------------------------------------------------------------------------//
case (stateReg)
default : // Estados Ilegais (idealmente nunca atingidos)
stateReg = QRESET;
QRESET : // Estado de Reset
begin
pcReg = 0;
statusReg[7:5] = 3'b0;
53
pclathReg = 0;
intconReg[7:1] = 7'b0;
optionReg = -1; // 1 em todos os bits
trisaReg = -1;
trisbReg = -1;
tmr0Reg = 0; // Datasheet: don't care
execOpReg = 0;
intclrReg = -1;
intstartReg = 0;
writeramReg = 0;
sleepflagReg = 0;
// Seta /T0 and /PD
if (~poweronSyncReg) // Power-on Reset
begin
statusReg[4] = 1; // /T0 = 1
statusReg[3] = 1; // /PD = 1
stackPntReg = 0; // Reseta o ponteiro da pilha
end
else if (~mclrSyncReg) // MCLR reset/MCLR wake up do modo sleep
begin
statusReg[4] = 1; // /T0 = 1
statusReg[3] = ~sleepflagReg; // /PD = 1 se reset normal, /PD = 0 se wake up
end
else if (wdtRstNode) // WDT reset/WDT wake up do modo sleep
begin
statusReg[4] = 0; // /T0 = 0
statusReg[3] = ~sleepflagReg; // /PD = 1 se reset normal, /PD = 0 se wake up
end
// A maior parte dos bits do eecon1 são zerados
eecon1Reg[4] = 0;
eecon1Reg[2:0] = 3'b0;
if (~poweronSyncReg) eecon1Reg[3] = 0; // zera o WRERR
else eecon1Reg[3] = eecon1Reg[1]; // escreve o valor de WR em WRERR
// Vai para o estado Q1 se não a sinal de reset
if (~resetCond) stateReg = Q1;
end
Q1 : // Estado Q1
begin
// 1) Zera os registradores externos de interrupção se GIE=0
if (intconReg[7]) intclrReg = 0;
else intclrReg = 1; // Zera o registrador de interrupção
// 2) Leitura das portas (E/S)
portAISyncReg = portAI;
portBISyncReg = portBI;
// 3) Leitura/Escrita da EEPROM
if (~intstartReg)
begin
if (eecon1Reg[0] && rdEepSyncReg) // fim da leitura na EEPROM
begin
eepDatReg = eepDatI;
54
eecon1Reg[0] = 0; // Zera o EECON1_RD
end
if (eecon1Reg[1] && wrEepSyncReg) // fim da escrita na EEPROM
begin
if (intconReg[7] && intconReg[6])
eecon1Reg[4] = 1; // INT (fim da escrita na EEPROM)
eecon1Reg[1] = 0; // zera o EECON1_WR
end
if (execOpReg) ramAdrReg = ramAdrNode; // Endereço de leitura da RAM
end
// 4) Verifica se há requisição para o incremento do TMR0
if (incTmrSyncReg == 2'b01) incTmrHoldReg = 1;
// 5) Proximo estado do autômato
if (resetCond)
stateReg = QRESET;
else
// Se estiver no modo SLEEP, espera até receber um wake-up trigger
if (sleepflagReg && ~intstartReg)
begin
if (inteSyncReg || rbintSyncReg)
begin
// Se ocorrer uma interrupção do PORT-B volta à execução normal,
// Se ocorrer um reset de ou /MCLR vai para o QRESET
sleepflagReg = 0;
stateReg = Q2;
end
end
// Se não estiver no modo SLEEP ou impedido continua a execução
else stateReg = Q2;
end
Q2 : // Estado Q2
begin
// 1) Lê a RAM e especifica os operandos da ALU
if (execOpReg && ~intstartReg) // se não está impedido
begin
// 1.1) Setar os operandos da ALU: aluinp1 e aluinp2
assign aluinp1Reg = (instMovf || instSwapf || instAddwf || instSubwf
|| instAndwf || instIorwf || instXorwf || instDecf
|| instIncf || instRlf || instRrf || instBcf
|| instBsf || instBtfsc || instBtfss || instDecfsz
|| instIncfsz || instComf) ? ramINode : // RAM/Registradores Especiais
(instMovlw || instAddlw || instSublw || instAndlw
|| instIorlw || instXorlw || instRetlw) ? instReg[7:0]: // Valor imediato ("k")
(instClrf || instClrw) ?0 :
wReg; // Registrador W
assign aluinp2Reg = (instDecf || instDecfsz) ? -1 :// Decremento
(instIncf || instIncfsz) ? 1 :// Incremento
(instSublw || instSubwf) ? ~wReg + 1 :// Complemento a 2 (SUB)
(instBcf) ? ~maskNode :
// BCF: AND com a mascara invertida ("1..101..1"), só tem um 0
55
(instBtfsc || instBtfss || instBsf) ? maskNode :
// BSF: OR com o maskNode ("0..010..0")
// FSC ou FSS: AND com o maskNode, compara com 0
wReg; // Registrador W
// 1.2) Setar o pontero do registrador de pilha (POP)
if (instRet || instRetlw || instRetfie)
stackPntReg = stackPntReg - 1; // ciclos 3,2,1,0,7,6...
// 1.3) Setar o registrador ramAdr (seta o endereço de escrita da RAM)
ramAdrReg = ramAdrNode; // RAM write address
end
// 2) Muda o valor do clock de saída
clkOReg = 1;
// 3) Verifica se há requisição para o incremento do TMR0
if (incTmrSyncReg == 2'b01) incTmrHoldReg = 1;
// 4) Proximo estado do autômato
if (resetCond) stateReg = QRESET;
else stateReg = Q3;
end
Q3 : // Estado Q3
begin
// 1) Calculo e armazenamento dos resultados no registrador de saída da ULA
if (execOpReg && ~intstartReg) // se não está impedido
begin
// 1.1) Seta a saída da ALU
assign aluoutReg = (instRlf) ? {aluinp1Reg[6:0],statusReg[0]}: //Rotaciona a
esquerda
(instRrf) ? {statusReg[0],aluinp1Reg[7:1]} : // Rotaciona a direita
(instSwapf) ? {aluinp1Reg[3:0],aluinp1Reg[7:4]} : // Troca os nibbles
(instComf) ? ~aluinp1Reg : // NOT
((instAndlw || instAndwf || instBcf || instBtfsc || instBtfss))
? (aluinp1Reg & aluinp2Reg) : // AND, zerar bits/testar bits
((instBsf || instIorlw || instIorwf))
? (aluinp1Reg | aluinp2Reg) : // OR, setar bits
((instXorlw || instXorwf))
? (aluinp1Reg ^ aluinp2Reg) : // XOR
((instAddlw || instAddwf || instSublw || instSubwf
|| instDecf || instDecfsz || instIncf || instIncfsz))
? addNode[7:0] : // Adição, Subtração, Incremento, Decremento
aluinp1Reg; // Sem alterações
// 1.2) Seta os flags C e DC
if (instAddlw || instAddwf || instSublw || instSubwf)
begin
statusReg[1] = addlowNode[4]; // flag DC
statusReg[0] = addNode[8]; // flag C
end
else if (instRlf) statusReg[0] = aluinp1Reg[7]; // flag C
else if (instRrf) statusReg[0] = aluinp1Reg[0]; // flag C
// 1.3) Seta o bit que habilita a escrita na RAM
if (writeramNode && addrSram) writeramReg = 1;
else writeramReg = 0;
56
end
else writeramReg = 0; // se está impedido
// 2) Verifica se ocorreu interrupção externa e seta o flag de interrupção,
//Incrementa TMR0
if (~intstartReg && intconReg[7]) // GIE
begin
// PORT-B0 INT
if (inteSyncReg)
begin
intconReg[1] = 1; // sea o INTF
intclrReg[0] = 1;
// zera os registradores de interrupção externa (intriseReg(0) e intdownReg(0))
end
// PORT-B[4-7] INT
if (rbintSyncReg)
begin
intconReg[0] = 1; // seta o RBIF
intclrReg[4:1] = -1;
// zera os registradores de interrupção externa (intriseReg(4-1) e intdownReg(4-1))
end
end
// Incrementa o TMR0
if (incTmrHoldReg || (incTmrSyncReg == 2'b01)) // incrementa o trigger
begin
tmr0Reg = tmr0Reg + 1; // incrementa
incTmrHoldReg = 0;
// Se ~intstart, GIE, T0IE e ocorre um estouro no timer, T0IF é setado
if (~intstartReg && intconReg[7] && intconReg[5] && (tmr0Reg == -1))
intconReg[2] = 1; // seta o T0IF
end
// 3) Proximo estado do autômato
if (resetCond) stateReg = QRESET;
else stateReg = Q4;
end
Q4 : // Estado Q4
begin
// 1) Carrega a próxima instrução do programa
instReg = progDatI;
if (~execOpReg && ~intstartReg) // se está impedido
begin
pcReg = incPcNode; // incrementa o PC
execOpReg = 1;
end
else // se não está impedido
begin
// 2) Guarda o resultado dos calculos, determina o PC, os flags e se executa o
//próximo ciclo
// 2.1) Determina o valor do registrador W, se não está em um ciclo impedido
// (~intstartReg) e os destinos de W
if (writewNode) wReg = aluoutReg; // escreve no registrador W
57
// 2.2) Esreve os dados na RAM/Registradores Especiais,
// se não está em um ciclo impedido (~intstartReg)
if (writeramNode)
begin
if (addrStat)
begin
statusReg[7:5] = aluoutReg[7:5]; // escreve IRP, RP1, RP0
statusReg[1:0] = aluoutReg[1:0]; // escreve DC,C
end
if (addrFsr) fsrReg = aluoutReg; // escreve no FSR
if (addrPortA) portAOReg = aluoutReg[4:0]; // escreve na PORT-A
if (addrPortB) portBOReg = aluoutReg; // escreve na PORT-B
if (addrEepDat) eepDatReg = aluoutReg; // escreve no EEDATA
if (addrEepAdr) eepAdrReg = aluoutReg; // escreve no EEADR
if (addrPclath) pclathReg = aluoutReg[4:0]; // escreve no PCLATH
if (addrIntcon) intconReg[6:0] = aluoutReg[6:0];
// escreve no INTCON (exceto GIE)
if (addrOption) optionReg = aluoutReg; // escreve no OPTION
if (addrTrisa) trisaReg = aluoutReg[4:0]; // escreve no TRISA
if (addrTrisb) trisbReg = aluoutReg; // escreve no TRISB
if (addrTmr0) tmr0Reg = aluoutReg; // escreve no TMR0
if (addrEecon1) // escreve no EECON1
begin
eecon1Reg[4:3] = aluoutReg[4:3];
eecon1Reg[2] = aluoutReg[2] && existeepromI;
if (aluoutReg[2:0] == 3'b110) eecon1Reg[1] = 1;
// WR: só é permitido setar esse bit
if (aluoutReg[1:0] == 2'b01) eecon1Reg[0] = 1;
// RD: só é permitido setar esse bit
end
end
// 2.3) Seta/zera o flag Z, se não está em um ciclo impedido
if (~intstartReg)
begin
if (addrStat) statusReg[2] = aluoutReg[2]; // Z=1 if result == 0
else if (instAddlw || instAddwf || instAndlw || instAndwf
|| instClrf || instClrw || instComf || instDecf
|| instIncf || instMovf || instSublw || instSubwf
|| instXorlw || instXorwf)
statusReg[2] = aluoutZeroNode // Z=1 se o resultado != 0
else if (instIorlw || instIorwf)
statusReg[2] = ~aluoutZeroNode;
end
// 2.4) Determina o PC e se deve executar o proximo ciclo.
// Depois de um ciclo impedido ir para o endereço de interrupção.
if (intstartReg)
begin
pcReg = 4; // Endereço de interrupção
execOpReg = 0; // o proximo ciclo é um ciclo impedido
end
58
else if (instRet || instRetlw || instRetfie) // instrução return
begin
pcReg = stackReg[stackPntReg];
execOpReg = 0; // o proximo ciclo é um ciclo impedido
end
else if (instGoto || instCall) // instrução goto/call
begin
pcReg = {pclathReg[4:3],instReg[10:0]};
execOpReg = 0;
end
else if (((instBtfsc || instDecfsz || instIncfsz) && aluoutZeroNode)
|| (instBtfss && ~aluoutZeroNode)) // instrução bitTest
begin
pcReg = incPcNode;
execOpReg = 0;// o proximo ciclo é um ciclo impedido, se for verdadeiro
end
else if (writeramNode && addrPcl) // PCL é o destino
begin
pcReg = pclathReg[4:0] & aluoutReg;
execOpReg = 0;
end
else
begin
// verifica o recebimento de um trigger de interrupção
if (~intNode) pcReg = incPcNode; // se não, próxima instrução é normal
else pcReg = pcReg; // Se sim, o valor do PC deve ser mantido
execOpReg = 1;
end
// 2.5) Faz um push do valor atual do PC na pilha se necessário
// Instrução CALL ou fim de um ciclo impedido de interrupção
if (instCall || intstartReg)
begin
stackReg[stackPntReg] = pcReg; // escreve o valor do PC na pilha
stackPntReg = stackPntReg + 1; // incrementa o ponteiro da pilha
end
// 2.6) Seta o bit GIE no registrador intcon (intconReg(7))
if (~intstartReg)
begin
if (intNode) // recebe um trigger de interrupção
begin
intconReg[7] = 0; // Zera o GIE
intstartReg = 1; // O próximo ciclo será um ciclo impedido
end
else if (instRetfie) // Retorno de uma instrução de interrupção
begin
intconReg[7] = 1;
intstartReg = 0;
end
else if (writeramNode && addrIntcon) // destino é o GIE
begin
59
intconReg[7] = aluoutReg[7];
intstartReg = 0;
end
else intstartReg = 0;
end
else intstartReg = 0;
// 2.7) Seta/Zera os flags /PD e /TO
if (~intstartReg)
if (instClrwdt || (instSleep && (~wdtRstNode && ~intstartReg)))
// CLRWDT ou SLEEP sem nenhum trigger de interrupção
if (instSleep)
begin
sleepflagReg = 1;
statusReg[4:3] = 2'b10; // SLEEP: /T0 = 1, /PD = 0
end
else statusReg[4:3] = 2'b11; // CLRWDT: /T0 = 1, /PD = 1
end
// 3) Zera o flag de abilitação de escrita de dados da SRAM
writeramReg = 0;
// 4) Muda o clock de saída
clkOReg = 0;
// 5) Verifica se há requisição para o incremento do TMR0
if (incTmrSyncReg == 2'b01) incTmrHoldReg = 1;
// 6) Proximo estado do autômato
if (resetCond) stateReg = QRESET;
else stateReg = Q1;
end
endcase
end
//-------------------------------------------------------------------------------------------------------//
// E) TMR0 e Watch Dog
//-------------------------------------------------------------------------------------------------------//
// 1) Pre-escalador do TMR0
// seleciona o pre-escalador
assign psClk = optionReg[5]?(t0ckiI ^ optionReg[4]):clkOReg;
// Corpo do pre-escalador
always @(posedge psClk or negedge ponRstNI)
begin
if (~ponRstNI)
begin
pscaleReg = 0;
psFullReg = 0;
end
else
begin
// seleciona o valor maximo do pre-escalador em PS2-0
case (optionReg[2:0])
3'b000 : rateval = 1;
3'b001 : rateval = 3;
60
3'b010 : rateval = 7;
3'b011 : rateval = 15;
3'b100 : rateval = 31;
3'b101 : rateval = 63;
3'b110 : rateval = 127;
3'b111 : rateval = 255;
default: rateval = 1;
endcase
if (pscaleReg >= rateval)
begin
pscaleReg = 0;
psFullReg = 1;
end
else
begin
pscaleReg = pscaleReg + 1;
psFullReg = 0;
end
end
end
// 2) seleciona o trigger de incremento do TMR0
assign incTmrClk = optionReg[3]?psClk:psFullReg;
// 3) Corpo do WDT
assign wdtInit = ~ponRstNI || ~mclrNI;
always @(posedge wdtClkI or posedge wdtInit)
begin
if (wdtInit) // (async reset)
begin
wdtReg = 0;
wdtFullReg = 0;
wdtClrReqReg = 2'b0;
wdtFullclrReqReg = 2'b0;
end
else
begin
// sincronizadores
// Pedido para zerar o WDT (CLRWDT/SLEEP)
wdtClrReqReg[0] = wdtClrReg;
wdtClrReqReg[1] = wdtClrReqReg[0];
// Pedido para zerar o flag de estouro do WDT (depois do reset por WDT)
wdtFullclrReqReg[0] = wdtFullClrReg && ~sleepflagReg;
wdtFullclrReqReg[1] = wdtFullclrReqReg[0];
// Flag de estouro
if (wdtReg >= WDT_TAMANHO) wdtFullNode = 1;
else wdtFullNode = 0;
// Corpo do contador WDT
if ((wdtClrReqReg == 2'b01) || ~wdtEnaI) wdtReg = 0;
else if (wdtFullNode) wdtReg = 0;
else wdtReg = wdtReg + 1;
// Corpo do trigger de interrupção do estouro do WDT
61
if ((wdtFullclrReqReg == 2'b01) || ~wdtEnaI) wdtFullReg = 0;
else if (wdtFullNode) wdtFullReg = 1;
end
end
// 4) Controlador do WDT
assign wdtClrAck = wdtClrReqReg[1]; // Sinal de acknowledge do reset do WDT
assign wdtFullO = wdtFullReg; // trigger do flag de estouro doWDT para CPU
always @(posedge clkI)
begin
if (~poweronSyncReg || ~mclrSyncReg)
begin
wdtClrReg = 0; // zera o registrador de pedido para zerar o WDT
wdtClrReqholdReg = 0; // 1 quando há algum pedido para zerar o WDT em espera
wdtFullClrReg = 0; // zera o registrador de pedido para zerar o flag de estouro
end
else
begin
// Pedido para zerar oWDT e guarder esse pedido
if (wdtClrReg)
if (wdtClrAckSyncReg) wdtClrReg = 0; // Anula o pedido
else if (wdtClrReqholdReg
|| ((stateReg == Q4)
&& execOpReg && ~intstartReg
&& (instClrwdt || instSleep))) // recebe pedido para zerar o flag
begin
wdtClrReg = 1;
wdtClrReqholdReg = 0;
end
// zera o flag de estouro do WDT (pedido de RESET para a CPU)
if (wdtFullClrReg) wdtFullClrReg = wdtFullSyncReg[1];
end
end
//-------------------------------------------------------------------------------------------------------//
// F) Interrupções
//-------------------------------------------------------------------------------------------------------//
// Wires usados nas listas de sensibilidade (Always@())
assign intclr0 = intclrReg[0];
assign intclr1 = intclrReg[1];
assign intclr2 = intclrReg[2];
assign intclr3 = intclrReg[3];
assign intclr4 = intclrReg[4];
// INT0
always @(posedge int0I or posedge intclr0)
begin
if (intclr0) intriseReg[0] = 0;
else intriseReg[0] = 1;
end
always @(negedge int0I or posedge intclr0)
begin
if (intclr0) intdownReg[0] = 0;
62
else intdownReg[0] = 1;
end
assign rb0Int = optionReg[6]?intriseReg[0]:intdownReg[0];
// INT4
always @(posedge int4I or posedge intclr1)
begin
if (intclr1) intriseReg[1] = 0;
else intriseReg[1] = 1;
end
always @(negedge int4I or posedge intclr1)
begin
if (intclr1) intdownReg[1] = 0;
else intdownReg[1] = 1;
end
assign rb4Int = intriseReg[1] || intdownReg[1];
// INT5
always @(posedge int5I or posedge intclr2)
begin
if (intclr2) intriseReg[2] = 0;
else intriseReg[2] = 1;
end
always @(negedge int5I or posedge intclr2)
begin
if (intclr2) intdownReg[2] = 0;
else intdownReg[2] = 1;
end
assign rb5Int = intriseReg[2] || intdownReg[2];
// INT6
always @(posedge int6I or posedge intclr3)
begin
if (intclr3) intriseReg[3] = 0;
else intriseReg[3] = 1;
end
always @(negedge int6I or posedge intclr3)
begin
if (intclr3) intdownReg[3] = 0;
else intdownReg[3] = 1;
end
assign rb6Int = intriseReg[3] || intdownReg[3];
// INT7
always @(posedge int7I or posedge intclr4)
begin
if (intclr4) intriseReg[4] = 0;
else intriseReg[4] = 1;
end
always @(negedge int7I or posedge intclr4)
begin
if (intclr4) intdownReg[4] = 0;
else intdownReg[4] = 1;
end
63
assign rb7Int = intriseReg[4] || intdownReg[4];
// Decodificação dos triggers de INT
assign inte = intconReg[4] && rb0Int;
assign rbint = intconReg[3] && (rb4Int || rb5Int || rb6Int || rb7Int);
//-------------------------------------------------------------------------------------------------------//
// G) Saídas
//-------------------------------------------------------------------------------------------------------//
assign progAdrO = pcReg; // Endereço da ROM de programa
assign ramAdrO = ramAdrReg; // Endereço da RAM
assign ramDatO = aluoutReg; // Dados para escrita na RAM
assign readramO = (stateReg[1:0] == Q2[1:0]); // Habilita a leitura na RAM
assign writeramO = writeramReg; // Habilita a escrita na RAM
assign eepAdrO = eepAdrReg; // Endereço da EEPROM
assign eepDatO = eepDatReg; // Dados para escrita na EEPROM
assign rdEepReqO = eecon1Reg[0]; // Solicita a leitura na EEPROM
assign wrEepReqO = eecon1Reg[1]; // Solicita a escrita na EEPROM
assign portAO = portAOReg; // Saída PORT-A
assign portADirO = trisaReg; // TRISA
assign portBO = portBOReg; // Saída PORT-B
assign portBDirO = trisbReg; // TRISB
assign rbpuO = optionReg[7]; // RBPU: habilita o pull-up
assign clkO = clkOReg; // clock de saída (clkI/4)
assign powerdownO = sleepflagReg; // Indicador da CPU de parada do clock
assign startclkO = inte || rbint || wdtFullReg || ~mclrNI || ~ponRstNI;
// Indicador da CPU de inicio do clock
//-------------------------------------------------------------------------------------------------------//
endmodule
//-------------------------------------------------------------------------------------------------------//
64
Apêndice B
65
B.2 – Código de Teste 2
; =============================================================
list p=16F84 ; para qual processador o código será gerado
radix hex ; padrão HEXADECIMAL para valores sem identificação
include <P16F84.inc>; anexa arquivo com definições do 16F84
; =============================================================
; Tabela de definições de RAM e constantes
X equ 0CH ; define variável auxiliar X na RAM 0CH (primeira posição)
W equ 0 ; facilita a referencia a W
; =============================================================
; memória de programa
org 0 ;reset
goto inicio ;endereço 000
nop ;endereço 001
nop ;endereço 002
nop ;endereço 003
goto interrupção ;endereço 004. Endereço padrão de interrupção
; =============================================================
interrupção:
retfie; retorna da interrupção
; =============================================================
inicio:
movlw B'00000000' ; W = ajuste para os bits do intcon
movwf INTCON ; INTCON = W
clrf PORTB ; inicializa PORTB
bsf STATUS, RP0 ; seleciona banco 1 para TRISB
movwf B'00000000' ; W = ajuste para os bits do TRISB
movwf TRISB ; TRISB = W; bits= 1 entrada; bits=0 saída
bcf STATUS, RP0 ; retorna ao banco 0 (padrão do Reset)
; =============================================================
principal:
movlw FFh ; W=11111111
clrf X; zera o X
incf X; incrementa o X
andwf X; W=W.X
btfss STATUS, Z ; se Z=1 pula a próxima instrução
movwf PORTB
END ; fim do programa fonte
; =============================================================
66
B.3 – Código de Teste 3
; =============================================================
list p=16F84 ; para qual processador o código será gerado
radix hex ; padrão HEXADECIMAL para valores sem identificação
include <P16F84.inc>; anexa arquivo com definições do 16F84
; =============================================================
; Tabela de definições de RAM e constantes
W equ 0 ;
X equ 0CH ;
W2 equ 0DH ;
; =============================================================
; memória de programa
org 0 ;reset
goto inicio ;endereço 000
nop ;endereço 001
nop ;endereço 002
nop ;endereço 003
goto interrupção ;endereço 004. Endereço padrão de interrupção
; =============================================================
interrupção:
movwf W2 ; salva o W atual
comf X ; inverte X
movf X, W ; W=X
altera:
movwf PORTB ; PORTB recebe os bits complementados
movlw A5h ; W = valor a carregar no TMR0
movwf TMR0 ; TIMER 0 contado valor ajustado até o estouro.
bcf INTCON, TOIF ; reabilita interrupção TIMER 0 antes de voltar
movf W2, W ; recupera o valor de W
retfie; retorna da interrupção
; =============================================================
inicio:
movlw B'00000000' ; W = ajuste para os bits do intcon
movwf INTCON ; INTCON = W
clrf PORTA ; inicializa PORTA e PORTB
clrf PORTB
bsf STATUS, RP0 ; seleciona banco 1 para OPTION e TRIS
movlw B'11011111' ; W = ajuste para os bits do OPTION
movwf OPTION_REG ; OPTION = W
movlw B'11111111' ; W = ajuste para os bits do TRISA
movwf TRISA ; TRISA = W; bits= 1 entrada; bits=0 saída
movwf B'00000000' ; W = ajuste para os bits do TRISB
movwf TRISB ; TRISB = W; bits= 1 entrada; bits=0 saída
bcf STATUS, RP0 ; retorna ao banco 0 (padrão do Reset)
; =============================================================
principal :
movlw 55H ; W=01010101
movwf X ;
movwf PORTB ; PORTB inicializa com 01010101
67
movlw A5h ; primeira carga do registro TMR0
movwf TMR0 ;
bcf INTCON, TOIF ; retiro eventual pedido pendente
bsf INTCON, TOIE ; habilita a interrupção do TMR0
bsf INTCON, GIE ; habilita as interrupções
; =============================================================
loop:
goto loop ; fica aqui indefinidamente
; =============================================================
END ; fim do programa fonte
; =============================================================
68
B.4 – Código de Teste 4
; =============================================================
list p=16F84 ; para qual processador o código será gerado
radix hex ; padrão HEXADECIMAL para valores sem identificação
include <P16F84.inc>; anexa arquivo com definições do 16F84
; =============================================================
; Tabela de definições de RAM e constantes
X equ 0CH ; define variável auxiliar X na RAM 0CH (primeira posição)
W equ 0 ; facilita a referencia a W
; =============================================================
; memória de programa
org 0 ;reset
goto inicio ;endereço 000
nop ;endereço 001
nop ;endereço 002
nop ;endereço 003
goto interrupção ;endereço 004. Endereço padrão de interrupção
; =============================================================
interrupção:
retfie; retorna da interrupção
; =============================================================
inicio:
movlw B'00000000' ; W = ajuste para os bits do intcon
movwf INTCON ; INTCON = W
clrf PORTB ; inicializa PORTB
bsf STATUS, RP0 ; seleciona banco 1 para TRISB
movwf B'00000000' ; W = ajuste para os bits do TRISB
movwf TRISB ; TRISB = W; bits= 1 entrada; bits=0 saída
bcf STATUS, RP0 ; retorna ao banco 0 (padrão do Reset)
; =============================================================
principal:
movlw FFh ; W=11111111
movwf X ; X=W=11111111
bcf STATUS, C ; bit carry=0
repete:
rlf X ; desloca o valor em X para a direita
btfss STATUS, C ; se C=1 pula a próxima instrução
goto fim ; quando carry=0 acabou de deslocar 8 vezes
movf X, W ; W = valor em X
escreve:
movwf PORTB ; PORTB=W=X
goto repete ; se carry=1 efetua novo deslocamento
fim:
END ; fim do programa fonte
; =============================================================
69