Magic‐1 Microcode Example - retrotruestory/M1DEV GitHub Wiki

Magic-1 Microcode Example

Magic-1 supports 256 instructions using 512 40-bit wide micrcode instructions. The microcode instructions are stored in 5 ROM devices, each holding an 8-bit slice of the 40-bit wide microinstruction. This storage is also known as the “microcode store”.

Magic-1 was originally designed to use 1970’s era bipolar PROMs, specifically the 74S472 512x8 bit device. These devices can only be programmed once, and today are uncommon and need rare vintage programmers to create. Although the one-time-programmable feature is not great, bipolar proms have the advantage of an extremely fast response time.

During Magic-1’s development, though, it was far easier to use a modern EPROM with a fast response time. Note, though, that even using modern EPROMs, only the first 512 bytes are used from each device.

In the early days of Magic-1’s design, I realized it was important to have a single source repository. And, because I used a variety of desktop and laptop computers, I decided to keep the microcode source code on a web site rather than locally on a build system. Thus, the web page describing Magic-1’s instruction set is also the source code used to generate the microcode store images. See: https://homebrewcpu.com/v2_3_microcode.htm

My build process would use “wget” to fetch the microcode instruction web page and then feed it though some tools to strip out the html tags and generate include and .c files that would be used to build the 5 microcode image files.

Magic-1 currently uses all of the possible 256 instructions, and almost all of the microcode space. I believe it would be possible to double the microcode store from 512 to 1024 entries with a minor hardware design change on the control card - but let’s save that discussion for later. In this document, we’ll run through the process of replacing an existing little-used Magic-1 instruction for a new one.

One difficulty in changing instructions is that if you change an instruction that is currently in use by Magic-1, you either have to make sure that the instruction is not currently being used by any existing Magic-1 software, or you might have to completely recompile and rebuild the system.

I earlier did a experiment to determine the frequency of existing instruction use. See:

https://docs.google.com/spreadsheets/d/1lzhDZCHd7SZC8Hp_lJb8UgNcHKj3_-eKjMtHEiw2zhI/edit?usp=sharing

There are several candidates, but for this example let’s remove the existing instruction “vshr.16 B”, opcode 0xef. This instruction does a right shift of register B a variable number of bit positions depending on the count stored in register C. We already have a variant which operates on register A, so we won’t miss this instruction. Now, as far as what to replace it with, for this example let’s go with a register exchange instruction. When doing assembly programming for Magic-1, I have often wanted to exchange the values held in registers A and B. However, doing so now takes multiple instructions and unless you use a clever logic trick, requires the use of a temporary register or memory location.

We’ll call the new instruction:

EXCH	A,B	; B -> A, A -> B

Note that this instruction is similar to a register copy, so we can look at the existing register copy microcode to get an idea of how to make it work. Looking at the microcode instruction web page:

0xf2 	copy A,C 	; 	TO_Z(R_C),L(R_A,LWORD),NEXT(Fetch)

This instruction is about as simple as you can get. Refer to the Magic-1 block diagram. The macro “TO_Z(R_C)” moves the value of register C to the Z bus. It does this by enabling the output of register C on the L bus, generating an immediate value of -1 (or 0xFFFF) on the R bus, telling the ALU to “AND” these two values and place the result on the Z bus. Next, we tell the A register to latch the result of the “AND” on the Z bus using “L(R_A,LWORD)” on the next clock edge. Finally, we set the next microcode instruction to be executed as the “Fetch” location, which is 0x100 in the microcode.

For our new instruction, we need to do three register copies. One nice thing about microcode is that you have access to some parts of the system that are not directly accessible using the regular instruction set. For us to exchange registers A and B, we need a temporary storage location. Fortunately, we have a special register, MDR (memory data register), which can be used for our exchange operation. In other words, copy A to MDR, copy B to A, copy MDR to B.

The microcode.htm file is a bit difficult to work with given all of the html tags. So, let’s preprocess it first to strip out the tags, and then create a version of gen_files.pl that skips lynx. Copy gen_files.pl to regen_files.pl and apply the following changes.

buzbee@M1Dev:~/workspace/M1Dev/PromData$ diff gen_files.pl regen_files.pl 
17c17
<    die "Usage: perl gen_files.pl source.htm tgtdir\n";
---
>    die "Usage: perl regen_files.pl source.htm tgtdir\n";
42c42
< print `cp $mc_full tmp123.htm`;
---
> #print `cp $mc_full tmp123.htm`;
45c45,46
< @infile = `lynx -nolist -dump -width 1024 tmp123.htm`;
---
> #@infile = `lynx -nolist -dump -width 1024 tmp123.htm`;
> @infile = `cat $mc_full`;
47c48
< unlink("tmp123.htm");
---
> #unlink("tmp123.htm");

Next, we’ll, strip out the tags of microcode.htm.

lynx -nolist -dump -width 1024 microcode.htm > processed.txt

From here on out, we’ll edit “processed.txt”.

In processed.txt, change the instructions at 0xef from:

0xef vshr.16 B      ; TO_Z(R_B),L(R_MDR,LWORD),NEXT(Vshr)
To:
0xef exch A,B       ; TO_Z(R_A),L(R_MDR,LWORD),NEXT(Exch)
Then, change the instructions 0x1fa and 0x1fb to:

0x1fa Exch           ; TO_Z(R_B),L(R_A,LWORD),NEXT(FALLTHRU)
0x1fb                ; TO_Z(R_MDR),L(R_B,LWORD),NEXT(Fetch)

Now, use the new regen_files.pl as follows:

./regen_files.pl processed.txt Autogen/

This only generates the Autogen files, so continue here with a “make”

make

Once you have this working, you might want to change the makefile to use regen_files.pl rather than gen_files.pl. I also think it’s a good idea to create a new microcode generation program. Back when I did this originally, I used the WSIWYG html editor that came with Microsoft FrontPage 98. It inserted a large number of tags and makes direct editing of the html file awkward.

In any event, in the above, the microcode at 0xef copies A to MDR, at 0x1fa we copy B to A, and at 0x1fb we copy MDR to B and then go to fetch the next instruction.

Finally, burn these new prom0.hex .. prom4.hex files into a new set of EPROMs and install in Magic-1 and boot into Minix.

Now, the assembler and other tools don’t know about our new exch instruction. But, we can still test it to make sure it works.

First create an empty C program such as:

int main() { } Then, get the assembly code for is by:

cc -S empty.c

Which should give you an empty.s file:

Magic1:/usr/home/buzbee # cat empty.s
	; Magic-1 assembly file, generated by NMH's SubC
	.cseg
	.global	_main
_main:
	enter		$$fs1
L1:
	leave
	ret
;	STATS - locals: 0, spill: 0, max_outs: 2
$$spl1	.equ	4
$$lb1	.equ	4
$$fs1	.equ	2
	.end

We’ll now add a few instructions to test our new “exch A,B”:

Magic1:/usr/home/buzbee # cat empty.s
	; Magic-1 assembly file, generated by NMH's SubC
	.cseg
	.global	_main
_main:
	enter		$$fs1
L1:
	ld.16	a,0x1234   ; set up A with a test value
	ld.16	b,0x5678	; set up B with a test value
	.defb 0xef		; The exch a,b opcode value
	br	L1
	leave
	ret
;	STATS - locals: 0, spill: 0, max_outs: 2
$$spl1	.equ	4
$$lb1	.equ	4
$$fs1	.equ	2
	.end

Go ahead and compile this into an executable (make sure empty.s - not empty.c):

cc -o empty empty.s

Now, let’s test it out using adb. We’ll run until _main and then single-step execution and look at register values in between. Use “s” to step and “$” to display the registers. Here’s a log:

Magic2s:/usr/home/magic # adb ./empty
Debugging 52
0x54, 0x52, 0x50
0x0000 cseg_start:
0x0000 | ld.16 a,0(sp)
(adb) b _main
Setting breakpoint 1 at 0x3c
(adb) r
0x003c _main:
0x003c | enter #2
(adb) s
0x003e _main+0x2:
0x003e | ld.16 a,#4660
(adb) s
0x0041 _main+0x5:
0x0041 | ld.16 b,#22136
(adb) s
0x0044 _main+0x8:
0x0044 | vshr.16 b
(adb) $
  A: 0x1234    B: 0x5678    C: 0x0001   DP: 0x0000
 SP: 0xfefe   PC: 0x0044  PSW: 0x00d3  PTB: 0x0014
(adb) s
0x0045 _main+0x9:
0x0045 | sbr L003e
(adb) $
  A: 0x5678    B: 0x1234    C: 0x0001   DP: 0x0000
 SP: 0xfefe   PC: 0x0045  PSW: 0x00d3  PTB: 0x0014
(adb) q
Quitting
Magic2s:/usr/home/magic # 
Note that the values of A and B were swapped - it worked!

Now, we’ve got the new instruction working - but next we need to update the various programming tools to understand “exch A,B”. Updates will need to be made to the assembler, as, the debugger, adb and the disassembler, dis.

dis

Edit workspace/M1Dev/dual_tools/dis/dis.c.  

At appx line 254, comment out:
{“vshr.16 b”, 1},

and insert the following line:
{“exch a,b”, 1},

adb

Edit workspace/M1Dev/root/usr/src/commands/adb/adb.c.

At appx line 441, comment out:
{“vshr b”, 1},

and insert the following line:
{“exch a,b”, 1},

as

The changes for the assembler are slightly more complicated - but still easy.

In opnames.h, duplicate line 45 (the “vshr” line), and then edit the new line 46 to be:
{ “exch , (S_REF | S_TOKEN), 0, NULL, EXCH },

In inst_names.h, comment out line 241 (the “vshr.16 B” line), and add the following:
“exch A,B”,

In m1_parser.y, we need to add a new token for the exch instruction and a parser production.

Add the following token declaration following line 142:
%token  EXCH 241

At about line 401, change:
| VSHR D16 B { emit0(239); }
to:
| EXCH A ‘,’ B { emit0(239); }

Rebuild everything and ftp over dis, as and adb to Magic-1.

Now, you should be able to write “exch a,b” instead of “.defb 0xef”, and adb should display the proper opcode.

...Bill

⚠️ **GitHub.com Fallback** ⚠️