Article 1046 of alt.sys.perq: Newsgroups: alt.sys.perq Path: news.umbc.edu!eff!news.kei.com!news.mathworks.com!uhog.mit.edu!bloom-beacon.mit.edu!spool.mu.edu!howland.reston.ans.net!news.sprintlink.net!pipex!warwick!bsmail!siva.bris.ac.uk!ard From: ard@siva.bris.ac.uk (PDP11 Hacker .....) Subject: Introduction to Microcode Message-ID: <14NOV199416532252@siva.bris.ac.uk> News-Software: VAX/VMS VNEWS 1.41 Sender: usenet@info.bris.ac.uk (Usenet news owner) Nntp-Posting-Host: siva.bris.ac.uk Organization: University of Bristol Physics Department Date: Mon, 14 Nov 1994 15:53:00 GMT Lines: 410 Here it is.... Introduction to PERQ microcode ------------------------------ Introduction ------------ Unlike most computers, the machine code instruction set of a PERQ (1,2,4) is not fixed, but is defined by program, written in microcode, loaded from the boot disk. This program interprets the machine code (e.g. Q-code for POS) instructions and activates the correct parts of the PERQ hardware. The following analogy may make this a little clearer : Consider a 1980's home computer - a Spectrum, C64, whatever. It was normal to program those machines in interpretted basic, something like this : User Basic Program --> Basic Interpretter --> Machine code --> Hardware The Basic interpretter reads one intruction at a time from the user program, and executes the correct machine code instructions. Now, on a PERQ, we move down a level User (Q-code) Program --> Q-code Interpetter --> Microcode --> Hardware The machine code instructions (Q-codes) are interpretted one at a time by the Q-code interpretter, which then executes the correct microinstructions (=microcode instructions). In fact the analogy goes a little further - just as most extended Basics allowed access to machine code through PEEK, POKE, and CALL instructions, the Q-code instruction set has opcodes to load the control store and to jump to a control store location. There's no reason why these instructions had to be provided - it would have been possible to implement useable instruction set (similar to Q-code) without them, just as it's possible to have a Basic without access to machine code. But, fortunately the instructions do exist, and thus microcode subroutines can be written (rather than, say, having to redefine an existing instruction). Why write microcode at all -------------------------- There seem to be 4 main reasons why it would be useful to write PERQ microcode routines : 1) Speed. Microcode is the the most fundamental language available on a PERQ, and it is free from the overheads of any higher-level language. Significant speed advantages may be gained by writing time-critical sections in microcode. 2) Bug-fixes. The Q-code interpretter is another program, and thus may contain bugs (although none have been found yet). These bugs can be fixed by modifying the microcode. 3) Emulation. The PERQ hardware is remarkably general purpose and makes few assumptions about the machine code instruction set. It is possible to write an interpretter (in microcode) for almost any other instruction set, should it be desired to do so 4) Access to hardware. The only way to access a general Input/Output port on a PERQ is from the microcode. Therefore the only way to operate a new (or nonstandard) hardware device is by writting a microprogram. Of course the standard hackish reason "because it's there" can be added to the above. Getting started --------------- Firstly a disclaimer : Bugs in microcode are like bugs in machine code, only worse!. A programming error may modify one of the existing machine code instructions and thus cause _all_ other programs to operate incorrectly, leading to disk corruption, or worse. There are no known problems with the examples below, but I am not responsible for loss of data. Please keep a backup of _everything_. These examples will only work under POS (they were tried under POS G.6), but should operate on any PERQ (I used a T1). Do not try them under Accent or PNX - they'll almost certainly cause damage to data. You'll need the PERQ pascal compiler, linker, microcode assembler (PrqMic.Run) and placer (PrqPlace.Run) to try out the examples. This document is intended to suplement, rather than replace, the microprogramming manual included with POS, which is a little difficult to understand in places, so it is useful to have this manual available while trying out the examples. OK, on with the examples. Here's the first microprogram : !test1.micro !check if we can return a value to a pascal program define(r330,330); place(7000,7777); loc(7000), r330:=2,next; tos:=r330,push,next; nextinst(0); end; Lines starting with ! are simply comments so I don't forget what this is for. The first active line "define..." sets up 'r330' as a symbolic name for register 330. Note that by default, all numbers are read in Octal. The "place..." line defines the addresses in the control store that the program can use. An attempt to store an instruction outside that range will cause an assembler error. The program itself now begins. Microinstructions consist of several 'Phrases' that are separated by commas, while the instructions themselves end in semicolons. The line breaks (and other white space) are ignored. What this means is that the phrase 'LOC...,' really belongs to the first instruction (r330...;) rather than being an instruction of it's own. The 'LOC...' phrase defines the control store location where the instruction will be loaded, here 7000 octal. The first instruction loads 2 into R330, and then continues with the next instruction of the microprogram. The ',next' phrase is not required - the microcode assembler assumes that the lines are to be executed sequentially unless explicit jumps are used, but for some odd reason it does not place these instructions in successive locations of the control store - it scatters them and then patches the jump fields to cause correct execution. The next instruction copies R330 onto the top of the expression stack (a 16 level integer stack in the CPU), pushes the stack, and continues with the next instruction. Finally, the 'nextinst(0)' instruction is executed. This causes a jump back to the standard Q-code interpretter (actually it causes the hardware to perform a multi-way jump based on the next Q-code in the pipeline), and is the standard way to end a microcode subroutine. So, type in the program using the normal POS editor (or extract it from the document and kermit it to the PERQ), and call it test1.micro Assemble and place it by typing : prqmic test1 prqplace test 1 to produce test1.bin - the actual binary microprogram that corresponds to the above code. To execute the program, it must be loaded into the control store and called. This is done by the following Pascal program. program test1(input,output); imports controlstore from controlstore; var tst:microfile; x:integer; begin; reset(tst,'test1.bin'); loadcontrolstore(tst); close(tst); x:=0; writeln('x=',x); writeln('calling microcode'); jumpcontrolstore(#7000); storexpr(x); writeln('x=',x); end. The controlstore file includes definitions of 'microfile', 'loadcontrolstore' and 'jumpcontrolstore' and is used by almost all programs that call microcode. To read in the microcode binary file, it is first openned for reading (the 'Reset...' line) and then the 'loadcontrolstore' routine copies into the CPU control store. The file is then closed. The variable 'x' is set to 0, displayed, and a message that the microcode is about to be called printed. The 'jumpcontrolstore' instruction causes microcode at location 7000 octal to be executed. The microcode, as explained above, leaves 2 on the CPU expression stack, and this is the transfered to the variable 'x' by the 'storexpr' instruction. The new value of x (hopefully 2) is then displayed, and the program ends. Save that file on the PERQ using the editor (or kermit), and compile and link it in the usual way : Pascal test1 link test1 Now execute test1.run test1 The output should be : x= 0 calling microcode x= 2 Things to try ------------- Obviously the value of the constant (2) in the microcode can be changed. Try some other values, noting that they are interpretted as octal numbers by the assembler. Also, try deleting the ',next' phrase from the microcode instructions. The program will still assemble and execute correctly, but won't be stored at sequential addresses. An alternative way to call the microcode ---------------------------------------- The 'jumpcontrolstore' instruction has the disadvantage that parameters cannot be passed to the microcode by pushing them onto the expression stack. To get round this it is necessary to use an inline Q-code. This method works in all cases, and the next pascal program shows how it's done program test1a(input,output); imports controlstore from controlstore; var tst:microfile; x:integer; begin; reset(tst,'test1.bin'); loadcontrolstore(tst); close(tst); x:=0; writeln('x=',x); writeln('calling microcode'); loadexpr(lor(shift(#7000,8),shift(#7000,-8))); inlinebyte(#277); {JCS instruction} storexpr(x); writeln('x=',x); end. Apart from the method of calling the microcode, this program is identical to test1.pas above. The change is that the 'jumpcontrolstore' instruction has been replaced by a pair of lines. The first one 'loadexpr...' saves the start address of the microprogram on the expression stack. The 'lor...' expression reverses the high and low bytes of this address - for some reason the JCS instruction requires that. The 'inlinebyte...' instruction places a JCS (Jump Control Store) Q-code into the program. The octal value for this opcode is 277. So, again transfer this program to the PERQ as test1a.pas, compile and run it. It should behave just like test1.pas above. Passing values to the microcode ------------------------------- So far, the microprogram has passed values back to the pascal program that called it, but has not accepted values from the pascal program. The easiest way to perform the latter transfer is to use the expression stack. Consider the following microprogram !test2.micro !check if we can get and return a value to a pascal program define(r330,330); place(7000,7777); loc(7000), r330:=tos,pop,next; r330:=r330+r330,next; tos:=r330,push,next; nextinst(0); end; The first instruction of the program (at location 7000 octal) transfers the top of the expression stack into register 330, pops the stack and continues with the next instruction. The next instruction effectivly doubles R330, by adding it to itself, and the following instruction saves the new value of R330 onto the expression stack. The program ends by executing nextinst(0) in the standard way. Transfer the above programs to test2.micro on the PERQ and assemble and place it. A suitable pascal driver for this microcode is given below program test2(input,output); imports controlstore from controlstore; var tst:microfile; x:integer; begin; reset(tst,'test2.bin'); loadcontrolstore(tst); close(tst); x:=4; writeln('x=',x); writeln('calling microcode'); loadexpr(x); loadexpr(lor(shift(#7000,8),shift(#7000,-8))); inlinebyte(#277); {JCS instruction} storexpr(x); writeln('x=',x); end. This program is similar to the ones given earlier. Firstly it loads test2.bin (the assembled microcode) into the control store so that it can be executed. It then sets x to 4, and displays it, along with a suitable message. The value of x is then pushed onto the expression stack (the 'loadexpr(x)' line), and the microcode called as in test1a.pas. The new value on top of the expression stack is transferred into x ('storexpr(x)') and then displayed. This program should be stored as test2.pas on the PERQ. Then compile and link it in the usual way, and execute it. The output should be : x= 4 calling microcode x= 8 Things to try ------------- Change the value of x in the pascal program, or let it be entered at run-time. Try replacing the second 'r330' in the addition microinstruction with a constant, like 3. The microcode should then add a constant value to x. Also try replacing the addition operator with one of the other ALU operators, like AND (see the POS microprogramming manual for details). A User Output Port ------------------ So far, everything that's been done in microcode could have been done in PERQ Pascal. This next example, however, _requires_ a microcode program - there is simply no way to do it in pure pascal. The programs give the PERQ a 16 bit parallel output port - a user output port, and require a PERQ fitted with an OIO board in the I/O option slot. The output data lines of the PERQlink are used for the output port. Connect some buffered LED's (e.g. using 74LS04 inverters) to the data lines. The connections are : Pin 39 = Data 0, Pin 37 = Data 1, etc. Ground can be obtained from any of the even-numbered pins. These LEDs will display the state of the data lines. Here's the microcode that's needed to write to the port : !userout.micro !Send value to user output port (=perqlink out) define(r330,330); place(7000,7777); loc(7000), r330:=tos,pop,next; ! Get value from TOS r330,iob(243),next; ! Write to Port 243 (User Out) nextinst(0); end; Most of the instructions should be familiar by now. The program starts at location 7000 (octal) by popping the value from the expression stack. The next line places this value on the outputs of the ALU (the r330 phrase), and then sends the value to port 243 (octal) (the iob(243) phrase). The program then ends in the standard manner. There is some ambiguity as to the correct names of the ports on a PERQ. The IO address bus is active low, and ports with the top bit high should be outputs. However, the PERQ microassembler seems to only invert the bottom 7 bits of this 8-bit address, and so port 243 is the accepted name for the PERQlink output port. Transfer that program to the PERQ and save it as userout.micro. Assemble and place it in the usual way. The next program is a trivial pascal driver for the microcode. It prompts the user for a value to send to the port, sends it, and repeats. The value '-1' is not sent to the port, but acts as a signal to quit the program. Here's the source listing. program userout(input,output); imports controlstore from controlstore; var microcode:microfile; x:integer; begin; reset(microcode,'userout.bin'); loadcontrolstore(microcode); close(microcode); repeat; write('Enter value to send to port'); readln(x); if x<>-1 then begin; writeln('calling microcode'); loadexpr(x); loadexpr(lor(shift(#7000,8),shift(#7000,-8))); inlinebyte(#277); {JCS instruction} end; until x=-1; end. This program is easy to understand, and the microcode is called in the same way as previous examples. Therefore no explanation will be given as to its operation. Save this file as userout.pas, and compile and link it. Run it, and type in suitable values. The binary form of the number should appear on the LEDs connected to the output port. Some devices need active-low data, particularly if they've been designed for operation with DEC DR-11 ports. That's very easy to fix - just change the microcode to send the inverted data to the port. This is done by changing userout.micro to the following : !userout.micro !Send value to user output port (=perqlink out) define(r330,330); place(7000,7777); loc(7000), r330:=tos,pop,next; ! Get value from TOS not r330,iob(243),next; ! Write to Port 243 (User Out) nextinst(0); end; The only change is that the phrase 'not r330' replaces 'r330' in the output instruction. This inverts the data as required. Note that this modification requires no extra instructions and takes no more clock cycles - an advantage to doing the inversion in microcode rather than in the pascal program. -tony Bristol University takes no responsibility for the views expressed in this posting. They are the personal views of the user concerned.