PERQ ROT13 Microcode

One of the nicest things about the PERQ is that it's soft-microcodable; users can write speed-critical pieces of code in the machine's native language, augment the existing instruction set, or even replace it altogether. This is a little bit of microcode I wrote in response to a challenge on ucam.chat (or was it oxbridge.tat? I don't remember now...) to come up with the shortest assembly language routine for ROT13 encoding a text string. Of course, on a PERQ this is a single instruction:

	HEX	OPCODE		COMMENTS
	13	ROT13		; ROT13 encode the quadword aligned
				; ASCIIZ string pointed to by the accumulator
Implementing the rest of the instruction set is left as an exercise for the reader :-)

A word of warning; I haven't tested this code... The original certainly has bugs in it: ARD12 made some changes and corrections.

Those who don't know any PERQ microcode might want to read ARD12's Introduction to Microcoding, parts one, two and three.

The Original Microcode

! rot13.mic  -- microcode to implement a ROT13 instruction
! Probably doesn't work -- I wasn't able to test it!

Define(Acc,100);	! set up some symbolic names for registers
Define(D1,101);		! Acc is the actual bytecode accumulator
Define(D2,102);		! On entry, Acc == ptr to quadword-aligned
Define(D3,103);		! 0-terminated string for ROT13 encoding.
Define(D4,104);
Define(D5,105);
Define(D6,106);
Define(D7,107);

Loc(114);               ! right placement for Rot13 instruction

! Enter here from NextInst (hardware jump-on-next-bytecode)
D2:=0;                  ! flag for end of string
! Loop back to here each quadword
Lp: MA:=Acc, Fetch4R;	! get quadword from memory in reverse order...
             		! 2 tstates here if we need them
TOS:=MDI, Push;		! here comes the data from that Fetch4
TOS:=MDI, Push;		! We now have 64bits of the string on the stack
TOS:=MDI, Push;		! Note that putting data on the stack and
TOS:=MDI, Push;		! adjusting the stack pointer are separate ops :->

D1:=TOS, Call(r13it);	! note the way we do the Pop after the Call
D3:=D1, Pop;		! to save a t-state each instruction
D1:=TOS, Call(r13it);
D4:=D1, Pop;
D1:=TOS, Call(r13it);
D5:=D1, Pop;
D1:=TOS, Call(r13it);
D6:=D1, Pop;

MA:=Acc, Store4;	! now we do the Store of this quadword -- t3
D3; 		        ! t0
D4;                     ! t1
D5;                     ! t2
D6;                     ! t3

D2;
if Neq Goto(Lp);        ! if we didn't hit the terminator, get next quadword
NextInst;               ! get next bytecode

! call r13it with a word in D1. Returns with D1=Rot13'd equivalent.
! If D2=1, no action. Sets D2 if it hits a zero byte. 
r13it: D2;
if Neq Return;          ! do nothing if D2==1
TOS:=D1, Push;          ! save for second byte          
RightShift(8);
D1:=Shift, Call(r13b);  ! get high byte, rot13 it
D2; 
if Neq Return;          ! was that end of string?
D7:=TOS and 377, Pop;   ! now low byte
LeftShift(8);
D1;
TOS:=Shift, Push;	! push ROT13d high byte
D1:=D7, Call(r13b);
D1:=D1 or TOS, Pop;	! put both bytes back together
Return;

! r13b: Rot13 a byte in D1. If byte == 0, set D2=1
r13b: D1;
if Eql Goto(l1);        ! if D1=0, set flag and quit
D2:=1,Return;
l1: D1-'A';
if Lss Return;          ! byte below A -- ignore
D1-'M';
if Gtr Goto(l2);        
l4: D1:=D1+15,Return;       ! octal constant!
l2: D1-'Z';
if Gtr Goto(l3);
l5: D1:=D1-15, Return;
l3: D1-'a';
if Lss Return;          ! non-alpha chars in the middle
D1-'m';
if Leq Goto(l4);
D1-'z';
if Leq Goto(l5);
Return;

ARD12's Corrections:

(I've edited the original posting slightly...)

From: ard12@eng.cam.ac.uk (A.R. Duell)
Newsgroups: alt.sys.perq
Subject: Re: Silly microcode...
Date: 6 May 1997 17:02:15 GMT
Organization: University of Cambridge, England
Message-ID: <5kno6n$rvb@lyra.csx.cam.ac.uk>
References: <5knk36$2im@mnementh.trin.cam.ac.uk>

pm215@cam.ac.uk (Peter Maydell) writes:
>Since this is my first microcode program, I thought it deserved
>a wider audience. I'd also like to know if I made any errors...

I have a few comments....

>---------begin file rot13.mic----------
>! rot13.mic  -- microcode to implement a ROT13 instruction
>! Probably doesn't work -- I wasn't able to test it!

Yes, you can. Put it somewhere sane, and use the JCS instruction to run
it. There are _very_ few changes needed to do that.

>Define(Acc,100);        ! set up some symbolic names for registers
>Define(D1,101);         ! Acc is the actual bytecode accumulator
>Define(D2,102);         ! On entry, Acc == ptr to quadword-aligned
>Define(D3,103);         ! 0-terminated string for ROT13 encoding.
>Define(D4,104);
>Define(D5,105);
>Define(D6,106);
>Define(D7,107);

I'd have used more meaningful names...


>Loc(114);               ! right placement for Rot13 instruction

>! Enter here from NextInst (hardware jump-on-next-bytecode)
>! Loop back to here each quadword
>Lp: MA:=Acc, Fetch4R;   ! get quadword from memory in reverse order...
>!set flag for end of string.
>D2:=0

Is one 'dummy' instruction correct here? I don't have the PERQ memory info
in front of me, alas...
                  
>TOS:=MDI, Push;         ! here comes the data from that Fetch4
>TOS:=MDI, Push;         ! We now have 64bits of the string on the stack
>TOS:=MDI, Push;         ! Note that putting data on the stack and
>TOS:=MDI, Push;         ! adjusting the stack pointer are separate ops :->

>D1:=TOS, Call(r13it);   ! note the way we do the Pop after the Call
>D3:=D1, Pop;            ! to save a t-state each time
>D1:=TOS, Call(r13it);
>D4:=D1, Pop;
>D1:=TOS, Call(r13it);
>D5:=D1, Pop;
>D1:=TOS, Call(r13it);
>D6:=D1, Pop;

You can save some instructions here. Firstly, move the D1:=TOS phrase into
the r13it routines. Then you can write : 

Call(r13it);
D3:=D1,Call(r13it); ! The assignment will happen before the call...
D4:=D1,Call(r13it);
D5:=D1,Call(r13it); ! and the last word is now in D1



>MA:=Acc, Store4;        ! now we do the Store of this quadword -- t3
>D3;                     ! t0
>D4;                     ! t1
>D5;                     ! t2
>D6;                     ! t3
Replace that last line with :
 D1;                     ! t3 Remember that the last routine left the
                         ! value in D1


> 
>D2;
>if Neq Goto(Lp);        ! if we didn't hit the terminator, get next quadword
>NextInst;               ! get next bytecode

You mean NextInst(0) here, I think.

> 
>! call r13it with a word in D1. Returns with D1=Rot13'd equivalent.
>! If D2=1, no action. Sets D2 if it hits a zero byte.
>r13it: D2;
>if Neq Return;          ! do nothing if D2==1
>TOS:=D1, Push;          ! save for second byte

It would be better as (remember, we've removed the pops earlier): 
D1:=TOS; ! Read next byte off the stack. Leave the SP
         ! unchangerd. Remember that the D1:=TOS's have been removed
 
>RightShift(8);

No! You can't do this. This instruction _will_ change 'R', so the shift in
the next instruction will read the wrong value. A better way would be to 
set up the shifter to do a Rotate(8) at the start of the program, and then 
to use that to swap the bytes in the word. 

>D1:=Shift, Call(r13b);  ! get high byte, rot13 it
>D2;
>if Neq Return;          ! was that end of string?
>D7:=TOS and 377, Pop;   ! now low byte
>LeftShift(8);
>D1;
>TOS:=Shift, Push;       ! push ROT13d high byte
>D1:=D7, Call(r13b);
>D1:=D1 or TOS, Pop;     ! put both bytes back together
>Return;

Something like : 
r13it:D2, Rotate(8); ! Set up shifter to do a byteswap
if Neq Return; ! If D2 <>0 then do nothing
TOS;           ! Copy next word to R lines
D1:=shift and 377, call(r13b); ! Read high byte into D1, rot13 it
D7:=D1
D1:=TOS and 377,pop,call(r13b); !rot13 it low byte 
D7; 
D1:=shift or D1; ! put the word back together
return;

>! r13b: Rot13 a byte in D1. If byte == 0, set D2=1
>r13b: D1;

We now need to change this to : 
D2; 
D1, if Neq return;

>if Eql Goto(l1);        ! if D1=0, set flag and quit

Surely that should be Neq, shouldn't it? You want to go (i.e. carry on) if
D1 is non-zero.

>D2:=1,Return;
>l1: D1-'A';
>if Lss Return;          ! byte below A -- ignore
>D1-'M';
>if Gtr Goto(l2);
>l4: D1:=D1+15,Return;       ! octal constant!
>l2: D1-'Z';
>if Gtr Goto(l3);
>l5: D1:=D1-15, Return;
>l3: D1-'a';
>if Lss Return;          ! non-alpha chars in the middle
>D1-'m';
>if Leq Goto(l4);
>D1-'z';
>if Leq Goto(l5);
>Return;

I'm sure there's a neater way to do that, but I'll have to think about
it...

>--------end file-------
--
-tony
ard12@eng.cam.ac.uk
The gates in my computer are AND,OR and NOT, not Bill

This page written by Peter Maydell (pmaydell@chiark.greenend.org.uk).