Basic operation is as follows:
- Wait for ASCII character
- If there is one, branch to location that processes it (the ASCII code can be thought of as an "instruction")
- If invalid, output error, go to step 1
- 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)
- Each two digits are written into one internal byte memory location (there is a small 64 bytes buffer)
- As a byte is written into internal RAM, the checksum is updated
- The number of bytes received is checked with expected record length, for error check
- Final byte received is the checksum. Added to accumulated checksum it should result in 0x00 in the LSB of the checksum register
- 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
- 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 );
- clk (IN) - common 12.5MHz, can be virtually any speed, but fast enough to be able to keep up with incoming baudrate
- reset_in (IN) - classic reset
- reset_out (OUT) - will generate a pulse if write is detected to any of the "reset_page" 8k blocks. Typically this would be 0x0000 - 0x3FFF for "PC starts at 0" CPUs (808X, CDP1802), and 0xC000 - 0xFFFF for "reset vector" CPUs (65XX, 68XX, 99XX)
- reset_page (IN) - 8 bits, each indicates 8k block
- debug (OUT) - signals from microcode controller unit, useful for single stepping through microcode
- nWR (OUT) - Z80 style memory write signal
- nBUSREQ (OUT), nBUSACK (IN) - DMA signals. The first time nBUSREQ will be generated when a valid HEX record has been received. At that point, memory write can be allowed if nBUSACK goes low. Which will only happen if operation mode is this one.
- nWAIT (IN) - see description below
- ABUS (OUT), DBUS (OUT) - connections to system bus (in this case, wires to 8085 SBC board)
- BUSY (OUT) - blinkenlight :-)
- HEXIN_READY (IN) - connected to UART, when a valid serial character is received, UART generates which pulse which captures the received character to process.
- HEXIN_CHAR (IN) - 8 bit ASCII character from input stream
- TRACE_ERROR, TRACE_WRITE, TRACE_CHAR (IN) - 3 independent switches that enable tracing when error, when writing to memory or when character is received. These are simply conditions for microcode, if true then execution branches to tracing (output of a text string). This is a fundamental advantage of microcoded designs as the debug facility can be written along (or best - before) the rest of the design / code!
- ERROR (OUT) - red blinkenlight!
- TXDREADY (IN), TXDSEND (OUT), TXDCHAR (OUT) - these are connected to TTY which allows tracing to be shown on VGA. Note that writing to VGA still takes some time so the input stream should be delayed by character or line when extensive tracing is turned on.
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:
- start wait mode: button(1)
- advance: button(3)
- stop wait: button(2), reset, or changing mode (reset_sw signal)
-- 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;