Nós escrevemos um emulador que não é necessário para ninguém

Bom dia


Há muito tempo, havia um desejo de escrever um emulador de algum processador.
E o que poderia ser melhor do que inventar uma bicicleta?


O nome da bicicleta é V16, colando a palavra Virtual e, de fato, profundidade de bits.



Por onde começar?


E você precisa começar, é claro, com uma descrição do processador.


No começo, planejei escrever um emulador de DCPU-16, mas existem muitos milagres na Internet, então decidi me concentrar apenas em "lamber" o mais básico do DCPU-16 1.1.


Arquitetura


Memória e portas


  • A V16 endereça 128 KB (65536 palavras) de RAM, que também podem ser usados ​​como buffers de dispositivo e a pilha.
  • A pilha começa com o endereço FFFF, portanto, o RSP possui um valor padrão de 0xFFFF
  • As portas de E / S V16 têm 256, todas com 16 bits. A leitura e escrita a partir deles é realizada através das instruções IN b, a AND OUT b, a .

Registros


O V16 possui dois conjuntos de registros de uso geral: primário e alternativo.
Um processador pode funcionar com apenas um conjunto, para que você possa alternar entre os conjuntos usando a instrução XCR .


Instruções


Todas as instruções têm um comprimento máximo de três palavras e são totalmente definidas primeiro
A primeira palavra é dividida em três valores: o byte baixo é o código de operação, o byte alto na forma de dois valores de 4 bits é a descrição dos operandos.


Interrupções


As interrupções aqui nada mais são do que uma tabela com endereços para os quais o processador duplica a instrução CALL . Se o valor do endereço for zero, a interrupção não faz nada, simplesmente redefine o sinalizador HF.


Intervalo de valorDescrição do produto
0x0 ... 0x3Caso como valor
0x4 ... 0x7Registre-se como um valor em
0x8 ... 0xBRegistrar + constante como valor no endereço
0xCConstante como um valor em
0xDConstante como valor
0xERegistro RIP como valor somente leitura
0xFRegistro RSP como valor

Um exemplo de pseudocódigo e palavras nas quais tudo isso deve ser traduzido:


 MOV RAX, 0xABCD ; 350D ABCD MOV [RAX], 0x1234 ; 354D 1234 

Ciclos


O V16 pode executar uma instrução em 1, 2 ou 3 compassos. Cada acesso à memória é um ciclo de relógio separado. Instrução não é tato!


Vamos começar a escrever!


Implementação de estruturas básicas de processador


  1. Um conjunto de registros. Existem apenas quatro registros, mas a situação melhora, pois existem dois desses conjuntos no processador. A troca ocorre usando a instrução XCR .


     typedef struct Regs { uint16_t rax, rbx; //Primary Accumulator, Base Register uint16_t rcx, rdx; //Counter Register, Data Register } regs_t; 

  2. Bandeiras Ao contrário do DCPU-16, o V16 possui saltos condicionais, chamadas de sub-rotina e retorna do mesmo. No momento, o processador possui 8 sinalizadores, 5 dos quais são sinalizadores de condição.


     //  ,    stdbool.h typedef struct Flags { bool IF, IR, HF; bool CF, ZF; bool EF, GF, LF; } flags_t; 

  3. Na verdade, o próprio processador. Também descreve a tabela de endereços de interrupção, que pode ser chamada de descritores e encontra outra referência ao x86.


     typedef struct CPU { //CPU Values uint16_t ram[V16_RAMSIZE]; //Random Access Memory uint16_t iop[V16_IOPSIZE]; //Input-Output Ports uint16_t idt[V16_IDTSIZE]; //Interrupt vectors table (Interrupt Description Table) flags_t flags; //Flags regs_t reg_m, reg_a; //Main and Alt register files regs_t * reg_current; //Current register file uint16_t rip, rsp, rex; //Internal Registers: Instruction Pointer, Stack Pointer, EXtended Accumulator //Emulator values bool reg_swapped; //Is current register file alt bool running; //Is cpu running uint32_t cycles; //RAM access counter } cpu_t; 

  4. Operando. Ao obter os valores, precisamos primeiro ler, depois alterar e, em seguida, escrever o valor de volta para onde conseguimos.


     typedef struct Opd { uint8_t code : 4; uint16_t value; uint16_t nextw; } opd_t; 


Funções para trabalhar com estruturas


Quando todas as estruturas são descritas, surge a necessidade de funções que dotarão essas estruturas com o poder mágico do código extinto.


 cpu_t * cpu_create(void); //   void cpu_delete(cpu_t *); //   void cpu_load(cpu_t *, const char *); // ROM   void cpu_rswap(cpu_t *); //   uint16_t cpu_nextw(cpu_t *); //RAM[RIP++]. Nuff said void cpu_getop(cpu_t *, opd_t *, uint8_t); //  void cpu_setop(cpu_t *, opd_t *, uint16_t); //  void cpu_tick(cpu_t *); //   void cpu_loop(cpu_t *); // ,    

Além disso, não mencionei uma grande enumeração com códigos de operação, mas isso não é necessário e é necessário apenas para entender o que está acontecendo em toda essa bagunça.


Função Tick ()


Além disso, há chamadas para funções estáticas destinadas apenas a chamadas de tick() .


 void cpu_tick(cpu_t *cpu) { //    HLT,      if(cpu->flags.HF) { //      ,      if(!cpu->flags.IF) { cpu->running = false; } return; } //       uint16_t nw = cpu_nextw(cpu); uint8_t op = ((nw >> 8) & 0xFF); uint8_t ob = ((nw >> 4) & 0x0F); uint8_t oa = ((nw >> 0) & 0x0F); //     //   opd_t opdB = { 0 }; opd_t opdA = { 0 }; //    cpu_getop(cpu, &opdB, ob); cpu_getop(cpu, &opdA, oa); //        -  uint16_t B = opdB.value; uint16_t A = opdA.value; uint32_t R = 0xFFFFFFFF; //    bool clearf = true; //       ? //   ! switch(op) { //     . ,   ,    R } //   if(clearf) { cpu->flags.EF = false; cpu->flags.GF = false; cpu->flags.LF = false; } //  ,  32-   16-  //  0xFFFF0000,   0xFFFF << 16 //        32-  if(R != 0xFFFFFFFF) { cpu_setop(cpu, &opdB, (R & 0xFFFF)); cpu->rex = ((R >> 16) & 0xFFFF); cpu->flags.CF = (cpu->rex != 0); cpu->flags.ZF = (R == 0); } return; } 

O que fazer depois?


Na tentativa de encontrar a resposta para essa pergunta, reescrevi o emulador cinco vezes de C para C ++ e vice-versa.


No entanto, os principais objetivos podem ser identificados agora:


  • Prenda interrupções normais (em vez de apenas chamar uma função e proibir o recebimento de outras interrupções, faça uma chamada de função e adicione novas interrupções à fila).
  • Parafusos de dispositivos, bem como maneiras de se comunicar com eles, o benefício dos códigos de operação pode ser de 256.
  • Ensinar Não escreva qualquer heresia em Habr O processador opera a uma velocidade de clock específica de 200 MHz.

Conclusão


Espero que este "artigo" seja útil para alguém, alguém os solicite a escrever algo semelhante.


Minhas tortas podem ser visualizadas no github .


Além disso, sobre o horror, eu tenho o assembler para a versão antiga deste emulador (não, nem tente, o emulador pelo menos reclamará do formato errado da ROM)

Source: https://habr.com/ru/post/pt457144/


All Articles