Have a data structure for CPU registers.
Have a data structure for memory.
Have a data structure for i/o.
Read a series of instructions that are being emulated.
Create a series of rules as to how those instructions interact with CPU, memory, i/o, in a mutable fashion. A long if/then/elif/elif/elif/elif/else type loop, or switch case type loop, if you will.
Create bridges between particular pieces of memory and system output: video, sound, controllers.
That's most of it in the classic case at least.
Now you have an emulator.
The "big league" emulators work differently. They may be simple compilers. Instead of compiling C code or java code, they compile machine code from system A to system B. A transpiler. This affords speed beyond the simple case. Speed, however, is only necessary if the machine running the emulator is weak, or if the machine being emulated is strong.
The "big league" emulators work differently. They may be simple compilers. Instead of compiling C code or java code, they compile machine code from system A to system B. A transpiler. This affords speed beyond the simple case. Speed, however, is only necessary if the machine running the emulator is weak, or if the machine being emulated is strong.