Close

Implementation in VHDL - structure and timing

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:390 Comments

EMZ1001A microcontroller implementation is contained in 2 VHDL source files:

As of 2022-12-20, everything described in Iskra / AMI documentation is implemented, except:

The "detailed block diagram" from the documentation is not extremely helpful with recreating the device, but still gives some useful hints. 

Timing

EMZ1001A has a very rigid timing: all instructions are executed in 1 machine cycle, machine cycle has always 4 clock cycles (T1, T3, T5, T7) and 2 phases (SYNC low - instruction fetch, and SYNC high - instruction decode). Implementation presented here is cycle accurate and follows the real device based on what I could infer from documentation.  2 outside clock sources are consumed, which drive 3 "processes" (VHDL term for defining how registers are updated):

ClockCLK (CPU operating frequency)I3 (assumed to be A/C mains 50Hz or 60Hz)
low to high transitioncapture state of RUN, K, I inputs during cycles T3, T5, T7 respectively 

on_clk_up: process(CLK, nPOR)
count up until limit set by EUR is reached at which point set a flag that can be consumed by SOS to skip

on_i_clk: process(nPOR, i_clk, sos_clr)
high to low transitionAdvance through T1, T3, T5, T7 cycles (using a 1-hot ring counter) and tie almost all of internal register updates based on cycle, run mode, current instruction

on_clk_down: process(CLK, nPOR)
N/A
async (reset condition)Initialize internal registers (except 64 nibble RAM)clear counter on reset, clear flag on reset and SOS execution

Main action is in the on_clk_down: process(CLK, nPOR). RUN and SKIP indicate the states of ir_run and ir_skp flags.

AlwaysRUNRUNNO RUNNO RUN
At the end of:SKIPNO SKIPSKIPNO SKIP
T1Capture ROMS state in the middle of SYNC low----
T3-Load instruction register with NOP (0x00)Load instruction register from ROM(PC)--
T5Capture ROMS state in the middle of SYNC highUpdate skip flag
Increment PC
Execute NOP
Update skip flag
Increment PC
Execute all instructions except JMP, RT, RTS
JMS: increment stack pointer
--
T7--JMP and JMS: update PC based on 6-bits in the instruction and state of PP prepared registers
RT, RTS: decrement stack pointer
--

Some notes: 

-- STATUS is a 4 to 1 mux
STATUS <= (t1 and (not mr_d_driven)) or (t3 and bl_is_13) or (t5 and mr_cy) or (t7 and ir_skp);
-- decide if internal or external ROM is used
with ir_roms select ir_introm <= 
    not(ir_bank(2) or ir_bank(1) or ir_bank(0))     when "00", -- LOW, internal for bank0, external for others 
    '0'                         when "01", -- SYNC, always external
    '1'                         when "10", -- /SYNC, not implemented test mode, assume "internal"
    '1'                         when others; -- HI, always internal

A/C frequency timer

An interesting feature is a simple divide by 60 or divide by 50 timer which allows 1 second intervals to be measured in simplest way. It was probably added for apps where simple but not to precise clock is needed, like microwave, dishwasher, maybe a toaster oven.

  1. First, A/C input (through some galvanic decoupling and voltage limiting) must be connected to input I(3)
  2. Execute EUR with right values. These values are stored into a 2-bit register ir_eur, value of which is address to a lookup table that determined XOR mask and counter limit
    ...
    
    when opr_eur =>	-- EUR
    	ir_eur <= mr_a(3) & mr_a(0); -- middle 2 bits are ignored
    ...
    
    -- EUR lookup
    constant eur_lookup: mem4x14 := (
    	O"73" & X"FF",	-- 00    60Hz, inverting
    	O"73" & X"00",	-- 01    60Hz, not inverting (power-up default)
    	O"61" & X"FF",	-- 10    50Hz, inverting
    	O"61" & X"00"	-- 11    50Hz, not inverting
    );

3. When the counter reaches the limit, ir_sec flag is set, which is consumed as skip condition for SOS instruction. If set, SOS asynchronously resets this flag, allowing another 1 second to elapse and be detected. 

-- 1 second timer logic
sos_clr <= (ir_run and ir_sec and t7) when (opr = opr_sos) else '0';

on_i_clk: process(nPOR, i_clk, sos_clr)
begin
	if ((nPOR = '0') or (sos_clr = '1')) then
		ir_sec <= '0';
		if (nPOR = '0') then
			ir_cnt <= (others => '0');
		end if;
	else
		if (rising_edge(i_clk)) then
			if (ir_cnt = eur_limit) then	-- if 50 or 60 reached
				ir_cnt <= (others => '0');	-- reset counter
				ir_sec <= '1';					-- set 1 second flag
			else
				ir_cnt <= std_logic_vector(unsigned(ir_cnt) + 1);
			end if;
		end if;
	end if;
end process;

This is as close as EMZ1001A can get to timer interrupt - main loop of the program (e.g. updating LEDs, scanning keyboard) needs to run all the time, with SOS places somewhere in that loop to detect events 1 second apart. 

Discussions