SimpleCPU Monitor: MikeMon

Home

Figure 1 : Apple I computer

Programs on the simpleCPU are best described as bare metal software, code that runs directly on physical hardware without an underlying operating system i.e. your program is the only code in the processor's memory, having direct unrestricted access to the processor's registers, memory and peripherals. This arrangement is fine for application specific systems designed to do one thing, but for general purpose systems we need some means of uploading and downloading code, we need some code to start the computer. Early home computers such as the Apple-I from 1976 (Link) shown in figure 1, solved this problem by using a monitor program: WOZ Monitor (WozMon) written by Steve Wozniak (Link). Monitor code (Link) allows programmers to interact with the processor's hardware, load and debug programs, the forerunner to modern operating system kernels (Link) and command-line shells (Link). As these early machines had very limited memory, monitor programs were very small e.g. WozMon fits inside 256 bytes of memory, therefore, these types of programs can only supported very basic read, write and execute commands.

Table of Contents

Hardware
Commands
Software - MikeMon v1
Software - MikeMon v2
Hardware / Software - MikeMon v3
Testing

Hardware

The Apple-I computer used a keyboard and monitor as its user interface. For the simpleCPU i could have used the PS2 keyboard interface designed for the snake game (Link) + the VGA or HDMI controller, but the PS2 interface is not present on the lab's FPGA board. Also, i am not looking to create an Apple-I, rather to add some code to support software development, game development etc. The lab FPGA boards do have an RS232 serial interface. Sooo, for the simpleCPU computer the user can use a terminal program (Putty) (Link) running on the PC to send and receive ASCII characters across a serial line i.e. talk to the monitor program running on the simpleCPU. To connect the PC to this serial line we will use an USB-to-serial adaptor running at 57600 bps, as shown in figure 2. Under Windows i use Putty to open a terminal on the PC, under Linux i used the screen command. Note, for more information on RS232 serial communications here are some past projects: (Link), (Link).




Figure 2 : Serial connectors, Putty (Windows), screen command (Linux)

Within the FPGA the serial port hardware i.e. the Universal Asynchronous Receiver Transmitter unit (UART) (Link) is mapped into the simpleCPU's memory map as shown in figure 3. The UART hardware has a fixed bit-rate of 57600 bps and no internal FIFO buffers i.e. it only has TX and RX registers. Therefore, its the monitor software's responsibility to ensure that as data is received it is moved into a software queue, otherwise the next character received will overwrite the last one. Similar for transmission of data, the monitor software has to check that the last character sent to the UART has been transmitted, otherwise it could overwrite/corrupt the current character being transmitted i.e. flow control is implemented in software. The serial port connector is only wired up to the TX and RX pins, flow control pins such as RTS and CTS are not connected, a design choice to reduce hardware, save money, what could go wrong :).

Note, if you would like to know more about UARTs, digikey has a good article and VHDL implementation here: (Link).

Figure 3 : Memory map

Commands

To replicate WozMon on the simpleCPU you first need to understand what commands were supported in this software and how these commands were used. Finding information about this code was relatively easy, in the land of retro computing its a historically significant piece of code, but finding detailed examples of how this code behaved for different combinations of commands, or error states was a little more tricky i.e. lots of contradicting statements regarding what it could and could not do. Yes, i could go through the original source code, but with any optimised software/hardware you sometimes never know what secret sauce was used i.e. what undocumented "feature" made the system behave the way it did. Also, it would be a lot of work :). Sooo, i decided to base my version of WozMon i.e. MikeMon, on the information obtained from this webpage: (Link). Also as the Apple-I was based on the 6502 there are some requirement differences, sooo MikeMon will not be an exact copy, some small changes with be needed for the simpleCPU implementation.

When running the monitor program it will display a "\" symbol as a prompt. I also decided to add a welcome banner, display "MikeMon", to show that the monitor code has started, or has been reset when an error occurs, as shown in figure 3. When a command is completed successfully, or when you just press the enter key, a new prompt "\" symbol is displayed. The user can now enter read, write or execute commands to manipulate memory, or run programs.

Note, i decided to keep the cursor on the same line as the "\" symbol, as you would see in a normal command-line terminal, in the original WozMon the cursor was moved below the "\" symbol when you entered a command.

Figure 3 : MikeMon

To read data stored in a memory location you simply enter its address, then press the ENTER key. To simplify coding the address must be specified in hex i.e. base-16. For the simpleCPU the range of addresses is 0x000 to 0xFFF i.e. the simpleCPU has a 12bit address bus. You do not need to enter the leading "0x", or all three digits e.g. 00A, 0A and A all represent the address 10 in decimal. If you were to enter more than three digits for an address only the last 3 digits are used e.g. 1234 would be treated as address 0x234, or 564 in decimal. Hex digits A, B, C, D, E and F must be capital letters, lower case letters will cause an error i.e. restart the monitor code. When complete a new prompt "\" symbol is displayed.

Note, you can use this three character limit to correct mistakes when typing e.g. 122123 will "overwrite" the incorrect address 122 with 123. Like the original WozMon you can press the BACKSPACE key to delete an entered char, however, like Wozmon this deletion is not displayed on the screen, it is only recorded internally in the code. If all else fails you can restart MikeMon by pressing the ESC key.

Read command examples
---------------------  
  
4F                 # read single memory location, display data at address 0x4F or decimal 71. 
4F 52 56           # read multiple memory locations, display data from address 0x4F, 0x52 and 0x56
.59                # read block of memory locations, display data from the last read address to address 0x59
4F.5F              # read block of memory locations, display data from address 0x4F to address 0x5F 
4F.52 56 58.5A     # mix mode, block read, followed by a single read, followed by a block mode, any combinations allowed.

Note, a SPACE char separates the read commands. You can not have multiple SPACE chars in a row i.e. a space followed by a space, as after a SPACE char the monitor code is expecting a number. The end of a command is identified by either a SPACE char or an ENTER char.

Figure 4 : read commands

A difference to WozMon is that data is displayed as 16bit values as the simpleCPU's memory is 16bits wide. When performing a block reads, values are displayed in lines of 16 words i.e. to prevent word wrap in the terminal. Each line starts with the address of the first data element in that row, as shown in figure 5.

Note, not quite sure of the rules when using the ".XXX" block read command i.e. what start address to use. I have seen different explanations, the way i have implemented it is that it start from the next unread memory location e.g. if you perform a read of address 60, then enter the command ".63", MikeMon will display the values of memory locations 61, 62 and 63, as you have already seen what is in memory location 60.

Figure 5 : block read command

To write data to a memory location you enter its address, then a COLON character, the data to be stored and then ENTER. Again the address and data values must be specified in hex i.e. base-16, address range is 12bits: 0x000 to 0xFFF and data is 16bits: 0x0000 to 0xFFFF. Like the address you do not need to enter all four data digits e.g. 000A, 00A, 0A and A all represent the data 10 in decimal. To signal that the write or block write has been performed the previous value from that address is first read and displayed i.e. displays the old value. When complete a new prompt "\" symbol is displayed.

Note, not sure why Wozmon read and displayed the old value when you do a write, i have kept this behaviour in for the moment, but may remove it. Also, not quite sure of the rules when using the ":" write command i.e. some examples have a SPACE char between the ":" and the data digits, others do not. I decided not to have a SPACE char i.e. "400:A" rather than "400: A", as its less to type and felt more inline with the "." read command.

Write examples
400:A0                        # write single memory location, memory location 0x400 is updated with the value 0x00A0
:A1                           # write single memory location, write data to the last write address+1
:A2 A3 A4                     # write block of memory locations, write data to the last write address+1, then to sequential memory locations
410:A0 A1 A2 A3 A4            # write block of memory locations, write data to sequential addresses M[410]=0xA0, M[411]=0xA1, M[412]=0xA2,
                              # M[413]=0xA3, M[414]=0xA4.

Note, again not quite sure of the rules when using the ":XXX" single or block write command i.e. what is the start address. Again the way i have implemented it is that it starts from the next memory location e.g. if you perform a write of address 60, then enter the command ":FF", MikeMon will write the value 0x00FF to memory location 61.

Figure 4 : write commands

The final command supported by WozMon is the run command: "R". To execute a program stored in memory the user enters the address of the first instruction, a "R" char and then ENTER. The the monitor code then jumps to that instruction and control of the UART and the processor switches to this program.

Run Example
100R            # jump to address 0x100 and execute instruction

To test if this command works correctly we need some code to run :). This can be anything e.g. 1 + 1, but to make this code visible via the serial port i decided to implement a "Hello World" test program, shown below. This code uses some of the MikeMon subroutines and variables, the main one was the string transmit subroutine: uart_tx_string. These labels need to be declared in this test program i.e. their addresses. This information can be obtained from the label.txt file when MikeMon code is assembled. The remaining Hello_World program is stored at address 500, or 0x1F4 hex, onwards i.e. anywhere in free memory.

.addr 0
start:

.addr 283
uart_tx_string:

.addr 500
test:
  load ra message_address          # display welcome message
  store ra string_pointer
  call uart_tx_string
  jump start

message_address:
  .data welcome

message:
  .data 0x0A    # LF
  .data 0x0D    # CR
  .data 0x4D    # H
  .data 0x69    # e
  .data 0x6B    # l
  .data 0x65    # l
  .data 0x6F    # o
  .data 0x6E    # SPACE
  .data 0x4D    # W
  .data 0x69    # o
  .data 0x6B    # r
  .data 0x65    # l
  .data 0x6F    # d
  .data 0x0A    # LF
  .data 0x0D    # CR
  .data 0x00    # NULL

Software - MikeMon v1

This is very much a pre-release version, a work in progress, buyer beware :). Listing below, you can download this file here: (mikemon_v1.asm). Unfortunately the lack of flow control on the UART has come back to bite me :(. From a normal serial terminal e.g. Putty or screen, with a human "user" all works fine, as the processor has milliseconds worth of delays between key presses to receive and send characters. However, if you cut-&-paste valid write commands into the serial terminal, this program "user" will blast this data down the serial line as fast as possible i.e. no delays between characters, which will cause serial data to be dropped, characters transmitted from the PC to be missed.

Note, the reason for cut-&-paste into the serial terminal was to allow the user to upload a program i.e. use multiple block write command, rather than having to manually type this data. Once the program had been written into memory the user could then run this program using the "R" command.

To understand what the issue is consider the block write command below:

RX - 0 1 F 4 : 0 0 0 1 SP 0 0 0 2 0 0 0 3 SP 0 0 0 4 CR 
TX -   0 1 F 4 : 0 0 0 1 SP 0 0 0 2 0 0 0 3 SP 0 0 0 4 CR 0 1 F 4 : SP 0 0 0 0 CR 

Each received character is echoed back to the serial terminal by the monitor code, so that the user can see what they are typing. From a timing point of view this is ok, as you have one char in and one char out, BUT each time you have a CR the monitor will also send back to the terminal the old value of the first address written to i.e. in the above example 01F4:0000. Thats 11 characters (including CR), sooo whilst this is being transmitted to the PC the monitor code can not receive the next block write command i.e. tx code is blocking, it waits until the string "01F4:0000" has been transmitted :(.

##############
# MikeMon v1 #
##############

##############
# MEMORY MAP #
##############

# ADDR		WR						RD
# 0xFFE 	HDMI_Y_SIZE				HDMI_Y_SIZE
# 0xFFD     HDMI_X_SIZE				HDMI_X_SIZE
# 0xFFC 	HDMI_COLOUR				HDMI_COLOUR
# 0xFFB     HDMI_TILE				HDMI_TILE
# 0xFFA 	HDMI_Y_POS				HDMI_Y_POS
# 0xFF9     HDMI_X_POS				HDMI_X_POS
# 0xFF8 	HDMI_COMMAND			HDMI_COMMAND
# 0xFF8     HDMI_STATUS				HDMI_STATUS
# 0xFF7     GPO				        GPO
# 0xFF6 	GPO			            GPI
# 0xFF5     UART TX DATA			UART RX DATA
# 0xFF4     UART TX DATA			UART STATUS 

# 0xFF3     MEM	RAM					RAM
# ...		...						...
# 0x000		MEM	RAM					RAM

##################
# UART REGISTERS #
##################

# TX : B7 - B0 data
# RX : B7 - B0 data 

# STATUS REGISTER VERSION-A
# -------------------------
# B7 : NU
# B6 : NU
# B5 : NU
# B4 : NU
# B3 : NU
# B2 : TX Idle
# B1 : RX Idle
# B0 : RX Valid

###############
# ASCII CODES #
###############

# Dec Hex Char                           Dec Hex Char      Dec Hex Char     Dec Hex Char
# ------------                           ------------      ------------     ------------
#  0  00  NUL (null)                      32  20  SPACE     64  40  @        96   60  `
#  1  01  SOH (start of heading)          33  21  !         65  41  A        97   61  a
#  2  02  STX (start of text)             34  22  "         66  42  B        98   62  b
#  3  03  ETX (end of text)               35  23  #         67  43  C        99   63  c
#  4  04  EOT (end of transmission)       36  24  $         68  44  D        100  64  d
#  5  05  ENQ (enquiry)                   37  25  %         69  45  E        101  65  e
#  6  06  ACK (acknowledge)               38  26  &         70  46  F        102  66  f
#  7  07  BEL (bell)                      39  27  '         71  47  G        103  67  g
#  8  08  BS  (backspace)                 40  28  (         72  48  H        104  68  h
#  9  09  TAB (horizontal tab)            41  29  )         73  49  I        105  69  i
# 10  0A  LF  (NL line feed, new line)    42  2A  *         74  4A  J        106  6A  j
# 11  0B  VT  (vertical tab)              43  2B  +         75  4B  K        107  6B  k
# 12  0C  FF  (NP form feed, new page)    44  2C  ,         76  4C  L        108  6C  l
# 13  0D  CR  (carriage return)           45  2D  -         77  4D  M        109  6D  m
# 14  0E  SO  (shift out)                 46  2E  .         78  4E  N        110  6E  n
# 15  0F  SI  (shift in)                  47  2F  /         79  4F  O        111  6F  o
# 16  10  DLE (data link escape)          48  30  0         80  50  P        112  70  p
# 17  11  DC1 (device control 1)          49  31  1         81  51  Q        113  71  q
# 18  12  DC2 (device control 2)          50  32  2         82  52  R        114  72  r
# 19  13  DC3 (device control 3)          51  33  3         83  53  S        115  73  s
# 20  14  DC4 (device control 4)          52  34  4         84  54  T        116  74  t
# 21  15  NAK (negative acknowledge)      53  35  5         85  55  U        117  75  u
# 22  16  SYN (synchronous idle)          54  36  6         86  56  V        118  76  v
# 23  17  ETB (end of trans. block)       55  37  7         87  57  W        119  77  w
# 24  18  CAN (cancel)                    56  38  8         88  58  X        120  78  x
# 25  19  EM  (end of medium)             57  39  9         89  59  Y        121  79  y
# 26  1A  SUB (substitute)                58  3A  :         90  5A  Z        122  7A  z
# 27  1B  ESC (escape)                    59  3B  ;         91  5B  [        123  7B  {
# 28  1C  FS  (file separator)            60  3C  <         92  5C  \        124  7C  |
# 29  1D  GS  (group separator)           61  3D  =         93  5D  ]        125  7D  }
# 30  1E  RS  (record separator)          62  3E  >         94  5E  ^        126  7E  ~
# 31  1F  US  (unit separator)            63  3F  ?         95  5F  _        127  7F  DEL

#####################
# TERMINAL COMMANDS #
#####################

# Common serial port speeds 
# Bit rate (bit/s)  Time per bit (μs)  Common applications 
# ----------------  -----------------  -------------------
# 75 	            13333.3 	 	
# 110 	            9090.9 	     	   Bell 101 modem
# 134.5 	        7434.9 	  	
# 150 	            6666.6 	 	
# 300 	            3333.3 	       	   Bell 103 modem or V.21 modem
# 600 	            1666.7 	  	
# 1,200 	        833.3 	  	       Bell 202, Bell 212A, or V.22 modem
# 1,800 	        555.6 	  	
# 2,400 	        416.7 	  	       V.22bis modem
# 4,800 	        208.3 	     	   V.27ter modem
# 7,200 	        138.9 	  	
# 9,600 	        104.2 	  	       V.32 modem
# 14,400 	        69.4 	     	   V.32bis modem
# 19,200 	        52.1 	  	
# 31,250 	        32 	  	           MIDI port
# 38,400 	        26.0 	  	
# 56,000 	        17.9 	  	       V.90/V.92 modem
# 57,600 	        17.4 	  	       V.32bis modem with V.42bis compression   ****
# 76,800 	        13.0 	  	       BACnet MS/TP networks[20]
# 115,200 	        8.68 	  	       V.34 modem with V.42bis compression, 
#                                      low cost serial V.90/V.92 modem with V.42bis or V.44 compression 
# screen /dev/ttyS0 19200,cs8
# screen /dev/ttyUSB0 57600

# Screen Command 	  Task
# --------------      ----
# Ctrl+a c 	          Create new window
# Ctrl+a k 	          Kill the current window / session
# Ctrl+a w            List all windows
# Ctrl+a 0-9 	      Go to a window numbered 0 9, use Ctrl+a w to see number
# Ctrl+a Ctrl+a 	  Toggle / switch between the current and previous window
# Ctrl+a S 	          Split terminal horizontally into regions and press Ctrl+a c to create new window there
# Ctrl+a :resize 	  Resize region
# Ctrl+a :fit 	      Fit screen size to new terminal size. You can also hit Ctrl+a F for the the same task
# Ctrl+a :remove 	  Remove / delete region. You can also hit Ctrl+a X for the same taks
# Ctrl+a tab 	      Move to next region
# Ctrl+a D (Shift-d)  Power detach and logout
# Ctrl+a d 	          Detach but keep shell window open
# Ctrl-a Ctrl-\ 	  Quit screen
# Ctrl-a ? 	          Display help screen i.e. display a list of commands
# Backspace allowed to delete entered char, but display is not updated
# Escape restarts

# Monitor Commands
# ----------------

# Read examples    
# 4F
# .55
# 4F 52 56
# 4F.5F
# 4F.52 56 58.5A

# Write examples
# 30:A0
# :A1 A2 A3 A4 A5
# 30:A0 A1 A2 A3 A4 A5

# Run
# 100 R

# Main Program
# ------------

start:
  load ra welcome_address          # display welcome message
  store ra string_pointer
  call uart_tx_string
  move ra 0
  store ra address

restart:
  move ra 0  			           # reset buffer index
  store ra buffer_rd_index
  store ra buffer_wr_index
  store ra mode
  store ra current_command

  move ra 0x5C			           # display prompt "\"
  call uart_tx 

# 7F  DEL   0
# 0D  CR   6
# 1B  ESC  13

get_CHAR:
  call uart_rx                     # wait for CHAR

  cmp ra 0x7F			           # DEL?
  jumpnz get_CHAR_test_cr         

  load ra buffer_wr_index          # yes, decrement write index
  sub ra 1
  jumpn get_CHAR
  store ra buffer_wr_index
  jump get_CHAR

get_CHAR_test_cr:  
  cmp ra 0x0D			           # CR?
  jumpnz get_CHAR_test_esc  

  call uart_tx_cr_lf
  jump process_BUF                 # yes, process buffer

get_CHAR_test_esc:
  cmp ra 0x1B			           # ESC?
  jumpz start                      # yes, error start

get_CHAR_test_line_feed:
  cmp ra 0x0A			           # LF?
  jumpz get_CHAR                   # yes, ignore

get_CHAR_buffer:
  load ra buffer_address       	   # no, store in buffer, must be in first 255 addr
  addm ra buffer_wr_index          # calc buffer address
  move rb ra

  load ra tmp                      # reload RX data 
  store ra (rb)                    # store in buffer
  load ra buffer_wr_index          # increment index
  add ra 1
  store ra buffer_wr_index

  subu ra 64                       # exceeded buf size 0-63?
  jumpn get_CHAR                   # no, repeat
  jump start                       # yes, error restart

# when CR pressed process data, buffer_wr_index points to last empty space

process_BUF:
  move rc 0			               # rc = address 			
  move rd 0                        # rd = digit count
  load ra current_command     
  store ra previous_command        # log command to detect duplicates

  load ra buffer_rd_index          # get read index     

digit_loop: 
  subm ra buffer_wr_index          # does read index = write index = empty space?
  jumpnz digit_read_char           # no, read char
  and rd 0xFF
  jumpnz decode_NUM                # digits entered decode
  jump restart                     # no digits entered restart

digit_read_char: 
  load ra buffer_address       	   
  addm ra buffer_rd_index          # generate read pointer
  load ra (ra)                     # read buffer
  store ra tmp			

  call test_digit                  # is char a hex digit
  cmp ra 0xFF                    
  jumpz decode_CMD	               # not a digit, decode
         
  asl rc                           # move hex digit up
  asl rc
  asl rc
  asl rc
  add rc ra                        # add new digit
  add rd 1                         # increment number digit count
  
digit_inc_index:
  load ra buffer_rd_index          # inc read pointer
  add ra 1
  store ra buffer_rd_index
  jump digit_loop                  # repeat

decode_NUM:
  # 0 = read
  # 1 = multi read
  # 2 = write
  # 3 = multi write

  load ra mode
  sub ra 1
  jumpz decode_NUM_multiple_read
  sub ra 1
  jumpz decode_NUM_single_write
  sub ra 1
  jumpz decode_NUM_multiple_write

decode_NUM_single_read: 
  move ra rc			           # save generated address
  store ra address

  call uart_tx_hex                 # text data
  call uart_tx_colon_space         # tx address
  
  load ra address
  load ra (ra)                     # read data at address
  call uart_tx_hex                 # text data

  move ra 0
  #store ra mode
  store ra current_command   

  call uart_tx_cr_lf
  call inc_rd_index                # inc read index           
  jump process_BUF                 # continue

decode_NUM_multiple_read:
  move ra rc			           # save generated address
  store ra max_address
  subm ra address                  # is address >= max error
  jumpn start

  load ra address
  call uart_tx_hex                 # tx address
  move ra 0x3A                     # tx colon
  call uart_tx

  move ra 0                        # line length count
  store ra count

decode_NUM_multiple_read_loop:
  move ra 0x20                     # tx space
  call uart_tx

  load ra address  
  load ra (ra)                     # read data at address
  store ra tmp
  call uart_tx_hex                 # tx data

  load ra max_address              # is address > max_address
  subm ra address
  jumpz decode_NUM_multiple_read_exit

  load ra address                  # inc address
  add ra 1
  store ra address

  load ra count                    # max line length?
  add ra 1 
  store ra count
  sub ra 16
  jumpnz decode_NUM_multiple_read_loop

  call uart_tx_cr_lf               # yes, newline
  load ra address
  call uart_tx_hex                 # tx address
  move ra 0x3A                     # tx colon
  call uart_tx 
       
  move ra 0                        # reset line count
  store ra count 
  jump decode_NUM_multiple_read_loop

decode_NUM_multiple_read_exit:
  move ra 0
  store ra mode
  store ra current_command   

  call uart_tx_cr_lf
  call inc_rd_index            
  jump process_BUF                 # continue

decode_NUM_single_write: 
  move ra rc			           # save generated data
  store ra data

  load ra address
  call uart_tx_hex                 # text data
  call uart_tx_colon_space         # tx address
  
  load ra address
  load ra (ra)                     # read data at address
  call uart_tx_hex                 # text data

  call uart_tx_cr_lf

  load ra address                  # read address
  move rb ra
  load ra data                     # read data
  store ra (rb)                    # write data

  call inc_rd_index        
  jump process_BUF                 # continue

decode_NUM_multiple_write: 
  move ra rc			           # save generated data
  store ra data

  load ra address                  # read address
  add ra 1                         # inc
  store ra address
  move rb ra

  load ra data                     # read data
  store ra (rb)

  call inc_rd_index                #  
  jump process_BUF                 # continue

# 20  SP     
# 2E  .    
# 3A  :    
# 52  R    
# 72  r    

decode_CMD:
  load ra tmp                      # read char
  store ra current_command

  cmp ra 0x20
  jumpz decode_CMD_space	       # space
  cmp ra 0x2E
  jumpz decode_CMD_dot		       # full stop = read
  cmp ra 0x3A
  jumpz decode_CMD_colon	       # colon = write
  cmp ra 0x52
  jumpz decode_CMD_run 	           # R = run
  cmp ra 0x72
  jumpz decode_CMD_run 	           # r = run

  jump start                       # error, restart

decode_CMD_space:
  and rd 0xFF                      # was there a number
  jumpnz decode_CMD_space_update
  jump start                       # error, restart

decode_CMD_space_update:
  load ra mode                  
  sub ra 1
  jumpn decode_NUM_single_read     # if 0 mode read
  jumpz decode_NUM_multiple_read   # if 1 mode multiple read
  sub ra 1
  jumpz decode_CMD_space_update_1  # if 2 mode write
  jump decode_NUM_multiple_write   # if 3 mode multiple write
             
decode_CMD_space_update_1:
  move ra 3                        # update mode to write multiple               
  store ra mode
  jump decode_NUM_single_write

decode_CMD_dot:
  load ra  previous_command
  sub ra 0x2E                      # was previous command a dot
  jumpnz decode_CMD_dot_update
  jump start                       # error, restart

decode_CMD_dot_update:
  and rd 0xFF                      # was there a number
  jumpz decode_CMD_dot_update_1

  move ra rc			           # save generated address
  store ra address
  move ra 1                        # flag read multiple               
  store ra mode
  call inc_rd_index            
  jump process_BUF                 # continue 

decode_CMD_dot_update_1:
  load ra address                  # no number, inc old address
  add ra 1
  store ra address
  move ra 1                        # flag read multiple               
  store ra mode
  call inc_rd_index            
  jump process_BUF                 # continue 

decode_CMD_colon:
  load ra  previous_command        # was previous command a colon
  sub ra 0x3A
  jumpnz decode_CMD_colon_update
  jump start                       # error, restart

decode_CMD_colon_update:
  and rd 0xFF                      # was there a number?
  jumpz decode_CMD_colon_update_1

  move ra rc			           # save generated address
  store ra address
  move ra 2                        # flag as write
  store ra mode
  call inc_rd_index            
  jump process_BUF                 # continue 

decode_CMD_colon_update_1:
  load ra address                  # no, increment address
  add ra 1
  store ra address
  move ra 2                        # flag as write
  store ra mode
  call inc_rd_index            
  jump process_BUF                 # continue 

decode_CMD_run:
  move ra rc			           # save generated address
  store ra address
  addm ra const_0x8000             # turn into a JUMP instruction
  store ra decode_CMD_run_go       # overwrite instruction

decode_CMD_run_go:
  jump decode_CMD_run_go           # run code


###############
# SUBROUTINES #
###############

# increment read index
# --------------------

inc_rd_index:
  load ra buffer_rd_index       
  subm ra buffer_wr_index          # does read index = write index = empty space?
  jumpz inc_rd_index_exit          # exit

  load ra buffer_rd_index          # inc read index
  add ra 1
  store ra buffer_rd_index

inc_rd_index_exit: 
  ret                              # exit

# Test if char is a digit
# -----------------------
# 0 to 9 = 0x30 to 0x39 
# A to F = 0x41 to 0x46 

test_digit: 
  sub ra 0x30                      # Shift ASCII digits down
  jumpn test_digit_err             # < '0' -> invalid
  cmp ra 10
  jumpn test_digit_exit            # '0'..'9' -> valid (0..9)

  load ra tmp
  sub ra 0x37                      # Shift ASCII hex upper ('A' = 0x41 -> 10)
  cmp ra 10
  jumpn test_digit_err             # < 'A' -> invalid
  cmp ra 16
  jumpn test_digit_exit            # 'A'..'F' -> valid (10..15)

test_digit_err:
  move ra 0xFF                     # Not a valid hex char

test_digit_exit:
  ret

###########################
# SERIAL PORT SUBROUTINES #
###########################

# STATUS REGISTER VERSION-A
# -------------------------
# B7 : NU            128
# B6 : NU            64
# B5 : NU            32
# B4 : NU            16
# B3 : NU            8
# B2 : TX Idle       4
# B1 : RX Idle       2
# B0 : RX Valid      1

# RX Char
# -------

uart_rx:
  load ra UART_STATUS              # test status  
  and ra 0x01                      #       
  jumpz uart_rx           

  load ra UART_RX                  
  store ra tmp
  call uart_tx                     # echo recieved char

  load ra tmp                      # reload
  ret 

# TX Char
# -------

uart_tx: 
  store ra char

uart_tx_wait:
  load ra UART_STATUS              # test status  
  and ra 0x04                      #        
  jumpz uart_tx_wait  

  load ra char
  store ra UART_TX                 # tx ASCII char in RA
  ret

# TX CR LF
# --------

uart_tx_cr_lf: 
  moveu ra 0x0D                    # tx CR 
  call uart_tx
  moveu ra 0x0A                    # tx LF
  call uart_tx
  ret

# TX COLON SPACE
# --------------

uart_tx_colon_space: 
  moveu ra 0x3A                    # tx COLON
  call uart_tx
  moveu ra 0x20                    # tx SPACE
  call uart_tx
  ret


# TX Hex 16bit
# ------------

uart_tx_hex:
  store ra tmp                     # buffer char
  xchg ra
  asr ra
  asr ra 
  asr ra
  asr ra                           # tx X000
                    
  call print_nibble
                   
  load ra tmp
  xchg ra                          # tx 0X00

  call print_nibble

  load ra tmp                      # tx 00X0      
  asr ra
  asr ra 
  asr ra
  asr ra
                      
  call print_nibble   
                 
  load ra tmp                      # tx 000X

print_nibble:
  and ra 0x0F
  add ra 0x30                      # Convert 0-9 to ASCII '0'-'9'
  cmp ra 0x3A
  jumpn print_nibble_send
  add ra 0x07                      # Convert 10-15 to ASCII 'A'-'F'

print_nibble_send:
  jump uart_tx                     # Send character 

# TX String (must terminate with a \0) 
# ------------------------------------

uart_tx_string:
  load ra string_pointer
  move rb ra

uart_tx_string_loop: 
  load ra (rb)                     # load  char 
  and ra 0xFF
  jumpz uart_tx_string_exit        # exit if 0
  call uart_tx                     # tx

  add rb 1                         # inc address
  jump uart_tx_string_loop         # repeat

uart_tx_string_exit:
  ret


########
# DATA #
########

buffer_address:
  .data buffer

buffer:
  space(64)

buffer_rd_index:
  .data 0 

buffer_wr_index:
  .data 0 

string_pointer:
  .data 0

# ------------------
# - Welcome string -
# ------------------

welcome_address:
  .data welcome

welcome:
  .data 0x0A    # LF
  .data 0x0D    # CR
  .data 0x4D    # M
  .data 0x69    # i
  .data 0x6B    # k
  .data 0x65    # e
  .data 0x4D    # M
  .data 0x6F    # o
  .data 0x6E    # n
  .data 0x0A    # LF
  .data 0x0D    # CR
  .data 0x00    # NULL

# Constants
# ---------

const_0x8000:
  .data 0x8000

# Variables
# ---------

# 0 = normal
# 1 = prev read
# 2 = prev write
# 3 = prev write space

mode:
  .data 0

tmp:
  .data 0

count:
number:
  .data 0		

current_command:
  .data 0
 
previous_command:
  .data 0

address:
  .data 0

max_address:
  .data 0

data:
  .data 0

char:
  .data 0

Software - MikeMon v2

To prevent commands / characters from the PC from being dropped we could consider a number of different solutions, or a combination of these:

Hardware / Software - MikeMon v3

Testing

-------------------
-- 8N1 Serial RX --
-------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity uart_data_display is
  generic (
    BAUD_RATE : integer := 9600 );
  port (
    ser_in    : in  std_logic;
    data_out  : out std_logic_vector(7 downto 0) := (others => '0');
    valid_out : out std_logic                    := '0' );
end uart_data_display;

architecture uart_data_display_arch of uart_data_display is
  constant BIT_PERIOD : time := 1 sec / BAUD_RATE;

begin

  uart_monitor : process
    variable rx_shift : std_logic_vector(7 downto 0);
  begin
    valid_out <= '0';

    -- 1. Wait for falling edge of the Start Bit
    wait until falling_edge(ser_in);

    -- 2. Delay to mid-bit position to verify Start Bit = 0
    wait for BIT_PERIOD / 2;

    if ser_in = '0' 
    then
      -- 3. Align to first data bit center
      wait for BIT_PERIOD;

      -- 4. Read 8 Data Bits (LSB first)
      for i in 0 to 7 
      loop
        rx_shift(i) := ser_in;
        wait for BIT_PERIOD;
      end loop;

      -- 5. Stop Bit duration sample
      data_out  <= rx_shift;
      valid_out <= '1';
      wait for BIT_PERIOD;
    end if;
  end process;

end uart_data_display_arch;
-------------------
-- 8N1 Serial TX --
-------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;

entity uart_data_source is
  generic (
    BAUD_RATE : integer := 9600;  
    FILE_PATH : string  := "input.txt" );
  port (
    start   : in  std_logic; 
    busy    : out std_logic := '0'; 
    ser_out : out std_logic := '1' );
end uart_data_source;

architecture uart_data_source_arch of uart_data_source is
  constant BIT_PERIOD : time := 1 sec / BAUD_RATE;

begin

  uart_sim: process
    file input_file       : text;
    variable line_buf     : line;
    variable char_val     : character;
    variable char_code    : integer;
    variable data_byte    : std_logic_vector(7 downto 0);
    variable read_success : boolean;

  begin
    ser_out <= '1';
    busy    <= '0';

    -- Wait for start signal, user signal, controlled from TB
    wait until rising_edge(start);

    busy <= '1';
    file_open(input_file, FILE_PATH, read_mode);

    -- Read text file line by line
    while not endfile(input_file) 
    loop
      readline(input_file, line_buf);

      -- Process each character in line
      while line_buf'length > 0 
      loop
        read(line_buf, char_val, read_success);
        if read_success 
        then
          -- Convert character to 8-bit vector
          char_code := character'pos(char_val);
          data_byte := std_logic_vector(to_unsigned(char_code, 8));

          -- 1. START BIT  
          ser_out <= '0';
          wait for BIT_PERIOD;

          -- 2. DATA BITS, 8 bits, LSB first
          for i in 0 to 7 
          loop
            ser_out <= data_byte(i);
            wait for BIT_PERIOD;
          end loop;

          -- 3. STOP BIT 
          ser_out <= '1';
          wait for BIT_PERIOD;

        end if;
      end loop;
    end loop;

    -- End of Transmission
    file_close(input_file);
    report "UART File TX: Completed sending file." severity note;
        
    ser_out <= '1';
    busy    <= '0';
  end uart_sim;

end uart_data_source_arch;

WORK IN PROGRESS

Creative Commons Licence

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

Contact email: mike@simplecpudesign.com

Back