The little Telnet block is humorous, but to make it much more interesting I want to wrap more useful functionality around it over time. So far I've only used it for sending data to the simulated design, but now I want to support sending data back as well.
A Virtual Device Project
I want to make a little simulated device to prove out the basic concepts of interacting between simulated hardware and running software. The device will be a little LED controller, with 2 lights and 2 switches; toggling the switches will toggle if the associated LED is on or off.
Via the socket interface, software can read the status of the lights and switches and can also toggle the lights. The software can't toggle the switches, since those are "physical".
I'll start off my modeling the basic hardware bits in Digital.
Before going too much further, I want to consider the communcation model and where this device is placed when integrated into a complete design. This device will be driven by a remote connection, so it'll act kind of a like server in a client-server model as far as the communication protocol is concerned.
The Telnet block in Digital reads and writes a byte at a time, but I'll only need a few bits for this protocol. For read requests I'll just send a 0
, and encode the switch and light states into 4 bits of the reply. For writes, I'll have 1 bit to indicate the write operation and the 2 other bits will be settings for the LEDs.
Now to the building!
Initial Bits and Bobs
The first change I'll make is to add some XOR
gates for the LEDs, and a second set of switches to use for some early testing.
With that in place, the bottom two switches can effecively override the top two switches. What I really want here is a 2-bit register to store these values, so I'll wire that up instead.
One more easy thing to add will be a status bus for reading the states of the switches and LEDs. I'll just merge the relevant wires onto a bus for that. I ultimately know this will be going to an 8-bit input to the Telnet block so I'll size it as such from the start.
Lastly, I'll slap my Telnet block into the design and loosely wire it. That'll be the next area of focus.
That takes care of the easy bits.
Now the Tricky Part
How I would wire up the rest of this design was not obvious to me. I thought I'd first try an approach that felt like it would simple if it worked... though I had strong doubts that it would work at all. I learn the most by breaking stuff so we'll call this an experiment!
It starts off looking well enough, until the hardware version of undefined behavior (high impedance) says otherwise. The problem is that the D
and en
inputs to the register are not being driven high or low, though on each clock cycle the register still does its best to use those garbage inputs.
The first little change I'll make to try to fix this is to use an AND
gate for the register's en
input. This will trigger the storage of the two register bits when out[0]
and av
from the telnet block are 1
.
I believe this circuit is going to have some timing issues because the register and telnet blocks are both triggering at the same rising edge of Clk
. I'll test this out to see what happens with a small Python script.
#!/usr/bin/env python3
from socket import create_connection
import time
ADDRESS = ('127.0.0.1', 1337)
socket = create_connection(ADDRESS)
socket.sendall(b"\x01")
time.sleep(1)
socket.sendall(b"\x03")
time.sleep(1)
socket.sendall(b"\x05")
time.sleep(1)
socket.sendall(b"\x07")
This will send 4 operations that save to the 2 bit register. The binary form of these operations is something like ?????ABW
where W
is the bit that indicates a set operation, A
and B
are the values to save and the ?
bits are ignored. This little script cycles through all 4 states.
I was a little surprised this looked stable. My thought was that on the rising edge of Clk
the telnet block would check its internal status and then set av
if incoming data was available. If that was the case, what would the rd
input be when Clk
rises? Not knowing why something doesn't work can be a pain, but not knowing why it does work is a little more frustrating!
Then I noticed the av
output may actually be asynchronous, which would explain the stability. I thought I'd slow that down and inspect it a bit more.
Ah! That confirms it. For the sake of science though... what if I made it synchronous? I'll add an AND
gate to test and see!
Oh my, that is entirely broken. Good to know!
On the rising edge of Clk
, the telnet block and the AND
gate below it recieve this transition at approximately the same time. The AND
gate gets it's true condition and shifts its output to a 1
, but the telnet block also reads rd
as the clock transition happened which is probably before the AND
gate had time to update its output. The tiny delay is just enough guarantee the telnet block is never read from and breaks everything.
I'll remove that AND
gate, it was definitely better before that.
Works On My Box
This is probably something worth fixing, or at least investigating more, for a real project. In this case I am not feeling too worried about it. If av
goes high before or after Clk
there will be no issues. When I manually caused a timing issue this prevented a read, but in a design where av
is allowed to stay synchronous that would simply add a minor delay.
At the end of the day, I'm using the telnet block so this design can only really ever run in Digital. As a single threaded software application, I'd bet the behavior is certainly deterministic enough for my silly needs.
I'm on to finishing up some final bits of this project to handle read commands as well. I'll just need to trigger the wr
input when av
is set and out[0]
is not.
I think that may do it, but I'll need a bit more Python for testing.
In this case, I'm going to make a little script that I'll include into my terminal to use as a cheap terminal interface and use via ipython
. It'll just define a few lazily written functions to perform the two types of interactions.
#!/usr/bin/env python
from socket import create_connection
import time
ADDRESS = ('127.0.0.1', 1337)
socket = create_connection(ADDRESS)
def set_lights(light1, light2):
if light1:
if light2:
socket.sendall(b"\x07")
else:
socket.sendall(b"\x03")
else:
if light2:
socket.sendall(b"\x05")
else:
socket.sendall(b"\x01")
def read_status():
socket.sendall(b"\x00")
x = socket.recv(1)[0]
print("{:04b}".format(x))
Now for the fun part, does it work!?
Eh... almost! There were some crossed wires.
These were quick enough to fix, I apparently tied the same LED to two bits of my read bus and the bit I thought was my second switch was actually the output of my first register bit. Once I spotted those though, everything was pretty simple to fix and worked like a charm!
There are certainly a variety of ways to improve this design, but I'm pretty content with where it's at for this post.
See you in the next one!