Close

In-circuit testing using Intel HEX file components

zpekiczpekic wrote 12/04/2021 at 19:47 • 22 min read • Like

Some background...

The main purpose of my Intel HEX project was to:


Turns out, with some breadboards, wiring, and bringing the Intel HEX component buses outside from FPGA into the physical world, the same components can be reused for a custom and handy testing tool.

This testing allows to check:

Note that boards / computers tested this way do not need to be populated fully with ICs or other components, so it is possible to check "dead" or "not yet alive" boards too.

There are two possibilities:

What cannot be readily tested using this approach:

Hardware

For this project I used a cool little 8085-based single board computer (8085 Minimax) described and graciously provided to me by Ken Yap (thanks again!). I was actually in the process of soldering together the board, and decided to use a verification step before plugging in my vintage Soviet CPU to see if there will even be a chance for it working or not...

 The hardware setup is simple but a bit messy affair:

Few notes:

This is how these connections look in VHDL (top file of the project):

                --PMOD interface
                JA1: inout std_logic;    -- Connected to USB2UART
                JA2: inout std_logic;    -- Connected to USB2UART
                JA3: inout std_logic;    -- Connected to USB2UART
                JA4: inout std_logic;    -- Connected to USB2UART
                JB1: out std_logic;    -- GRAY 74F573.19 A0
                JB2: out std_logic;    -- GRAY 74F573.18 A1
                JB3: out std_logic;    -- GRAY 74F573.17 A2
                JB4: out std_logic;    -- GRAY 74F573.16 A3
                JB7: out std_logic;    -- GRAY 74F573.15 A4
                JB8: out std_logic;    -- GRAY 74F573.14 A5
                JB9: out std_logic;    -- GRAY 74F573.13 A6
                JB10: out std_logic;    -- GRAY 74F573.12 A7
                JC1: out std_logic;    -- WHITE 8085.21    A8
                JC2: out std_logic;    -- WHITE 8085.22    A9
                JC3: out std_logic;    -- WHITE 8085.23    A10
                JC4: out std_logic;    -- WHITE 8085.24    A11
                JC7: out std_logic;    -- WHITE 8085.25    A12
                JC8: out std_logic;    -- WHITE 8085.26    A13
                JC9: out std_logic;    -- WHITE 8085.27    A14
                JC10: out std_logic;    -- WHITE 8085.28    A15
                JD1: out std_logic;    -- PURPLE 8085.30 IO/M (low for memory access)
                -- breadboard signal connections
                BB1: inout std_logic;    -- BLUE 8085.12 AD0
                BB2: inout std_logic;    -- BLUE 8085.13 AD1
                BB3: inout std_logic;    -- BLUE 8085.14 AD2
                BB4: inout std_logic;    -- BLUE 8085.15 AD3
                BB5: inout std_logic;    -- BLUE 8085.16 AD4
                BB6: inout std_logic;    -- BLUE 8085.17 AD5
                BB7: inout std_logic;    -- BLUE 8085.18 AD6
                BB8: inout std_logic;    -- BLUE 8085.19 AD7
                BB9: out std_logic;        -- ORANGE    8085.31 nWR
                BB10: out std_logic;        -- YELLOW    8085.32 nRD

Software

There are 3 different toolchains and languages coming together in this project:

The software components are best explained by going through the 4 supported modes of operation:

---------------------------------------------------------------------------------------------            
--     SW7    SW6    Mode                TTY (VGA)            UART TX                7seg LED    
---------------------------------------------------------------------------------------------
--        0        0        sel_hexout        -                         Generated HEX        mem2hex debug port (or bus if nWait = 0)
--        0        1        sel_hexin        Microcode trace    Echo UART RX        hex2mem debug port (or bus if nWait = 0)
--        1        0        sel_loopback0    Echo UART RX        Echo UART RX        Baudrate (decimal)
--        1        1        sel_loopback1    Echo UART RX        Echo UART RX        UART mode    
---------------------------------------------------------------------------------------------

Mode 3 - display UART mode and loopback test

To have confidence in a test circuit, it is useful for the test circuit to test itself :-) This mode:

UART is two separate circuits (SER2PAR and PAR2SER) that I reuse in many projects. They support a variety of 8-bit per character transmit and receive frames. The terminal program on the host should be set to same setting (8-N-1 in this case)

Anvyl board switches 2..0 select the mode as visible in the image below.

(note 700ms delay per line - this is to allow time for the trace of HEX2MEM microcode to display before processing next incoming character, more about this below)

To simplify top level object, the TTY, video RAM, chargen RAM and VGA controller are wrapped up in one component called TTY2VGA:

Mode 2 - display baudrate and loopback test

Another test mode which:

Anvyl switches SW5..3 select the baudrate from 600 (000) to 57600 (111). Note that the number displayed is not exactly the typical standard rate. The reason is that the frequency is actually measured on the board. First, the FPGA 50MHz board frequency is divided by two prescale factors, one leads to freq_4096 that can be divided by powers of 2 down to 1 Hz, and the other based on the selected divide value to get baudrate_x8 frequency:

prescale: process(CLK, baudrate_x8, freq4096, switch_uart_rate)
begin
    if (rising_edge(CLK)) then
        if (prescale_baud = 0) then
            baudrate_x8 <= not baudrate_x8;
            prescale_baud <= prescale_value(to_integer(unsigned(switch_uart_rate)));
        else
            prescale_baud <= prescale_baud - 1;
        end if;
        if (prescale_power = 0) then
            freq4096 <= not freq4096;
            prescale_power <= (clk_board / (2 * 4096));
        else
            prescale_power <= prescale_power - 1;
        end if;
    end if;
end process;

Eventually these two are used to feed into a counter that counts in BCD (more precisely, it has a 32-bit adder inside that can add in BCD or binary):

counter: freqcounter Port map ( 
        reset => RESET,
      clk => freq_2048(11),
      freq => baudrate_x1,
        bcd => '1',
        add => X"00000001",
        cin => '1',
        cout => open,
      value => baudrate_debug
    );

 The counter assumes that the "clk" signal is 50% duty cycle, as it has 2 counters which work on opposite sides of the clk level. Counts accumulated on "high" side are displayed on "low" side and vice versa, with the net result that each 1s the count ("freq" signal) is refreshed. Because 50MHz cannot be divided by some integer to create exact baudrate, they are off by less than <1% which is of course well within timing tolerances. 

This way the crucial UART frequency generation, LED debug display etc. are tested. 


Mode 1 - Accept ASCII stream in Intel HEX format, and write to memory

The key component here is Hex2Mem which I intend to document better on its own page. But few explanations here until I get around to do it. Refer to slightly modified VHDL and microcode

Basic operation is as follows:

  1. Wait for ASCII character
  2. If there is one, branch to location that processes it (the ASCII code can be thought of as an "instruction")
  3. If invalid, output error, go to step 1
  4. If valid, process it based on which it is and what is expected or not (for example ":" can come only once at the beginning of line, spaces or tabs anywhere but will be ignored, unless they are between hex digits that should not be split (e.g. data bytes)
  5. Each two digits are written into one internal byte memory location (there is a small 64 bytes buffer)
  6. As a byte is written into internal RAM, the checksum is updated
  7. The number of bytes received is checked with expected record length, for error check
  8. Final byte received is the checksum. Added to accumulated checksum it should result in 0x00 in the LSB of the checksum register
  9. If checksum is correct, the data bytes are written in a burst to external RAM bus. This means RAM will not be thrashed by bad checksum record
  10. Either CR and/or LF indicates end of record, this increments the line counter, clears the character counter (these are only used to show error message) and processing of new record can start.

This is how it is hooked up into the design:

hexin: hex2mem Port map (
            clk => hex_clk,
            reset_in => reset,
            reset_out => open,
            reset_page => page_sel, -- not really used but i8080-like system would reset at lowest 8k updated
            --
            debug => hexin_debug(15 downto 0),
            --
            nWR => nWrite,
            nBUSREQ => hexin_busreq,
            nBUSACK => hexin_busack,
            nWAIT => nWait,
            ABUS => ABUS,
            DBUS => DOUT,
            BUSY => hexin_busy,    -- yellow LED when busy
            --
            HEXIN_READY => hexin_ready,
            HEXIN_CHAR    => hexin_char,
            HEXIN_ZERO => open,
            --
            TRACE_ERROR => dip_traceerror, 
            TRACE_WRITE => dip_tracewrite, 
            TRACE_CHAR    => dip_tracechar, 
            ERROR => LDT2R,    -- red LED when error detected
            TXDREADY => tty_sent,
            TXDSEND => hexin_debug_send,
            TXDCHAR => hexin_debug_char
        );

Signals:

The video is a shaky recording of a session to input from a test HEX file into the memory. It wasn't successful because I forgot to clear the wait mode, so the component was stuck waiting to write a byte (false condition, so repeat kept executing):

        // ask CPU for memory, then write 1 byte with any number of optional wait cycles
writemem:    ram_addr = bytecnt, nBUSREQ = 0;
        ram_addr = bytecnt, nBUSREQ = 0, if nBUSACK then repeat else next;
        ram_addr = bytecnt, nBUSREQ = 0, nWR = 0;
        ram_addr = bytecnt, nBUSREQ = 0, nWR = 0, if nWAIT then next else repeat;

Finally, I typed a few random characters to show how it detected bad input and emitted error message about it:

//    error codes are 1 to 6, 0 means no error
errcode:        .regfield 3 values
            ok, 
            err_badchar,        // ERR1
            err_unexpected,        // ERR2
            err_badchecksum,    // ERR3
            err_badrecordtype,    // ERR4
            err_badrecordlength,    // ERR5
            same
            default same;

While I was fiddling with WAIT, the host was sending data, and because there is no handshake, many bytes got lost. Eventually it sync'd up with ":" record start character and after that it wrote to RAM and output the trace:

            if TRACE_WRITE then next else nextaddr; 
            emit(char_A);        // A[address]=data
            emit(char_open);
            printaddr();
            emit(char_close);
            printram();


The wait circuit is implemented in top level component, because it is reused by HEX2MEM and MEM2HEX. It is triggered by either component activating nRD or nWR signal (nAccess signal). That means memory operation is requested. If the WAIT is enabled (a S/R flip/flop controls that) then nWAIT is locked low until a button is pressed. This way each memory access can be inspected (the A and DBUS values appear on the 7seg LED which is conveniently 6 digits on Anvyl so 4 hex A and 2 hex DBUS can be displayed).

 The FF below has a little trick - the clock itself is multiplexed depending on its state. When not in WAIT mode (nWait = '1') it will be triggered on nRD or nWR going low, but  once waiting, then press on the button(3) flips in around. Therefore:

-- Wait signal
wait_ena <= not (reset or reset_sw or button(2) or wait_dis);
wait_dis <= not (button(1) or wait_ena);

wait_clk <= (not nAccess) when (nWait = '1') else button(3);
on_wait_clk: process(reset, wait_clk)
begin
    if (wait_dis = '1') then
        nWait <= '1';
    else
        if (rising_edge(wait_clk)) then 
            nWait <= not nWait; 
        end if;
    end if;
end process;

Mode 0 - Read memory contents and convert to Intel HEX format ASCII stream

The key component in this mode is predictably the Mem2Hex described here. This is how the component is hooked-up:

hexout: mem2hex port map (
            clk => hex_clk,
            reset => reset,
            --
            debug => hexout_debug(15 downto 0),
            --
            nRD => nRead,
            nBUSREQ => hexout_busreq,
            nBUSACK => hexout_busack,
            nWAIT => nWait,
            ABUS => ABUS,
            DBUS => DIN,
            START => button(0),        
            BUSY => LDT1Y,            -- yellow LED when busy
            PAGE => page_sel,        -- select any 8k block using micro DIP switches
            COUNTSEL => '0',        -- 16 bytes per record
            TXDREADY => tx_ready,
            TXDSEND => hexout_send,
            CHAR => hexout_char
        );

Few notes:

Here is how the send character handshake appears in the microcode:

        // "UART" is supposed to signal TDXREADY = 1 when presented 0x00 or when serial trasmit is done
emit:        if TXDREADY then next else repeat;    // sync with baudrate clock that drives UART
        if TXDREADY then next else repeat;
        if TXDREADY then next else repeat;
        if TXDSEND then return else return;

 TDXREADY is checked 3 times in a row to prevent any clock domain glitches. Finally, the TXDSEND is checked, but this condition is hardcoded to "1", means it will always return to the caller at this point, but a simple comparator is hooked up to look for check of this condition to generate the send pulse:

-- hack that saves 1 microcode bit width
TXDSEND <= '1' when (unsigned(m2h_seq_cond) = seq_cond_TXDSEND) else '0';


Sanity check for I/O:

Reading the I/O space can give some indication if it "sniffs right", like in this case. The only IC hooked up to I/O space is 8251 UART, which is enabled when address is XXXXXXXX0001XXXX - when dumping out addresses that match it is visible that "something" appears in those locations, while everywhere else the DBUS returns the default float high. 

Like

Discussions