Projeto | Quarto SoC escrito em VHDL |
---|---|
Autor | Richard James Howe |
Direitos autorais | 2013-2019 Richard Howe |
Licença | MIT/LGPL |
[email protected] |
Este projeto implementa um computador small stack adaptado para executar Forth baseado na CPU J1. O processador foi reescrito em VHDL da Verilog e ligeiramente ampliado.
Os objetivos do projeto são os seguintes:
Todos os três foram concluídos.
O processador H2, assim como o J1, é um processador baseado em pilha que executa um conjunto de instruções especialmente adequado para FORTH.
O alvo atual é a placa Nexys3, com um FPGA Xilinx Spartan-6 XC6LX16-CS324, novas placas serão direcionadas no futuro, pois esta placa está chegando ao fim de sua vida útil. O VHDL é escrito de forma genérica, com componentes de hardware sendo inferidos em vez de instanciados explicitamente, isso deve tornar o código bastante portátil, embora as interfaces para os componentes da placa Nexys3 sejam específicas para os periféricos dessa placa.
Um vídeo do projeto em ação, no hardware, pode ser visto aqui:
O SoC também pode ser simulado com um simulador escrito em C, conforme mostrado abaixo:
A arquitetura do sistema é a seguinte:
As licenças usadas pelo projeto são mistas e por arquivo. Para meu código eu uso a licença MIT - então fique à vontade para usá-la como desejar. As outras licenças utilizadas são a LGPL e a licença Apache 2.0, elas estão confinadas a módulos únicos, portanto podem ser removidas se você tiver alguma aversão ao código LGPL.
O único alvo disponível no momento é o Nexys3, isso deve mudar no futuro, já que o tabuleiro está no fim da vida útil. As próximas placas que pretendo oferecer suporte são sua sucessora, a Nexys 4, e a myStorm BlackIce (https://mystorm.uk/). A placa myStorm usa um conjunto de ferramentas totalmente de código aberto para síntese, localização e rota e geração de arquivos de bits.
A compilação foi testada no Debian Linux, versão 8.
Você precisará de:
Hardware:
O Xilinx ISE pode (ou poderia ser) baixado gratuitamente, mas requer registro. O ISE precisa estar no seu caminho:
PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64;
PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/lib/lin64;
Para fazer o conjunto de ferramentas baseado em C:
make embed.hex
Para criar um arquivo de bits que possa ser transferido para o quadro de destino:
make simulation synthesis implementation bitfile
Para fazer upload do bitfile para o quadro de destino:
make upload
Para visualizar a forma de onda gerada por "fazer simulação":
make viewer
O simulador CLI baseado em C pode ser invocado com:
make run
Que montará o arquivo fonte H2 Forth embed.fth e executará o arquivo objeto montado no simulador H2 com o depurador ativado. Um simulador gráfico pode ser executado com:
make gui-run
O que requer freeglut e também um compilador C.
O projeto J1 original está disponível em:
Este projeto tem como alvo o núcleo J1 original e fornece uma implementação eForth (escrita usando Gforth para meta-compilação/compilação cruzada para o núcleo J1). Ele também fornece um simulador para o sistema escrito em C.
O interpretador eForth no qual o meta-compilador é construído pode ser encontrado em:
O processador H2 e os periféricos associados agora estão bastante estáveis, porém a fonte é sempre o guia definitivo de como as instruções e os periféricos se comportam, bem como o mapa de registros.
Existem algumas modificações na CPU J1 que incluem:
A CPU H2 se comporta de forma muito semelhante à CPU J1, e o PDF J1 pode ser lido para melhor entendimento deste processador. O processador é de 16 bits com instruções que executam um único ciclo de clock. A maioria das palavras Forth primitivas também podem ser executadas em um único ciclo, uma exceção notável é store ("!"), que é dividida em duas instruções.
A CPU possui o seguinte estado:
Carrega e armazena no bloco RAM que contém o programa H2 descarta o bit mais baixo, todas as outras operações de memória usam o bit inferior (como saltos, cargas e armazenamentos em periféricos de entrada/saída). Isso ocorre para que os aplicativos possam usar o bit mais baixo para operações de caracteres ao acessar a RAM do programa.
O conjunto de instruções é decodificado da seguinte maneira:
+---------------------------------------------------------------+
| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------------+
| 1 | LITERAL VALUE |
+---------------------------------------------------------------+
| 0 | 0 | 0 | BRANCH TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 0 | 1 | CONDITIONAL BRANCH TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 1 | 0 | CALL TARGET ADDRESS |
+---------------------------------------------------------------+
| 0 | 1 | 1 | ALU OPERATION |T2N|T2R|N2A|R2P| RSTACK| DSTACK|
+---------------------------------------------------------------+
| F | E | D | C | B | A | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---------------------------------------------------------------+
T : Top of data stack
N : Next on data stack
PC : Program Counter
LITERAL VALUES : push a value onto the data stack
CONDITIONAL : BRANCHS pop and test the T
CALLS : PC+1 onto the return stack
T2N : Move T to N
T2R : Move T to top of return stack
N2A : STORE T to memory location addressed by N
R2P : Move top of return stack to PC
RSTACK and DSTACK are signed values (twos compliment) that are
the stack delta (the amount to increment or decrement the stack
by for their respective stacks: return and data)
Todas as operações da ALU substituem T:
Valor | Operação | Descrição |
---|---|---|
0 | T | Topo da pilha |
1 | N | Copiar T para N |
2 | T+N | Adição |
3 | T&N | E bit a bit |
4 | Rasgado | OU bit a bit |
5 | T ^ N | XOR bit a bit |
6 | ~T | Inversão bit a bit |
7 | T=N | Teste de igualdade |
8 | N Comparação assinada | |
9 | N >> T | Mudança Lógica para a Direita |
10 | T-1 | Diminuir |
11 | R | Topo da pilha de retorno |
12 | [T] | Carregar do endereço |
13 | N << T | Deslocamento Lógico para a Esquerda |
14 | profundidade | Profundidade da pilha |
15 | N você< T | Comparação não assinada |
16 | Definir estado da CPU | Habilitar interrupções |
17 | Obtenha o estado da CPU | As interrupções estão ativadas? |
18 | profundidade | Profundidade de retorno stk |
19 | 0= | T == 0? |
20 | ID da CPU | Identificador de CPU |
21 | LITERAL | Instrução Interna |
Os registros marcados com o prefixo 'o' são registros de saída, aqueles com o prefixo 'i' são registros de entrada. Os registros são divididos em uma seção de registros de entrada e saída e os endereços dos registros de entrada e saída não correspondem entre si em todos os casos.
Os seguintes periféricos foram implementados no SoC VHDL para fazer interface com dispositivos na placa Nexys3:
O SoC também apresenta um conjunto limitado de interrupções que podem ser habilitadas ou desabilitadas.
O mapa do registro de saída:
Cadastre-se | Endereço | Descrição |
---|---|---|
oUart | 0x4000 | Registro UART |
oVT100 | 0x4002 | Gravação no Terminal VT100 |
oLeds | 0x4004 | Saídas LED |
oTimerCtrl | 0x4006 | Controle de temporizador |
oMemDout | 0x4008 | Saída de dados de memória |
oMemControl | 0x400A | Controle de memória / endereço Hi |
oMemAddrLow | 0x400C | Endereço de memória baixa |
o7SegLED | 0x400E | 4 x display LED de 7 segmentos |
oIrcMask | 0x4010 | Máscara de interrupção da CPU |
oUartBaudTx | 0x4012 | Configuração do relógio UART Tx Baud |
oUartBaudRx | 0x4014 | Configuração do relógio UART Rx Baud |
Os registros de entrada:
Cadastre-se | Endereço | Descrição |
---|---|---|
iUart | 0x4000 | Registro UART |
iVT100 | 0x4002 | Status do terminal e teclado PS/2 |
iSwitches | 0x4004 | Botões e interruptores |
iTimerDin | 0x4006 | Valor atual do temporizador |
iMemDin | 0x4008 | Entrada de dados de memória |
A seguinte descrição dos registros deve ser lida em ordem e também descrever como funcionam os periféricos.
Um UART com taxa de transmissão e formato fixos (115200, 8 bits, 1 bit de parada) está presente no SoC. A UART possui um FIFO de profundidade 8 nos canais RX e TX. O controle do UART é dividido entre oUart e iUart.
Para escrever um valor no UART, afirme TXWE junto com a colocação dos dados em TXDO. O estado FIFO pode ser analisado observando o registro iUart.
Para ler um valor do UART: iUart pode ser verificado para ver se os dados estão presentes no FIFO, se estiver assert RXRE no registrador oUart, no próximo ciclo de clock os dados estarão presentes no registrador iUart.
A taxa de transmissão do UART pode ser alterada reconstruindo o projeto VHDL, comprimento de bits, bits de paridade e bits de parada só podem ser alterados com modificações em uart.vhd
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X |TXWE| X | X |RXRE| X | X | TXDO |
+-------------------------------------------------------------------------------+
TXWE: UART TX Write Enable
RXRE: UART RX Read Enable
TXDO: UART TX Data Output
O dispositivo VGA Text emula um terminal com o qual o usuário pode conversar escrevendo no registro oVT100. Ele suporta um subconjunto da funcionalidade do terminal VT100. A interface se comporta como escrever em uma UART com os mesmos sinais de ocupado e de controle. A entrada é obtida de um teclado PS/2 disponível na placa, que se comporta como o mecanismo RX da UART.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X |TXWE| X | X |RXRE| X | X | TXDO |
+-------------------------------------------------------------------------------+
TXWE: VT100 TX Write Enable
RXRE: UART RX Read Enable
TXDO: UART TX Data Output
Na placa Nexys3 existe um banco de LEDs que ficam situados próximos aos interruptores, esses LEDs podem ser ligados (1) ou desligados (0) escrevendo no LEDO. Cada LED aqui corresponde ao switch ao lado.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | X | X | X | X | X | LEDO |
+-------------------------------------------------------------------------------+
LEDO: LED Output
O temporizador é controlável pelo registro oTimerCtrl, é um temporizador de 13 bits rodando a 100 MHz, pode opcionalmente gerar interrupções e a contagem interna dos temporizadores atuais pode ser lida novamente com o registro iTimerDin.
O temporizador conta quando o bit TE é ativado, quando o temporizador atinge o valor TCMP, ele é encerrado e pode, opcionalmente, gerar uma interrupção afirmando INTE. Isso também alterna as linhas Q e NQ que saem do temporizador e são roteadas para os pinos da placa (consulte o arquivo de restrições top.ucf para os pinos).
O temporizador pode ser redefinido escrevendo para RST.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| TE | RST|INTE| TCMP |
+-------------------------------------------------------------------------------+
TE: Timer Enable
RST: Timer Reset
INTE: Interrupt Enable
TCMP: Timer Compare Value
O núcleo H2 possui um mecanismo para interrupções, as interrupções devem ser habilitadas ou desabilitadas com uma instrução. Cada interrupção pode ser mascarada com um bit no IMSK para habilitar aquela interrupção específica. Um '1' em um bit de IMSK habilita aquela interrupção específica, que será entregue à CPU se interrupções forem habilitadas dentro dela.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | X | X | X | X | X | IMSK |
+-------------------------------------------------------------------------------+
IMSK: Interrupt Mask
Este registro é usado para definir a frequência de baud e de clock de amostra apenas para transmissão.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| BTXC |
+-------------------------------------------------------------------------------+
BTXC: Baud Clock Settings
Este registro é usado para definir a freqüência de baud e de clock de amostragem apenas para recepção.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| BRXC |
+-------------------------------------------------------------------------------+
BRXC: Baud Clock Settings
Dados a serem enviados para o endereço selecionado quando a habilitação de gravação (WE) for emitida no oMemControl.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Data Ouput |
+-------------------------------------------------------------------------------+
Este registro contém os registros de controle da memória integrada da placa Nexys3. A placa contém três dispositivos de memória, dois dispositivos de memória não volátil e um dispositivo baseado em RAM volátil. Os dois dispositivos acessíveis por uma interface SRAM simples (um M45W8MW16 volátil, um não volátil - um NP8P128A13T1760E) são ambos acessíveis, o terceiro é um dispositivo de memória baseado em SPI, NP5Q128A13ESFC0E) e atualmente não está acessível.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| OE | WE | RST|WAIT| RCS| FCS| Address Hi |
+-------------------------------------------------------------------------------+
OE: Output Enable - enable reading from current address into iMemDin
WE: Write Enable - enable writing oMemDout into ram at current address
RST: Reset the Flash memory controller
RCS: RAM Chip Select, Enable Volatile Memory
FCS: Flash Chip Select, Enable Non-Volatile Memory
Address Hi: High Bits of RAM address
OE e WE são mutuamente exclusivos; se ambos forem definidos, não haverá efeito.
O controlador de memória está em desenvolvimento ativo e a interface para ele pode mudar.
Estes são os bits de endereço inferiores da RAM.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Address Lo |
+-------------------------------------------------------------------------------+
Na placa Nexys3 existe um banco de displays de 7 segmentos, com ponto decimal (8 segmentos na verdade), que pode ser utilizado para saída numérica. Os segmentos de LED não podem ser endereçados diretamente. Em vez disso, o valor armazenado em L8SD é mapeado para um valor de exibição hexadecimal (ou um valor BCD, mas isso requer regeneração do SoC e modificação de um genérico no VHDL).
O valor '0' corresponde a um zero exibido no segmento LED, '15' a um 'F', etc.
Existem 4 exibições seguidas.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| L7SD0 | L7SD1 | L7SD2 | L7SD3 |
+-------------------------------------------------------------------------------+
L7SD0: LED 7 Segment Display (leftmost display)
L7SD1: LED 7 Segment Display
L7SD2: LED 7 Segment Display
L7SD3: LED 7 Segment Display (right most display)
O registro iUart funciona em conjunto com o registro oUart. O status do FIFO que armazena a transmissão e a recepção de bytes está disponível no registro iUart, bem como em quaisquer bytes recebidos.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X |TFFL|TFEM| X |RFFL|RFEM| RXDI |
+-------------------------------------------------------------------------------+
TFFL: UART TX FIFO Full
TFEM: UART TX FIFO Empty
RFFL: UART RX FIFO Full
RFEM: UART RX FIFO Empty
RXDI: UART RX Data Input
O registro iVT100 funciona em conjunto com o registro oVT100. O status do FIFO que armazena tanto a transmissão quanto a recepção de bytes está disponível no registro iVT100, bem como em quaisquer bytes recebidos. Funciona da mesma forma que os registros iUart/oUart.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X |TFFL|TFEM| X |RFFL|RFEM| 0 | ACHR |
+-------------------------------------------------------------------------------+
TFFL: VGA VT100 TX FIFO Full
TFEM: VGA VT100 TX FIFO Empty
RFFL: PS2 VT100 RX FIFO Full
RFEM: PS2 VT100 RX FIFO Empty
ACHR: New character available on PS2 Keyboard
Este registro contém o valor atual do contador dos temporizadores.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | TCNT |
+-------------------------------------------------------------------------------+
TCNT: Timer Counter Value
iSwitches contém linhas de entrada de múltiplas fontes. Os botões (BUP, BDWN, BLFT, BRGH e BCNT) correspondem a um D-Pad na placa Nexys3. Os interruptores (TSWI) são os mencionados nos oLeds, cada um possui um LED próximo a eles.
As chaves e os botões já estão debounced no hardware para que não precisem ser processados posteriormente depois de lidos nesses registros.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| X | X | X | BUP|BDWN|BLFT|BRGH|BCNT| TSWI |
+-------------------------------------------------------------------------------+
BUP: Button Up
BDWN: Button Down
BLFT: Button Left
BRGH: Button Right
BCNT: Button Center
TSWI: Two Position Switches
Entrada de memória, seja de SRAM ou Flash, indexada por oMemControl e oMemAddrLow. Ao ler do flash, isso pode ser, na verdade, informações de status ou informações da tabela de consulta.
+-------------------------------------------------------------------------------+
| 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+-------------------------------------------------------------------------------+
| Data Input |
+-------------------------------------------------------------------------------+
As seguintes rotinas de serviço de interrupção são definidas:
Nome | Número | Descrição |
---|---|---|
isrNenhum | 0 | Não usado |
isrRxFifoNotEmpty | 1 | UART RX FIFO não está vazio |
isrRxFifoFull | 2 | UART RX FIFI está cheio |
isrTxFifoNotEmpty | 3 | UART TX FIFO não está vazio |
isrTxFifoFull | 4 | UART TX FIFO está cheio |
isrKbdNovo | 5 | Novo personagem do teclado PS/2 |
isrTimer | 6 | Contador de temporizador |
isrDPadButton | 7 | Qualquer estado de mudança de botão D-Pad |
Quando ocorre uma interrupção e as interrupções são habilitadas no processador, uma chamada para o local na memória é realizada - o local é igual ao número ISR. Um ISR com número '4' realizará uma chamada (não um salto) para o local '4' na memória, por exemplo.
As interrupções têm uma latência de pelo menos 4-5 ciclos antes de serem acionadas, há um atraso de dois a três ciclos no manipulador de solicitação de interrupção, então a chamada para o local do ISR na memória deve ser feita e, em seguida, a chamada para o palavra que implementa o próprio ISR.
Se duas interrupções ocorrerem ao mesmo tempo, elas serão processadas do número de interrupção mais baixo para o mais alto.
As interrupções são perdidas quando ocorre uma interrupção com o mesmo número que não foi processada.
O simulador baseado em Disassembler e C para o H2 está em um único programa (veja h2.c). Este simulador complementa o banco de testes VHDL tb.vhd e não o substitui. O meta-compilador é executado sobre um interpretador eForth e está contido nos arquivos embed.ce embed.blk. O meta-compilador (linguagem Forth para compilador cruzado) é um programa Forth que é usado para criar a imagem eForth que é executada no destino.
O conjunto de ferramentas está atualmente em mudança, daqui para frente é provável que haja mais integração entre h2.c e embed.c, juntamente com a mudança da Máquina Virtual Incorporada para uma que se assemelhe mais à CPU H2 com o objetivo de longo prazo de criar uma auto-hospedagem sistema.
Para construir ambos, é necessário um compilador C, o alvo de construção "h2" construirá o executável, h2, e "embed" construirá o meta-compilador:
make h2 embed
E pode ser executado no arquivo de origem embed.fth com o alvo make:
make run
O arquivo make não é necessário:
Linux:
cc -std=c99 h2.c -o h2 # To build the h2 executable
cc -std=c99 embed.c -o embed # To build the embed VM executable
./embed embed.blk embed.hex embed.fth # Create the target eForth image
./h2 -h # For a list of options
./h2 -r embed.hex # Run the assembled file
Windows:
gcc -std=c99 h2.c -o h2.exe # Builds the h2.exe executable
gcc -std=c99 embed.c -o embed.exe # Builds the embed.exe executable
embed.exe embed.blk embed.hex embed.fth # Create the target eForth iamge
h2.exe -h # For a list of options
h2.exe -r embed.hex # Run the assembled file
Uma lista de opções de linha de comando disponíveis:
- stop processing options, following arguments are files
-h print a help message and exit
-v increase logging level
-d disassemble input files (default)
-D full disassembly of input files
-T Enter debug mode when running simulation
-r run hex file
-L # load symbol file
-s # number of steps to run simulation (0 = forever)
-n # specify NVRAM block file (default is nvram.blk)
file* file to process
Este programa é lançado sob a licença MIT, fique à vontade para usá-lo e modificá-lo como desejar. Com modificações mínimas, ele deverá ser capaz de montar programas para o núcleo J1 original.
O meta-compilador é executado em cima da máquina virtual incorporada, é uma máquina virtual de 16 bits que originalmente descende da CPU H2. O projeto inclui um esquema de metacompilação que permite que uma imagem do eForth gere uma nova imagem do eForth com modificações. Esse sistema foi adaptado para uso com o H2, que substituiu o compilador cruzado escrito em C, que permitiu a criação da primeira imagem do H2.
O meta-compilador é um programa Forth comum e está contido em embed.fth. O programa meta-compilador Forth é então usado para construir uma imagem eForth capaz de rodar no alvo H2.
Para obter mais informações sobre metacompilação no Forth, consulte:
O desmontador pega um arquivo de texto contendo o programa montado, que consiste em números hexadecimais de 16 bits. Em seguida, ele tenta desmontar as instruções. Também pode ser alimentado um arquivo de símbolos que pode ser gerado pelo montador e tentar encontrar os locais para os quais os saltos e chamadas apontam.
O desmontador é usado por um script tcl chamado por GTKwave, ele transforma o rastreamento de instruções do H2 de uma série de números nas instruções e destinos de ramificação que eles representam. Isso torna a depuração do VHDL muito mais fácil.
O traço roxo mostra as instruções desmontadas.
O simulador em C implementa o núcleo H2 e a maior parte do SoC. O IO do simulador não tem precisão de ciclo, mas pode ser usado para executar e depurar programas com resultados muito semelhantes ao comportamento do hardware. Isso é muito mais rápido do que reconstruir o arquivo de bits usado para atualizar o FPGA.
O simulador também inclui um depurador, projetado para ser semelhante ao programa DEBUG.COM disponível em DOS. O depurador pode ser usado para desmontar seções de memória, inspecionar o status dos periféricos e despejar seções de memória na tela. Ele também pode ser usado para definir pontos de interrupção, etapa única e executar o código até que um ponto de interrupção seja atingido.
Para executar o depurador, um arquivo hexadecimal ou um arquivo de origem deve ser fornecido:
# -T turns debugging mode on
./h2 -T -r file.hex # Run simulator
Ambos os modos de operação podem ser aumentados com um arquivo de símbolos, que lista onde variáveis, rótulos e funções estão localizadas com o núcleo montado.
Quando a opção "-T" é fornecida, o modo de depuração será inserido antes da simulação ser executada. Um prompt deverá aparecer e a linha de comando deverá ficar assim:
$ ./h2 -T -R h2.fth
Debugger running, type 'h' for a list of command
debug>
Os pontos de interrupção podem ser definidos simbolicamente ou por localização do programa. O comando 'b' é usado para definir pontos de interrupção:
Os números podem ser inseridos em octal (prefixar o número com '0'), hexadecimal (prefixar com '0x') ou em decimal. Por exemplo, os três comandos debug a seguir definem um ponto de interrupção no mesmo local:
debug> b 16
debug> b 0x10
debug> b 020
'k' pode ser usado para listar os pontos de interrupção atuais definidos:
debug> k
0x0010
Isso define um ponto de interrupção quando a função "chave?" é chamado:
debug> b key?
Funções e rótulos podem ser interrompidos, isso requer que um arquivo de símbolos seja especificado na linha de comando ou montagem e execução para ser usado em um arquivo de origem, não em um arquivo hexadecimal. Arquivos de símbolos podem ser usados em arquivos de origem ou hexadecimais.
Para uma única etapa, o comando 's' pode ser fornecido, embora não aconteça muita coisa se o rastreamento estiver desativado (o rastreamento está desativado por padrão). O rastreamento pode ser ativado ou desativado com o comando 't':
debug> s
debug> s
debug> t
trace on
debug> s
0001: pc(089a) inst(4889) sp(0) rp(0) tos(0000) r(0000) call 889 init
debug> s
0002: pc(0889) inst(807a) sp(0) rp(1) tos(0000) r(089b) 7a
debug> s
0003: pc(088a) inst(e004) sp(1) rp(1) tos(007a) r(089b) 6004
É aconselhável desligar o rastreamento ao executar o comando 'c' ou continuar.
O '.' O comando pode ser usado para exibir o estado interno dos núcleos H2:
debug> .
Return Stack:
0000: 0000 08aa 0883 017b 0000 031b 0000 ffb0 0000 02eb ffb5 0210 0167 0167
0167 0167
0010: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000
Variable Stack:
tos: 0000
0001: 0000 0000 0000 0001 0004 0005 0000 ffb0 0000 0000 0000 0000 0000 0000
0000 0000
0011: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000
pc: 0538
rp: 0001
dp: 0000
ie: false
E o comando 'p' pode ser usado para exibir o estado dos periféricos simulados:
debug> p
LEDS: 00
VGA Cursor: 0005
VGA Control: 007a
Timer Control: 8032
Timer: 001b
IRC Mask: 0000
UART Input: 6c
LED 7seg: 0005
Switches: 00
LFSR: 40ba
Waiting: false
Para obter uma lista completa de comandos, use o comando 'h'.
Outras maneiras de entrar no modo de depuração incluem colocar a diretiva assembler ".break" no código-fonte (isso só funciona se o comando montar e executar for usado em arquivos de origem, não em arquivos hexadecimais) e pressionar o caractere de escape quando o simulador for tentando ler dados através do teclado UART ou PS/2 simulado (o escape ainda será passado para o simulador, mas também ativa o modo de depuração).
Um programa separado pode ser compilado e testado em Linux e Windows. Isso simula os periféricos da placa Nexys3 com os quais o SoC faz interface, mas fornece um ambiente gráfico, diferente do utilitário de linha de comando. É mais fácil interagir com o dispositivo e ver o que ele está fazendo, mas as sessões de depuração são menos controladas. Requer excesso grátis.
Abaixo está uma imagem de uma sessão em execução no simulador GUI:
A construção pode ser feita com
make gui
E correndo:
make gui-run
Ou:
./gui h2.hex (on Linux)
gui.exe h2.hex (on Windows)
A compilação do Linux deve funcionar quando o pacote de desenvolvimento gratuito estiver instalado em seu sistema, a compilação do Windows pode exigir alterações no sistema de compilação e/ou instalação manual do compilador, bibliotecas e cabeçalhos.
O mapa principal atual é:
Up Activate Up D-Pad Button, Release turns off
Down Activate Down D-Pad Button, Release turns off
Left Activate Left D-Pad Button, Release turns off
Right Activate Right D-Pad Button, Release turns off
F1 - F8 Toggle Switch On/Off, F1 is left most, F8 Right Most
F11 Toggle UART/PS2 Keyboard Input
F12 Toggle Debugging Information
Escape Quit simulator
Todas as outras teclas do teclado são redirecionadas para a entrada do teclado UART ou PS/2.
Os botões Switches e D-Pad podem ser clicados para ligá-los, os interruptores ligam com cliques esquerdos e desligam com cliques direitos. Os botões dos D-Pads são ativados com um clique em cima deles e desativados com o pressionamento de uma tecla em qualquer lugar da tela.
Os componentes VHDL usados neste sistema são projetados para serem reutilizáveis e portáteis entre diferentes cadeias de ferramentas e fornecedores. Componentes de hardware, como bloco de RAM, são inferidos e não instanciados explicitamente. Os componentes também são feitos para serem tão genéricos quanto possível, com a maioria tendo larguras selecionáveis. Isto seria levado ao extremo, mas infelizmente muitos fornecedores ainda não suportam o padrão VHDL-2008.
Arquivo | Licença | Autor | Descrição |
---|---|---|---|
utilitário.vhd | MIT | Richard J. Howe | Uma coleção de componentes genéricos |
h2.vhd | MIT | Richard J. Howe | H2 Quarto Núcleo de CPU |
uart.vhd | MIT | Richard J. Howe | UART TX/RX (tempo de execução personalizável) |
vga.vhd | LGPL3.0 | Javier V Garcia | Tela VGA 80x40 em modo texto |
Richard J. Howe | (e emulador de terminal VT100) | ||
kbd.vhd | ??? | Scott Larson | Teclado PS/2 |
A linguagem pseudo Forth usada como assembler é descrita acima, o aplicativo que realmente roda no núcleo Forth é em si um intérprete Forth. Esta seção descreve o interpretador Forth que é executado no H2 Core e está contido em embed.fth.
PENDÊNCIA:
Existem várias linguagens usadas neste projeto, todas radicalmente diferentes umas das outras e requerem seu próprio conjunto de padrões de codificação e guias de estilo.
Nomes de sinais comuns:
clk - The system clock
rst - A reset signal for the module
we - Write Enable
re - Read Enable
di - Data In
din - Data In
do - Data Out
dout - Data Out
control - Generally an input to a register, the documentation
for the module will need to be consulted to find out
what each bit means
signal_we - The write enable for 'signal'
signal_i - This is an input signal
signal_o - This is an output signal
Geralmente o uso dos sufixos "_i" e "_o" não é usado, os módulos são mantidos curtos e os nomes escolhidos de forma que seu significado seja óbvio. Esta regra poderá ser revista à medida que o projeto crescer.
Os componentes devem:
constant N: positive := 4;
signal a: std_logic_vector(N - 1 downto 0) := (others => '1');
Em vez de:
signal a: std_logic_vector(3 downto 0) := x"F";
As regras de estilo são as seguintes:
Um exemplo de diretrizes de formatação, que descreve um registro simples de largura arbitrária:
-- Lots of comments about what the unit does should go
-- here. Describe the waveforms, states and use ASCII
-- art where possible.
library ieee, work;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all; -- numeric_std not std_logic_arith
entity reg is -- generic and port indented one tab, their parameters two
generic (
N: positive); -- Generic parameters make for a generic component
port (
clk: in std_logic; -- standard signal names
rst: in std_logic; --
we: in std_logic;
di: in std_logic_vector(N - 1 downto 0);
do: out std_logic_vector(N - 1 downto 0)); -- note the position of ");
end entity; -- "end entity", not "end reg"
architecture rtl of reg is
signal r_c, r_n: std_logic_vector(N - 1 downto 0) := (others => '0');
begin
do <= r_c;
process(rst, clk)
begin
if rst = '1' then -- asynchronous reset
r_c <= (others => '0');
elsif rising_edge(clk) then -- rising edge, not "clk'event and clk = '1'"
r_c <= r_n;
end if;
end process;
process(r_c, di, we)
begin
r_n <= r_c;
if we = '1' then
r_n <= di;
end if;
end process;
end; -- "end" or "end architecture"
Há bastante código C usado neste projeto, usado para fazer uma cadeia de ferramentas para o núcleo H2 e para simular o sistema.
Não há nada de surpreendente sobre o código C aqui, então algumas das exceções devem ser tratadas.
static const char *alu_op_to_string(uint16_t instruction) {
/* notice also that the 'case' clauses are inline with the
* switch selector */
switch (ALU_OP(instruction)) {
case ALU_OP_T: return "T";
case ALU_OP_N: return "N";
case ALU_OP_T_PLUS_N: return "T+N";
case ALU_OP_T_AND_N: return "T&N";
case ALU_OP_T_OR_N: return "T|N";
case ALU_OP_T_XOR_N: return "T^N";
case ALU_OP_T_INVERT: return "~T";
case ALU_OP_T_EQUAL_N: return "N=T";
case ALU_OP_N_LESS_T: return "T>N";
case ALU_OP_N_RSHIFT_T: return "N>>T";
case ALU_OP_T_DECREMENT: return "T-1";
case ALU_OP_R: return "R";
case ALU_OP_T_LOAD: return "[T]";
case ALU_OP_N_LSHIFT_T: return "N<N";
case ALU_OP_ENABLE_INTERRUPTS: return "seti";
case ALU_OP_INTERRUPTS_ENABLED: return "iset?";
case ALU_OP_RDEPTH: return "rdepth";
case ALU_OP_T_EQUAL_0: return "0=";
case ALU_OP_CPU_ID: return "cpu-id";
default: return "unknown";
}
}
if (foo)
bar();
else
baz();
picocom --omap delbs -b 115200 -e b /dev/ttyUSB1