Close

Interactive Verilog UM245R UART

A project log for SPAM-1 - 8 Bit CPU

8 Bit CPU in 7400 with full Verilog simulator and toolchain

john-lonerganJohn Lonergan 04/21/2020 at 12:310 Comments

What I need to make the verilog simulation of my (or any) CPU interesting is some I/O.

Many home brew CPU implementations include a UART of some kind. In my case I'm following the suggesiton of Warren Toomey (Crazy Small CPU) by using a UM245R.

I my other project the "Integrated Circuit Tester" I put together an interactive GUI and decided I wanted some interactive capability for my UART sim.

I've seen a couple of UART implementations in verilog but all I've seen is a static sim that is transmit only to the console with no receive function and no interactivity. I thought I'd see if I could advance this.

Scheme

The approach I've taken is to simulate the behaviour of the _RD/RW/_TXE/RXF interface of the UM245R and hook the implementation of the UART up to some bidirectional file IO. 

Using this approach I can send interactive commands at the UART to tell it to receive or transmit bytes and I can also see any bytes "transmitted" by the UART. The interactive "client", if you want to call that, in this case is a couple of Linux 'cat' commands each talking to a file. One to file "/tmp/uart.control" to send instructions to the UART and the other file "/tmp/uart.out" to collect the transmit output of the UART. 

The code is here https://github.com/portingle/spam-1/blob/master/verilog/uart/um245r.v


Commands

The currently supported instructions are...

- tXXX = Permit the UART to send a certain number of bytes where XXX represents the number. The _TXF line remains low until this number of bytes has been transmitted to the UART by the application code in the CPU by strobing the WR input.

- rSomeCharacters = Populate the receive buffer inside the UART with a collection of characters. Doing this drives _RXF low signalling to the application code in the CPU that the UART contains data to be received by the CPU. The CPU reads the data byte by byte by strobing _RD. And, _RXF  returns high once the CPU has consumed all the data.

- /some comment = This appears on the console in the simulator.

- #interval = Instructs the UART simulation to allow time to advance by this many clock intervals. The UART uses a nanosec timebase. So '#10000' means execute a 10000ns verilog delay.  See note below on timing.

= Instructs the UART to shut down the simulation. This is a call to verilog "$finish". I've found that If one is sending data to a verilog "$dumpfile" then that data isn't reliably available until the sim quits. So before visualising any timings I do a 'q'.

It's perhaps interesting to note that read of the control file currently the UART blocks the simulation. So time doesn't advance in the sim unless we send a comment to the UART to tell it to allow time to progress. This is the purpose of the '#' command. If we want the CPU to have an opportunity to consume or transmit data after a 't' or 'r' command then we need to tell the system to let time advance. I might also add a 'd' for 'detach' to let the sim freewheel.

Also, of note is that the file "/tmp/uart.control" can be used for replay purposes too if I want to rerun the same set of instructions.

Testing/Demoing

At the moment I have a test harness acting as the CPU around my UM245R component. The test harness is coded so that as soon as _RXF goes low it starts strobing _RD to read all the data, which it then dumps to the console. Likewise, if _TXE goes low then the test hardness is coded to start sending data by strobing _WR; at present it merely sends a sequential counter value to the UART  (0 to 255 and wrap-around).


So, for example a command sequence might be

/Starting test
rHelloWorld
#10000
/Ending test
q

 This sequence prints a comment in the sim console, puts "HelloWorld" into the UART receive buffer, tells the UART to let time advance by 10000ns, prints another comment to the console, then quits.

For a result like this from the test ...

VCD info: dumpfile dumpfile.vcd opened for output.
[      0ns] TEST: START
  50ns UART: reset end
[    150ns] opening uart.control
[    150ns] opening uart.out
[    150ns] fifos open
[    150ns] /Starting test
[    346ns] TEST: RECEIVED     Dec= 72 Bin=01001000 Hex=48 Char=H
[    632ns] TEST: RECEIVED     Dec=101 Bin=01100101 Hex=65 Char=e
[    918ns] TEST: RECEIVED     Dec=108 Bin=01101100 Hex=6c Char=l
[   1204ns] TEST: RECEIVED     Dec=108 Bin=01101100 Hex=6c Char=l
[   1490ns] TEST: RECEIVED     Dec=111 Bin=01101111 Hex=6f Char=o
[   1776ns] TEST: RECEIVED     Dec= 87 Bin=01010111 Hex=57 Char=W
[   2062ns] TEST: RECEIVED     Dec=111 Bin=01101111 Hex=6f Char=o
[   2348ns] TEST: RECEIVED     Dec=114 Bin=01110010 Hex=72 Char=r
[   2634ns] TEST: RECEIVED     Dec=108 Bin=01101100 Hex=6c Char=l
[   2920ns] TEST: RECEIVED     Dec=100 Bin=01100100 Hex=64 Char=d
[  10150ns] #10000 delay end
[  10150ns] /Ending test
[  10150ns] QUIT

(!) One thing to watch out for is NOT to take _RD and WR low at the same time. I don't know what the defined behaviour is for the hardware as I didn't get an proper answer from FTDI. In the case of RAM the write typically wins over a read so I was expecting something similar - but not documentedf.

Discussions