Ao desenvolver dispositivos diferentes, você costuma ter um problema: o algoritmo de dispositivo para dispositivo é repetido em alguns locais e os próprios dispositivos são completamente diferentes. Eu tenho três dispositivos em desenvolvimento que, em alguns lugares, repetem a funcionalidade um do outro, eles usam três processadores diferentes (três arquiteturas diferentes), mas existe apenas um algoritmo. De alguma forma, para unificar tudo, foi planejado escrever uma máquina virtual mínima.
Em geral, olhei para o bytecode do Java, Lua e outras máquinas, mas não queria reescrever toda a bagagem disponível em outro idioma. Então decidimos o idioma - C. Embora Java ou Lua ainda pareça atraente. [1] [2] [3] [4].O próximo critério foi o compilador. Nos meus projetos, costumo usar "escritos por estudantes para cookies GCC (c) anonymus". Essa. se você descrever sua arquitetura, precisará criar todo o GCC (compilador, vinculador, etc.).Como sou uma pessoa preguiçosa, estava procurando a menor arquitetura possível com suporte ao GCC. E tornou-se o MSP430.Pequena descrição
MSP430 é uma arquitetura muito simples. Possui apenas 27 instruções [5] e quase qualquer endereçamento.A construção da máquina virtual começou com o contexto do processador. O contexto do processador nos sistemas operacionais é uma estrutura que descreve completamente o estado do processador. E o estado desse processador virtual é descrito através do seguinte:- Equipe atual
- Registros
- Registros opcionais de estado de interrupção
- Conteúdo opcional de RAM e ROM
Os registros do MSP430 são 16. Dos 16 registros, os 4 primeiros são usados como registros do sistema. Digamos, um registro nulo é responsável pelo ponteiro atual do comando que está sendo executado no espaço de endereço (contador de comandos).Você pode ler mais sobre registros no guia do usuário original msp430x1xxx [6]. Além dos registros, há também o conteúdo do espaço de endereço - RAM, ROM. Porém, como é fácil manter a “Máquina Host” (a máquina que executa o código da máquina virtual) na memória da máquina virtual, frequentemente, não faz sentido - o retorno de chamada é usado.Esta solução permite executar programas “totalmente canhotos” em processadores com arquitetura Harvard (leia AVR [7] [8]), retirando o programa de fontes externas (por exemplo, memória i2c ou cartão SD).Também no contexto do processador está uma descrição dos registros de interrupção (SFRs). O sistema de interrupção MSP430 é descrito com mais precisão na [6], seção 2.2.Mas na máquina virtual descrita, me afastei um pouco da original. No processador original, os sinalizadores de interrupção estão nos registros periféricos. Nesse caso, as interrupções são descritas nos registros SFR.A periferia do processador é descrita da mesma maneira, por meio de retorno de chamada, que permite criar seus próprios periféricos à vontade.O próximo item do processador é o multiplexador de comando. O multiplexador de comando executa uma função separada. O multiplexador seleciona o próprio comando na palavra de comando, endereçando a fonte e o destinatário, e executa a ação do comando selecionado.Funções separadas descrevem o endereço de origem (SRC) e o receptor.Como usá-lo
Na pasta de exemplos do repositório do projeto [9], existem exemplos para os seguintes processadores:- STM8 para o compilador IAR
- STM8 para compilador SDCC
- STM32 para o compilador Keil armcc
- AVR para compilador GCC
No arquivo Cpu.h, o processador está configurado.Descrição das configurações abaixo:- RAM_USE_CALLBACKS - Indica se é necessário usar chamadas (retornos de chamada) em vez de matrizes individuais no contexto do processador. Se deseja usar chamadas para trabalhar com RAM (chamadas cpu.ram_read, cpu.ram_write)
- ROM_USE_CALLBACKS - Se deseja usar chamadas para trabalhar com ROM (chamada cpu.rom_read)
- IO_USE_CALLBACKS - Se deseja usar chamadas para trabalhar com a periferia (chamadas cpu.io_read, cpu.io_write), se 0, as funções para trabalhar com a periferia devem ser descritas na função msp430_io do arquivo cpu.c
- RAM_SIZE - tamanho da RAM (RAM), o endereço final é automaticamente recalculado com base nesse parâmetro
- ROM_SIZE - Tamanho da ROM (ROM), o endereço inicial é recalculado automaticamente com base nesse parâmetro
- IRQ_USE - Indica se serão utilizadas interrupções; se 1, as interrupções são ativadas
- HOST_ENDIANESS - Indica a ordem dos bytes do controlador host (o controlador que executa a máquina virtual). As arquiteturas AVR, X86, STM32 são little-endian, STM8 são big-endian
- DEBUG_ON - indica se a depuração será usada. A depuração é feita via fprintf - stderr
O uso da biblioteca começa conectando cpu.c e cpu.h ao projeto.#include "cpu.h"
A seguir, é apresentado o contexto do processador. Dependendo do uso dos parâmetros * _USE_CALLBACKS, o código da declaração de contexto será alterado.para todas as declarações de contexto do processador * _USE_CALLBACKS = 1 terão a seguinte aparência:msp430_context_t cpu_context =
{
.ram_read_cb = ram_read,
.ram_write_cb = ram_write,
.rom_read_cb = rom_read,
.io_read_cb = io_read,
.io_write_cb = io_write
};
Onde as variáveis * _cb aceitam ponteiros de função (veja exemplos).Por outro lado, para * _USE_CALLBACKS = 0, as declarações terão a seguinte aparência:msp430_context_t cpu_context =
{
.rom = { },
};
A seguir, é apresentada a inicialização do contexto através da função:msp430_init(&cpu_context)
E executando uma instrução de cada vez através de uma função:while(1)
msp430_cpu(&cpu_context)
Os retornos de chamada para trabalhar com espaço de endereço são assim:uint16_t io_read(uint16_t address);
void io_write(uint16_t address,uint16_t data);
uint8_t ram_read(uint16_t address);
void ram_write(uint16_t address,uint8_t data);
uint8_t rom_read(uint16_t address);
Os endereços para E / S são transmitidos em relação ao espaço de endereço 0 (ou seja, se o programa da máquina virtual acessar P1IN, atribuído ao endereço 0x20, o endereço 0x20 será passado para a função).Pelo contrário, os endereços para RAM e ROM são transmitidos em relação aos pontos de partida (por exemplo, ao acessar o endereço 0xfc06 e iniciar a ROM em 0xfc00, o endereço 0x0006 será passado para a função. Ou seja, o endereço é de 0 a RAM_SIZE, 0 - ROM_SIZE)Isso permite o uso de memória externa , por exemplo, I2C (que já diminui a velocidade do processador).Como completar
Completamente o projeto não foi concluído. Funciona, o firmware de teste funciona com um estrondo. Mas a maioria dos compiladores praticamente não usa comandos específicos diferentes (por exemplo, Dadd é a adição decimal da fonte e do receptor (com hifenização)). Portanto, não há necessidade de falar sobre 100% de compatibilidade com processadores reais.Naturalmente, existem cerca de duas dúzias de operações da máquina host por um comando de máquina virtual; portanto, não faz sentido falar sobre nenhuma característica de velocidade.As fontes do projeto e uma descrição mais extensa estão disponíveis em bitbucket.org [9].Eu ficaria feliz se este projeto é útil para alguém.[1] dmitry.gr/index.php?r=05.Projects&proj=12.%20uJ%20-%20a%20micro%20JVM[2] www.harbaum.org/till/nanovm/index.shtml[3]www.eluaproject.net[4] code.google.com/p/picoc[5] en.wikipedia.org/wiki/MSP430[6] www.ti.com/lit/ug/slau049f/slau049f.pdf[7] en.wikipedia.org/wiki/%D0%93%D0%B0%D1%80%D0%B2%D0%B0%D1%80%D0%B4%D1%81%D0%BA%D0%B0%D1 % 8F_% D0% B0% D1% 80% D1% 85% D0% B8% D1% 82% D0% B5% D0% BA% D1% 82% D1% 83% D1% 80% D0% B0[8] pt .wikipedia.org / wiki / AVR[9] bitbucket.org/intl/msp430_vm