-1

I've been working on a 24-bit virtual machine to help me learn more about computers and programming in general and was hoping to find a bit more information on memory mapped input/output and hardware interrupts.

At the moment I have most of the opcodes implemented and about have of them implementated in an assembler. At the moment I can load a 'rom' and the machine will load it into 'memory' and run through the instructions until it reaches an $FF in memory then it ends and dumps the register states to a terminal.

At the time I started this, I was interested in learning to program the snes but it seemed frustrating and somewhat unrewarding so I took what I understood about how the snes worked and tried to simplify it and make it easier (and more fun) to program.

The CPU has 16 16-bit registers that are byte or word addressable and one special 8-bit flag register modifiable through special op codes. The first 8 are general purpose registers. The upper 8 are special registers that have specific functions. It has 24-bit memory addressing using either the upper or lower byte from one register as an offset register and the word from another as the 16-bit address. Memory is separated into 256 pages of 65535 bytes each allowing 16mb of addressable memory. Instructions are read one word at a time and each instruction will move the instruction pointer to where it needs to be at the end of the instruction.

My overall goal is to allow some basic graphic output and input handling and maybe eventually simulated block devices for storage. My plan is to use BearLibTerminal for input/output. Its a fairly small, SDL, based pseudoterminal that allows characters and graphical tiles to be treated the same way. Its also a runtime library that can be configured through an external text file so the hassle of dealing with graphics stored in memory is avoided.

The part I'm not sure about is how to send input from the 'terminal' to the machine and output from the machine to the 'terminal'. I was thinking of using an area of memory to map to some basic terminal functions. Setting an address would open a terminal another would clear and refresh it. I was going to use a page or two as screen buffers that can be drawn to the screen. How they're drawn would depend on the current mode of the terminal.

Input would be mapped into a different section of memory. I was thinking of using a few bytes to store raw key presses and using an interrupt to copy them from there to a different area of memory before the next instruction is carried out.

The part I'm not sure about is exactly how interrupts work and how I can have my processor jump in the middle of an instruction to deal with an interrupt. I'm also not sure if this is even how I should go about doing this. If anyone has any experience in this and could share some advice it'd be very much appreciated. I've read through both the nes and snesdev wikis and the docs for the 6502 and 65816 processors several times and studied the memory maps for both machines. I'm programming my machine in D so I also took a look at the source for this project: https://code.dlang.org/packages/gbaid and it helped but I'm still sort of lost. Thanks in advance for any help.

  • 1
    You will need to find some example code for the processor you are thinking of. Interrupt handlers are not easy to do, and what you can do, and how to do it, is architecture specific. Here is some information on interrupt handlers for those processors, but you need samples: en.wikipedia.org/wiki/Interrupts_in_65xx_processors – Frank Hileman Oct 2 '17 at 22:17
  • 1
    If you are trying to learn, you should just take a stab at implementing interrupts then see what works and what doesn't work, then look at how other platforms do it. – whatsisname Oct 2 '17 at 22:24
  • Additionally all questions asking to "point in a direction where I can learn more" are off-topic. – whatsisname Oct 2 '17 at 22:25
  • Thank you. Sorry about the question format. I removed the part about pointing in a direction. I think the part I'm having trouble understanding is which part of the interrupt is handled by the cpu and which parts would be implemented by the running program. For example a keypress would trigger an interrupt signal telling the processor to handle the key press. How much of what happens next is dependent on the running program vs something built in to the cpu? Or is this something else that varies by architecture? – Grawprog Oct 3 '17 at 1:26
4

The part I'm not sure about is exactly how interrupts work and how I can have my processor jump in the middle of an instruction to deal with an interrupt.

The basic idea you already have is that the processor performs this jump. Modern processors will usually change the permission level but this is/was not always the case, and I'll leave out that additional complexity. I'll try to keep it minimal, and of course, there is lots of room for variation in how this might work.

So, an interrupt happens during the execution of regular (user) code, and steps go something like this:

  1. the processor automatically transfers control to prearranged address. (Sometimes there are different addresses for different interrupts, on other processors, there's only one address for all interrupts.)

    • transferring control in this manner will essentially alter some cpu state. (How much depends on your instruction set, etc..) However, because we don't want any of the interrupted code's state wiped out due to an interrupt, the processor must offer some hardware support to preserving any such interrupt-altered state.

    • common examples of state that are changed when the interrupt is serviced are: the pc, of course, and a processor status word which holds privilege level and interrupt enabled bit. The pc is of course, changed to the interrupt service address, and most processors will disable interrupts (and/or enable privilege) in their processor status word (because the very first part of the interrupt service routine is usually not re-entrant). Thus, a processor needs to provide a way to access the pre-interrupt values of such items. Some processors will provide special registers with special opcodes to retrieve those items (these registers will often not be members of the general register file for user code).

  2. save context for the interrupted code so that it can be resumed later.

    • to get further, the code at the interrupt service address must take certain actions. First, some of the normal cpu registers are saved, for example, to some hard-coded absolute memory locations. That then frees up a few cpu registers so that some limited and specialized code can run. Its job is to identify the proper location to save the cpu context for the interrupted code.

    • in a typical operating system, the currently running thread has a data structure associated with it, and this data structure will have room for all the CPU registers, which amounts to the state needed to restore/resume the interrupted thread. A job of the interrupt service routine will be to identify that thread's data structure, and move all the cpu state to that data structure. This means copying the pre-interrupt pc value, as well as other pre-interrupt status (like privilege level), along with the few cpu registers saved to the absolute addresses, and all the other cpu (register) state.

    • further, the interrupt service routine will load the cpu registers will relevant context for executing the rest of the isr: context like a stack pointer, for example, maybe a global data pointer if used on the processor.

    • Now the processor can execute somewhat more normal code (but not totally normal); at least it has all of the cpu registers available.

  3. the next critical part of an interrupt service routine is to capture the data from the interrupting device before it disappears. The isr, thru processor-provided special instructions will inquire interrupt flags and etc.. associated with the external device that interrupted. This will yield a device number or some such. That will inform the isr the proper interrupt handler to run. The proper handler will fetch any relevant data and acknowledge the receipt of it to the device. The data is either buffered immediately, or, a lower priority routine is scheduled for handling the data at a later time.

  4. now it is important to re-enable interrupt servicing before data from another interrupting device is lost. re-enabling the interrupts may result in another interrupt right away. if not then the operating system scheduler should be invoked to run whatever routine is now top-priority (and ready to run, of course).

    • if the interrupt was a clock interrupt, it may be that the cpu time-slice for a given user thread is done, and it is time to give control to another thread.

    • if the interrupt service routine deferred some operation for later, that later operation will likely have higher priority than the interrupted user code, and so will be scheduled next.

    • if none of those are the case, then perhaps the interrupted code is simply resumed.

Essentially, there is a choreography that must occur thru the cooperation of hardware and software then taking up any slack for functions that the hardware doesn't offer, while also servicing interrupts within time periods, and with limited operations available during the highest priority parts of the interrupt servicing. Memory allocation, for example, is generally considered a bad idea until you're in a deferred handler (that can run with interrupts turned on). So, the interrupt handling code is usually written with levels of capabilities where the lower levels offer good response timing and the higher levels offer longer term buffer (and device) management.

The complexity varies greatly among cpus. For example, an x86 processor is in a long-lived family of processors, and in order to maintain some abstraction from the OS, these processors do more of the work of saving their own internal cpu state than just the PC and the interrupt enable bit (as might have been done in the old days). That enables these processors to evolve somewhat without immediately requiring an OS update.


I had mentioned that the processor saves the interrupt enable state but that was just a bit misleading. Obviously, if the processor has just been interrupted, the interrupt enable was on/true. However, usually this bit is in the processor status word, and other things in that word change (e.g. privilege levels) to allow the isr to run properly, and this whole word is what may be saved off by the hardware to capture some of the pre-interrupt execution state that is altered when jumping to the isr.

  • Thank you this is the kind of information I was looking for. I appreciate you taking the time to write this up. This helps me understand the separation better. I should be able to figure out how I can implement them to fit with my processor. I think I'm going to need to change the main cycle loop and plan some of the memory map first it seems like. – Grawprog Oct 4 '17 at 18:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.