The first generation 2200 machines used a large number of ROMs to contain the microcoded Wang BASIC interpreter. The interpreter implemented the core BASIC language, and added a number of commands for peripherals that Wang knew would be commonly used, such as the disk commands, card readers, cassette drives, and plotters. Even providing for the popular, existing peripherals wasn't enough. Wang had plans for supporting many more, many of which hadn't even been dreamed up by the time the 2200 CPU was out the door.
Most other machines can simply install a driver for a new peripheral; the driver is typically written in assembly language and runs at high privilege. Such concepts are alien to the 2200. There is no assembly language, as the BASIC interpreter is written in microcode. Even if Wang was willing to write drivers for new peripherals in microcode, there was no practical way to support the wide variety of drivers that would be needed via replacing the mask ROM chips in the machines out in the field. Wang needed some solution to support new peripherals that would allow flexibility with decent performance.
Wang's solution was what I call "synthetic microcode". The $GIO command defined a small instruction set of 16b instructions that were interpreted in the true CPU microcode. The user's application would load up a string array with the synthetic microcode command words and the issue a $GIO command to perform that "microcode".
The $GIO instruction set contains a wide variety of instructions to select a device, send or receive single or multiple bytes, wait until a peripheral was ready, wait a certain time delay, etc. The commands to send/receive a string of bytes allowed data transfer at the full microcode rate, which was on the order of 50 KB/sec.
These $GIO commands sufficed to allow controlling telecommunication devices, 9-track tape drives, and lab instrument controllers. However the $GIO instruction set does not model a general purpose processor. It was intentionally quite limited in many ways (relaxed somewhat for Wang BASIC-2), such as the inability to directly reference memory or to have nested loops. This was done to prevent user programs from corrupting the state of the BASIC interpreter or causing other mischief.
Here is an example from the manual. It sends two header bytes to the device at address /03B, then writes the entire contents of array B$(), then sends an LRC byte, and finally receives a status response byte from the device. The status is saved in string R$.
100 $GIO WRITE /03B (6C01 4400 A206 8601, R$) B$()
This breaks down as
|$GIO||General-purpose I/O command|
|WRITE||just a descriptive comment (and is optional)|
|/03B||select device 03B|
|6C01||Single character output sequence
"WR, OBS/IMM, W5, CPB, IBS, VERIFY, TERM"
|4400||Single character output sequence
|A206||High speed multicharacter output
"(WR, DATAOUT/OBS), REPEAT, LEND"
For each character in the array,
After sending all the bytes,
|8601||Single character input
"CPB, IBS, SAVE"
|R$||a string holding the working "registers" for the command|
|B$()||a data array to be operated upon|
BASIC-2 added some additional command words beyond what was in the $GIO command set of the first generation Wang BASIC.
The Apple II BASIC interpreter had a similar scheme although it was done for different reasons.
Steve Wozniak defined a 16 bit CPU instruction set that was simple and efficient to interpret in 6502 machine language. He called it SWEET16. Wozniak's purpose (other than showing off a cool idea) was to allow writing programs that were more dense than pure assembly language by dropping into SWEET16 mode when it was advantageous, such as when a lot of address pointer manipulation had to be done.