I'm writing a low-level emulator (in Java) of the IBM 1620, a late-50s computer. By "low level," I mean that the lowest observable unit of work is the 20-microsecond "trigger" -- machine instructions achieve their objectives by running a series of these triggers; and I've written code to emulate almost all of them. By "observable" I mean that the 1620's console contained a "Single Cycle Execution" key which allowed you to manually advance one trigger at a time - and most of the CPU's inner state (registers, PC, conditions) were displayed on a large console of lights. I am guaranteeing correctness of the state at the single-cycle level.
Normally the emulator is either waiting for the operator to press START, or it's in its main run loop, executing trigger after trigger. It also needs to be able to respond to external events like a thrown toggle switch, or the afore-mentioned Single Cycle Execution key, which are conveyed to the CPU Thread from the GUI thread by an event queue. The run loop peeks at this queue before firing off the next trigger, and so far this has worked fine.
Now, however, I'm implementing the card-reader I/O instructions, and I have a problem. All 1620 I/O was synchronous (the 1620 did not have interrupt capability), so when it executed the ReadCard instruction, it would literally wait in mid-trigger until the card reader delivered a card. This normally took a tenth of a second, unless the operator had not mounted the card deck! It's that latter contingency that creates the problem: the 1620 must wait in mid-trigger (i.e. the main run loop is not running), while remaining responsive to external events (thrown switches, the Reset key, or, of course, a card from the card reader).
I can't seem to think of a clean, elegant way to design this. Polling for, and handling, "unrequested" events at the top of the run loop has worked well so far, but now I need a callable mechanism that does the same thing but is able to return control to its caller when the right event occurs. Here's what it looks like right now:
state:
MANUAL means unable to immediately process next trigger
AUTO means we can go ahead and process next trigger
do forever {
processQueuedEvents() // event-processing never blocks
if (state == MANUAL) {
waitForEvents() // this blocks until queue not empty
} else { // state == AUTO
trigger = doTrigger(trigger) // run trigger and get next one
}
}
CPU trigger E30 sends a message to CardReader requesting a card buffer, and must wait to receive the event containing the buffer. In the meantime, the CPU must continue to receive and process events as usual.
I could imagine this to be an operating-system design problem, but that's not my strong suit. Can anyone provide some guidance?