Close

Recreating a simple EMZ1001A assembler

A project log for Iskra EMZ1001A - a virtual resurrection

4-bits wonder to print "Hello World!" and to calculate Fibonacci numbers to 13 decimal digits!

zpekiczpekic 12/12/2022 at 05:400 Comments

Any processor - no matter how simple or complex - is of little use if it cannot execute some program. I could not find any listing or ROM code for any EMZ1001A application, the closest was this note about using EMZ1001A as a DMTF frequency signal decoder. In addition, executing a program is the only way to test the instructions, so I decided to write 2 "apps" to validate the processor and demo it.

Next problem was lack of development toolchain. AMI documentation describes a sophisticated development system that includes hardware and software - with a macro-assembler. Even if this could be found today, it would be not practical to integrate with PC-based FPGA toolchain, so only option was to write own. 

One approach would be to leverage already existing tools, for example a universal cross-assembler.  Then I realized, I already have written one myself - my micro-code compiler

How does a 2-pass microcode compiler become a 2-pass assembler? Not easily, except for lucky coincidence that EMZ1001A uses only 8-bit op-codes, which is exactly the same as horizontal microcode, where each microinstruction has exactly the same width (usually 20 bits +), and the number of microinstruction formats is very limited (often times, only 1 format, meaning each field in microinstruction has same meaning in each microinstruction).

Microcode compiler allows defining multiple fields in the microinstruction (from emz.mcc include file):

// define any slices of the instruction word format
f76			.valfield 2 values * default 0;
f54			.valfield 2 values * default 0;
f32			.valfield 2 values * default 0;
f10			.valfield 2 values * default 0;

With these 2-bit fields, EMZ1001A instructions formats can be expressed as: 

FormatLayout

o - opcode bit

U - operand bit that must appear inverted in code

X - operant bit that must appear not-inverted in code

Sample instructions.mcc definition
8+0ooooooooNOP...DISNopr8 .valfield f76 .. f10 values * default 0;
6+2ooooooUUSZM, STM, RSM, LB*opr6 .valfield f76 .. f32 values * default 0;
val2 .valfield f10 .. f10 values * default 0;
6+2 invertedooooooUUXC*, LAMopr6 .valfield f76 .. f32 values * default 0;
val2 .valfield f10 .. f10 values * default 0;
4+4ooooXXXXLAI, ADISopr4 .valfield f76 .. f54 values * default 0;
val4 .valfield f32 .. f10 values * default 0;
4+4 invertedooooUUUUPPopr4 .valfield f76 .. f54 values * default 0;
val4 .valfield f32 .. f10 values * default 0;
2+6ooXXXXXXJMP, JMSopr2 .valfield f76 .. f76 values * default 0;
val6 .valfield f54 .. f10 values * default 0;

To differentiate between inverting and non-inverting formats, simply the "macro" includes the ! symbol which will appear before the operand and result in inverted bits in the binary:

// 2-bit operand, inverted (0x30 .. 0x3F)
XCI		.alias opr6 = 0b001100, val2 = 3 & !;
XCD		.alias opr6 = 0b001101, val2 = 3 & !;
XC		.alias opr6 = 0b001110, val2 = 3 & !;
LAM		.alias opr6 = 0b001111, val2 = 3 & !;

// 2-bit operand, not inverted (0x40 .. 0x4F)
LBZ		.alias opr6 = 0b010000, val2 =;
LBF		.alias opr6 = 0b010001, val2 =;
LBE		.alias opr6 = 0b010010, val2 =;
LBEP		.alias opr6 = 0b010011, val2 =;

With this, it is possible to easily define each EMZ1001A instruction and write assembly code similar to the original (main difference is that each line must end in ; and that the comment character is different //)

In summary, I had to make 3 changes to existing microcode-compiler:

With a working assembler, the toolchain becomes simple:

-- 1k of internal ROM contains the "Hello world!" program
firmware: rom1k generic map(
	    filename => "..\prog\helloworld_code.hex",
	    default_value => X"00" -- NOP
	)	
	port map(
	    D => introm,
	    A => pc,
	    nOE => '0'
	);

Note: Xilinx (now AMD) as Intel competitor of course does not support natively loading .hex files during build time, but a simple file reader included in the project package file (see init_filememory() function) I wrote is sufficient. 

Discussions