⬆️ ⬇️

We write to nobody the necessary emulator

Good day.



For a long time there was a desire to write an emulator of some kind of processor.

And what could be better than inventing a bicycle?



The name of the bike - V16, from gluing the word Virtual and, in fact, bit.





Where to begin?



And you need to start, of course, with a description of the processor.



At the very beginning, I planned to write the DCPU-16 emulator, but there are more than enough such wonders on the Internet, so I decided to stop only at "licking" the most basic from DCPU-16 1.1.



Architecture



Memory and Ports





Registers



V16 has two sets of general-purpose registers: main and alternative.

The processor can work with only one set, so you can switch between sets using the XCR instruction.



Instructions



All instructions have a maximum length of three words and are fully defined first.

The first word is divided into three values: low byte - opcode, high byte in the form of two 4-bit values ​​- the description of the operands.



Interruptions



Interrupts here are nothing more than a table with addresses to which the processor duplicates the CALL instruction. If the value of the address is zero, then the interrupt does nothing, just reset the HF flag.



Value rangeDescription
0x0 ... 0x3Register as value
0x4 ... 0x7Register as value by address
0x8 ... 0xBRegister + constant as value at
0xCConstant as value at
0xDConstant as value
0xERegister RIP as read-only value
0xFRegister RSP as value


An example of pseudocode and words in which all this should be translated:



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


Cycles (Tacts)



V16 can execute one instruction in 1, 2 or 3 cycles. Each access to RAM is one separate cycle. The instruction is not a beat!



Let's start writing!



The implementation of the main structures of the processor



  1. A set of registers. There are only four registers, but the situation is improved by the fact that there are two such sets in the processor. Switching takes place using the XCR instruction.



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


  2. Flags. Unlike DCPU-16, V16 has conditional jumps, subroutine calls and returns from there. At the moment, the processor has 8 flags, 5 of which are condition flags.



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


  3. Actually, the processor itself. It also describes the table of interrupt addresses, which can be called descriptors and find another reference to 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. Operand. When receiving values, we need to first read, then change, and then write the value to where we got it from.



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




Functions for working with structures



When all structures are described, the need arises for functions that will give these structures the magic power of the extinguished code.



 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 *); // ,    


Also, I did not mention a large listing with opcodes, but this is not necessary and necessary only to understand what is happening in this whole mess.



Tick ​​() function



There are also calls to static functions that are only intended to be called from 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; } 


What to do next?



In trying to find the answer to this question, I rewrote the emulator from C to C ++ five times and back.



However, the main goals can be identified now:





Conclusion



I hope that someone this "article" will be useful, someone pushes to write something like that.



My kulichiki can be viewed on github .



Also, oh, horror, I have an assembler for the old version of this emulator (No, don't even try, the emulator will at least complain about the wrong ROM format)



')

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



All Articles