This is another quick post about an unfinished (but working) software which I wrote around 8 years ago. It is an Intel 8085 microprocessor simulator, with a text based interface. The objective was to simulate the microprocessor, along with a minimal interface which closely resembles the microprocessor kit with the 7-segment displays, hex keyboard and minimal debugging features.

Quick story

In our undergraduate computer science degree, we had a few subjects on microprocessor architecture. One of the subjects focused on the Intel 8085 microprocessor architecture in great details, Intel 8086 architecture, interfacing, etc. Along with the detailed architecture, we also had to do some assembly code for 8085. It was fun because, we had to use a physical 8085 microprocessor kit with a hex keyboard and just those 7-segment displays.

8085 Microprocessor trainer kit

To write a code in the kit, you need to scan through the memory and enter the values of the assembled code. Who assembles it? We had to do that manually. We would have a table of all the instructions and the hex value for the op-code. More essentially it is very important to know the precise operations for each instruction. What operation it performs, which registers are accessed, what memory locations are accessed and how does it change the flags.

It gets more interesting (sometimes painful) when you first write your code in assembly in a white sheet of physical paper, then you refer the table and convert the assembly code to machine code, basically an entire page of hex values. Next, you get to your 8085 microprocessor kit, start from a memory location and keep on typing these hex values like a maniac. There were 8085 microprocessor kits which had some “debugging” facility, but essentially, if something goes wrong, it was extremely difficult to find, given our skills.

Although we were required to use the physical 8085 microprocessor kit in the exams, but for practice, we used 8085 microprocessor simulators. There are quite a few 8085 microprocessor simulators available. One of them was provided with one of the text books. There were simulators with text interface, some with text and some with nice GUI interfaces. I used one of them, the GNUSim8085. You can find an in depth review of GNUSim8085, which I wrote for OpenSource For You long ago, also posted here: Reviewing the GNUSim8085 (v1.3.7).

I personally did not like most of the simulators or the interfaces. All of them involved a lot of mouse-clicking, which slows you down a lot and does not reflect the actual 8085 trainer kit experience. There were a few which had full keyboard control, but somehow we felt that they were cumbersome. The Intel 8085 trainer kit experience was highly required inorder to timely and correctly finish the tasks given in the examination. The good thing was, I (and a few of my friends) knew exactly what I was looking for. Therefore I tried to make one … (drum roll)Dirty8085.

The product

First let me demonstrate “the product”. This is how it looks. Compared to the present age (even when this was written), it is kind of old and looks scary, but bear with me, as the keyboard shortcuts will have the 8085 coding blazing fast.

The right hand side shows the flags and the register states and the present start address of the simulator, very useful for step execution and debugging. The left hand side shown the kind of 7-segment display which you will get in a basic simulator kit.

[ mem | val]
[420c | 13]

The left hand part of this indicates the memory address 0x420c and the right part shown the contents of the memory 0x13.

To execute a code, first enter your assembled code, then set the start address by pressing s and then press x to execute. The previous image shows a step execution scenario (press z). In this case the simulator disassembles the machine code and displays the presently being executed code and the next one.

Press F1 to know the keyboard shortcuts.

The main features in my opinion are:

  • Clean and clutter free interface
  • Intuitive keyboard key bindings, and full keyboard control
  • Dependency on only ncurses for the interface, therefore can run in command line mode
  • Step execute, step over features. Can save load machine state

Bubble sorting code demo

In the test machine state file dirty_test, I wrote and assembled a bubble sort code in 8085 and loaded it into 0x4200 location. The input list for the list to be sorted is in 0x0000. The first number in the list indicates the length of the list, then the actual numbers to be sorted follows. The bubble sort code sorts the list at 0x0000o and stores the result list in 0x07d0.

To run this,

  1. start dirty
  2. Press F9 to load the file
  3. Type “dirty_test” without the quotes, when prompted (or provide the full path if not in present working directory)
  4. Optionally press g and enter 0000 or 07d0 or 4200 to see the contents of the memory locations
  5. Press s and type 4200 and press enter to set the starting address. Note that the “start address” value will change
  6. Press x to start execution
  7. Press g and enter 07d0 to go to the output location to check if the list was sorted or not

Also, try pressing z to start step execution. To stop step execution press q. To exit the simulator, press ESC and then press y to exit.

I do not have this 8085 code with me presently, as I wrote it long ago.

Go through the README file, as it has a description and I also have uploaded a file with three demo codes which you can run immediately. Play with the features if you want, and if you are able to break anything, please comment.

I would recommend this book: Microprocessor Architecture, Programming, and Applications with the 8085 to learn the Intel 8085 end to end.

Dirty8085: Source Code

As I was in the middle of the year, and I could not allow much time to be invested on this, I gave myself a good 14 days to get the first version running. When I finished this, I thought that although it works as planned, but as it was not complete, therefore I thought to post this in my blog once I implement the TODOs for the second iteration (which never happened). After a few years I put that in github.

You can get the code in github: https://github.com/phoxis/dirty8085

  • 8085.h: Structures and public functions defined
  • 8085_main.c: All the simulator code goes here
  • curses_ui.c: The ncurses UI, the dirty component

Download it as zip or clone repository. Execute make, run from present directory ./dirty or do make install as root. Note you need to have ncurses-devel, else build will fail. For Fedora/RedHat/OpenSuSe/CentOS do a dnf install ncurses-devel.

Next, I will go through the main highlights of the code.

Instruction set structure

The basic idea is to make a lookup table based on the op-code number, which will hold all necessary information to execute the instruction.

struct _instruction_set {
u8int_t (*ins)(void); // Function pointer to the corresponding instruction
char ins_str[10]; // The instruction string
char operand_1[10]; // First operand
char operand_2[10]; // Second operand
u8int_t mem_opnd; // the upper nibble represents the no of mem operations of 1st opnd, the lower nibble tells the no of mem operands in 2nd operand
// u8int_t bytes; //the instruction length //TODO: initilize the array with this info
};

static struct _instruction_set instruction_set[0x100] =
{
/* store the type of opnd , ie the addressing mode also, to help the assembler */
/* function , asm_str , opnd1, opnd2, upper_nibble=1st operand size, lower_nibble=2nd operand size */
{nop      , "nop"  , ""  , ""  , 0x00}, //0x00
{_lxi_b   , "lxi"  , "b" , " " , 0x02}, //0x01
{stax_b   , "stax" , "b" , ""  , 0x00}, //0x02
{inx_b    , "inx"  , "b" , ""  , 0x00}, //0x03
{inr_b    , "inr"  , "b" , ""  , 0x00}, //0x04
{dcr_b    , "dcr"  , "b" , ""  , 0x00}, //0x05
{_mvi_b   , "mvi"  , "b" , " " , 0x01}, //0x06
{rlc      , "rlc"  , ""  , ""  , 0x00}, //0x07
{_invalid , ""     , "x" , "x" , 0xff}, //0x08
{dad_b    , "dad"  , "b" , ""  , 0x00}, //0x09
{ldax_b   , "ldax" , "b" , ""  , 0x00}, //0x0a
{dcx_b    , "dcx"  , "b" , ""  , 0x00}, //0x0b
{inr_c    , "inr"  , "c" , ""  , 0x00}, //0x0c
{dcr_c    , "dcr"  , "c" , ""  , 0x00}, //0x0d
{_mvi_c   , "mvi"  , "c" , " " , 0x01}, //0x0e
{rrc      , "rrc"  , ""  , ""  , 0x00}, //0x0f
{_invalid , ""     , "x" , "x" , 0xff}, //0x10
{_lxi_d   , "lxi"  , "d" , " " , 0x02}, //0x11
{stax_d   , "stax" , "d" , ""  , 0x00}, //0x12
{inx_d    , "inx"  , "d" , ""  , 0x00}, //0x13
{inr_d    , "inr"  , "d" , ""  , 0x00}, //0x14
{dcr_d    , "dcr"  , "d" , ""  , 0x00}, //0x15
{_mvi_d   , "mvi"  , "d" , " " , 0x01}, //0x16
{ral      , "ral"  , ""  , ""  , 0x00}, //0x17
{_invalid , ""     , "x" , "x" , 0xff}, //0x18
{dad_d    , "dad"  , "d" , ""  , 0x00}, //0x19
{ldax_d   , "ldax" , "d" , ""  , 0x00}, //0x1a
{dcx_d    , "dcx"  , "d" , ""  , 0x00}, //0x1b
{inr_e    , "inr"  , "e" , ""  , 0x00}, //0x1c
{dcr_e    , "dcr"  , "e" , ""  , 0x00}, //0x1d
{_mvi_e   , "mvi"  , "e" , " " , 0x01}, //0x1e
{rar      , "rar"  , ""  , ""  , 0x00}, //0x1f
{rim      , "rim"  , ""  , ""  , 0x00}, //0x20
.
.
.
};

The struct _instruction_set holds all the information related to the instructions. Each entry for the array of structures struct _instruction_set instruction_set indicates the description of an instruction, which is needed. The way struct _instruction_set instruction_set was initialised is key. The description of an instruction is ordered in the array of struct _instruction_set instruction_set in the order of their op-code values. This will enable us to use the op-code to directly fetch the related information about the instruction.

The first field ins holds the function pointer for instruction. The second field ins_str the instruction string, this is used to display the instruction being executed. The third and forth fields are the operand_1 and operand_2. If an operand is not present, this will be an empty string. In the case if the operand is an immediate number, then this is set as a blank space. The last field mem_opnd is essentially a combined field. The upper nibble of this byte indicates the number of memory operations required to access the first operand, where as the lower nibble of this byte indicates the number of memory operations required to access the second operand. This is kind of unnecessary, but this was useful as in our course, it was sometimes required to compute and write the exact number of memory accesses.

For example {_mvi_e , "mvi" , "e" , " " , 0x01}, //0x1e is indexed in the 0x1e location, which is the op-code for MVI E. The first field has the function pointer _mvi_e, which simulates the move immediate to register E instruction. The second field shows the name of the instruction mvi. The first operand is the register e, therefore requires 0 memory operations, thus the lower nibble of the mem_opnd is 0. The second operand is kept a blank space, indicating there will be an immediate numeric operand, and in this case the number of memory operations would require one clock cycle.

CPU State

Next is the CPU state. It has three sections. Sets of registers, including the flags register. The system memory array, 64K long, which is the max addressable memory for the Intel 8085. The last section is to store some additional things to help the simulator, like storing the last location where the program counter was (will help to implement step back), and holding a pointer to the instruction set structure to hold which instruction is running, so we can use the structure to print on screen what is going on.

typedef struct __8085_machine_t {
  /* Registers */
 u16int_t a;
 u8int_t flag;
 u8int_t b;
 u8int_t c;
 u8int_t d;
 u8int_t e;
 u8int_t h;
 u8int_t l;
 u16int_t sp;
 u16int_t pc;

 /* Memory */
 u8int_t memory[MAX_MEM];

 /* Last pc val */
 u16int_t prev_pc;

 /* Current states */
 struct _instruction_set *current_instruction;
} _8085_machine_t;

Now we need to implement the functions which simulates the instructions. This took a significantly long time to write correctly by referring to the manual and then unit testing each function to make absolutely sure they work. This is because, I found quite a number of well used simulator had bugs in the DAA instruction.

// 0x81
u8int_t add_c (void)
{
 tm->a = (tm->a & 0xff) + tm->c;

 tm_update_flags (UPDATE_SIGN, UPDATE_ZERO, UPDATE_AUXCARRY, UPDATE_PARITY, UPDATE_CARRY);
 return 0;
}

This is an example of an instruction, ADD C. This performs A = A + C, and then updates the flags. Initially I implemented the flag updates inside each function, then I realised in a case of bug or change, it will be extremely difficult to maintain it, therefore made a centralised tm_update_flags function which is responsible to perform all the updates.

One interesting thing is, not all operators has the same number of memory operands. Updating the program counter is done by the main engine which performs the execution. But for the program counter modification like in jump instructions the instruction functions are is still responsible to perform change. This could have been a bit more centralised, but this is what I decided that time instead of over-engineering the thing. Possibly putting the number of bytes the instruction reads in the instruction description struct would have been a better idea.

The main engine

Once these things are done, there is one main function loop which performs the instruction fetch cycle and the instruction execute. This code, which ties everything is pretty simple and shown below.

u8int_t execute (u16int_t start_addr)
{
 u8int_t retval;

 tm_set_start_address (start_addr);

 do {
 tm_fetch_instruction ();
 retval = tm_execute_instruction ();

 } while ((retval != EXECUTE_END) && (retval != EXECUTE_ERR));

 tm->current_instruction = NULL;
 return retval;
}

This just keeps on fetching the next instruction and execute it until the code is not finished yet. One must place HLT at the end to terminate the code, which returns the EXECUTE_END and stops the loop.

The rest of the code which does the fetch and execute works on an internal state struct _8085_machine_t *tm. This is an object which represents the machine state entirely and on which all the instruction will work on. This state machine can directly be stored disk. A critical point to be noted is that I should have serialised the structure while storing. The UI was made in the last moment (visible from the code :) ). As the structure padding is implementation defined, if you save the machine state in an executable in one compiler, and load from an executable compiled in another compiler, the padding can change and create problems loading the file. I have tested in gcc (GCC) 7.1.1 20170622 (Red Hat 7.1.1-3). The tm_fetch_instruction reads the next instruction from the memory and uses it as an index in the instruction set structure to take out the corresponding row. Then it loads operands depending on the nature of the instruction, adjusts the program counter. It also sets the tm->current_instruction to the instruction is read. After this, the simulator is ready to perform the execution. This is done with the tm_execute_instruction function. These two are called in a loop in the above execute function.

static struct _instruction_set *tm_fetch_instruction (void)
{
 u16int_t program_counter;
 u8int_t opcode;
 u16int_t mem_operand_low, mem_operand_hi;
 u8int_t mem_operand_size;

 program_counter = tm->pc;
 opcode = tm->memory[program_counter++];
 tm->current_instruction = &instruction_set[opcode];

 /* First operand number of bytes in upper nibble */
 mem_operand_size = (instruction_set[opcode].mem_opnd & 0xf0) >> 4;
 if (mem_operand_size == 1)
 {
  mem_operand_low = tm_fetch_memory (program_counter);
  program_counter++;
  sprintf (instruction_set[opcode].operand_1, "%02xh", mem_operand_low);
 }
 else if (mem_operand_size == 2)
 {
  mem_operand_low = tm_fetch_memory (program_counter);
  program_counter++;
  mem_operand_hi = tm_fetch_memory (program_counter);
  program_counter++;
  sprintf (instruction_set[opcode].operand_1, "%04xh", make_val_16 (mem_operand_hi, mem_operand_low));
 }

 /* Second operand number of bytes in lower nibble */
 mem_operand_size = (instruction_set[opcode].mem_opnd & 0x0f);
 if (mem_operand_size == 1)
 {
  mem_operand_low = tm_fetch_memory (program_counter);
  program_counter++;
  sprintf (instruction_set[opcode].operand_2, "%02xh", mem_operand_low);
 }
 else if (mem_operand_size == 2)
 {
  mem_operand_low = tm_fetch_memory (program_counter);
  program_counter++;
  mem_operand_hi = tm_fetch_memory (program_counter);
  program_counter++;
  sprintf (instruction_set[opcode].operand_2, "%04xh", make_val_16 (mem_operand_hi, mem_operand_low));
 }

 // printf ("\n\npc = %x , memory[%x] = %x, ins = %s\n\n", pc-1, pc-1, memory[pc-1], tm->current_instruction->ins_str);
 return tm->current_instruction;
}

/* Execute the current opcode */
static u8int_t tm_execute_instruction (void)
{
 if (tm->current_instruction != NULL)
 {
  /* Increment pc to 1 for the instruciton. The memory operand increment updates are still the responsibility
   * of the instruction emulating function, when it uses the fetch_operand function. Unfortunately when step
   * executing this will twice fetch the memory operands, one in the fetch instruction and another during when
   * the emulation funciton is executed
   */
   tm->prev_pc = tm->pc++;
   // tm->pc = tm->pc + (tm->current_instruction->mem_opnd & 0x0f) + ((tm->current_instruction->mem_opnd >> 4) & 0x0f);
   return tm->current_instruction->ins ();
 }
 else
   return 0;
}

For the step execute and step over, the same principle is followed with the necessary changes.

User interface

I don’t know UI programming very well. At that time I was into a bit Qt3 and trying to move towards Qt4, but for this one I wanted to keep it classic. Therefore I selected the curses UI for terminal, with plenty of keyboard shortcuts. For example, to save a file use F6 and load use F9! how cool is that!

If you see the 8085.h file, it defines the interface using which one can hook the simulator to any UI. To test the interface, I made a very quick UI. At that point of time, I was pretty tired and wrote and tested th UI in 2 days. Both the code and the UI was dirty, therefore the name.

One good feature was, if your 8085 code has an infinite loop, unlike the other simulators, you will be able to stop this one by pressing Ctrl + C, as this saves a jump buffer and on a SIGINT jumps out of the loop and returns the control to the UI again. Don’t bother about the setjmp, it should definitely be sigsetjmp. I was yet to learn a lot of stuffs :).

Future work

After 14 days, I stopped, and I was myself surprised that this actually went pretty smoothly, possibly because I strictly stuck to the plan, worked slowly but steadily (and put a huge amount of time). I did most of the practice in 8085 on this simulator, and found a few bugs. A few friends helped me by using this and spotting out a few bugs as well.

In this simulator you have to manually assemble and enter the code as hex, exactly like in an actual 8085 kit. That was one of the main goals, not to have an editor with the user writing assembly code, but to simulate the 8085 kit. After this I thought to write an assembler, and also have an optional module, which assembles a code and loads in the memory. So I started to write an assembler. It worked fine (not in this repository), but didn’t work in some situations (had no idea about compiler design at that time). Therefore I took an assembler from another opensource assembler project. Although, I did not continue this as the need was fulfilled, although I wanted to continue this project, but couldn’t because of time.

Next works could be to

  • Implement IO instructions
  • Implement the 8085 undocumented instructions
  • Major code cleanup and finish all the TODOs
  • Add an assembler
  • Maybe have a web interface. (I can go to CGI at most)

Let me know your views and ideas in the comments.

References

Advertisements

One thought on “Dirty8085: An Intel 8085 Microprocessor simulator

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s