In this section we address a number of additional topics to consider while developing a digital communication protocol using LabVIEW FPGA.
Open-Collector/Open-Drain Signal Lines
In the previous SPI examples each signal line is only driven by a single device. However, in many protocols signal lines may be driven or controlled by more than one device depending on the state of the bus or communication. This allows more than device to initiate transmission on the bus or to use the same data line to send and receive data. Typically this is achieved using an open-collector/open-drain circuit. In this configuration a device can only drive or pull a signal line low, but will let the signal line float when it wants to set the line high or not drive the line. In addition to each device that is connected to a signal line, the signal line also has a pull-up resistor to a set voltage to establish the high voltage of the line if none of the connected device are driving the signal low. Using this setup any device can drive the line low without creating a voltage contention between different devices. Such signals are typically defined to be active-low meaning that the line is in a high state when the bus is idle and a device will assert the signal by pulling the line low. Active-low signals are often indicated by a bar across the signal name or an asterisk after the signal name as in the case of the ChipSelect* (SPI CS*) signal used in the earlier examples.
To implement an open-collector signal in LabVIEW FPGA we use the ability to control the direction of the digital line to switch between driving a line low and letting it float. In LabVIEW FPGA the I/O Method Node is used to enable or disable a digital output line. While a line is disabled the FPGA does not drive the digital line and allows it to float high. To drive the line low when it is enabled, we set the Output Data to False. Output Data is a software register which maintains its value regardless whether the line is set to drive or float the line.
The following figures show an example of an open-collector configuration in LabVIEW FPGA for the I2C (Inter-Integrated Circuit) protocol. I2C is used in similar applications as SPI to communicate with different types of integrated circuits such as EEPROMs, ADC, DAC, etc. The I2C bus only has two signal lines, clock and data, and each is an open-collector line. To select the proper receiver for a communication the sender first transmits a unique device address which specifies the receiving device.
To initialize the VI for open-collector communication the two signal lines (SCL and SDA) are disabled to put the bus into the idle state. The Output Data for each signal is set to False. From this point on, the state of each line is controlled using the Set Output Enable method to enable the line and drive it low or to disable it and let it float high.
Figure 9: Configure two digital lines for open-collector communication
To initiate a transmission on the I2C bus, the sender transmits a start condition by driving the data line (SDA) low followed by driving the clock line (SCL) low.
Figure 10: Implement the I2C Start condition on the bus
To transmit a data bit on the I2C bus (see figure 11), the sender updates the data line (1st frame), and then toggles the clock line high (2nd frame) and low (4th frame).
In the I2C protocol after each data byte (8 bits) is transferred, an extra clock cycle is inserted into the communication to allow the receiving device to acknowledge the successful reception of the previous byte. For this purpose the receiver pulls the data line low during the 9th clock cycle. On the sender side in our example this is implemented by sending a high data bit and checking the actual state of the data line (3rd frame) during the clock cycle. Even though the sender is letting the data line float high, the actual data value should be low as the receiver is pulling the data line low to acknowledge the last data byte.
Figure 11: Transmit a data bit on the I2C bus
OSI Reference Model
The OSI (Open Systems Interconnection) reference model is a representation of the different logical layers of a communication protocol including the application that is using the communication protocol. It is used to better define and understand the different aspects of a protocol and network.
Figure 12: OSI Reference Model
Layer 1 is the physical layer dealing with electrical and mechanical details providing the ability to send data across a carrier. Simple asynchronous packets operate at this level.
Layer 2 provides basic definition of bit and byte level data as well as modulation beyond NRZ and synchronization.
The next couple of layers deal with addressing, definition of data packets, error checking, etc.
As we can see from our examples the FPGA operates at the L1 Physical Layer interfacing directly to the each of the digital input and output lines at the electrical level. This means that we, as developers of a digital communication protocol in LabVIEW FPGA, are responsible for all layers of the reference model in regards to the protocol and application. For many simpler protocols this will only require a bit more programming than what we have done so far, to add an interface on top of the protocol layer to allow the application to interact with the FPGA and use the protocol. However, for more advanced protocols we may need significantly more programming to provide these intermediate protocol layers, such as bus level error detection and handling, packet building and parsing, handling different types of packets, etc.
Timing and Resolution
As we have seen, timing plays a very important role in developing a protocol implementation. Data and clock lines need to be updated at precise intervals. While decoding a protocol it is important to accurately measure these same intervals. It is important to keep the timing behaviour of the FPGA in mind when we develop communication protocols to ensure that our implementation will meet the requirements and specifications of the protocol.
The FPGA operates on a base clock frequency and all timing functions are based on the same clock rate. For LabVIEW FPGA the default FPGA clock frequency is 40 MHz, so that each clock cycle and unit of time is 25 nanoseconds (ns). In LabVIEW FPGA we can specify time intervals for the timing functions in units of milliseconds, microseconds, and ticks. Each tick corresponds to one clock cycle or 25 ns. This means the highest resolution we have for specifying any delay or loop iteration time is 25 ns. While this sounds very accurate, when we take the inverse and determine the possible frequencies that we can generate we see the effect it has at higher rates.
To generate an update rate of 1 MHz for a digital line we can use a Loop Timer function set for 40 ticks.
1 MHz => 1 us = 1000 ns
1000 ns / 25 ns per tick = 40 ticks
What if we want an update rate of 1.25 MHz?
1.25 MHz =>0.8 us = 800 ns
800 ns / 25 ns per tick = 32 ticks
For 1.25 MHz we use a delay of 32 ticks. What if we want an update rate of 1.1 MHz?
1.1 MHz => 0.9091 us = 909.1 ns
909.1 ns / 25 ns per tick = 36.36 ticks
Since we can not choose specific partial clock cycles we would have to choose 36 clock cycles which is a delay of 900 ns and corresponds to a frequency of 1.111 MHz. The next lower frequency we could use is 37 ticks or 1.081 MHz. So we see that using the 40 MHz FPGA clock we can update the digital lines at 1.081 MHz or 1.111 MHz, but not in between.
Using the FPGA properties and clock resources in the LabVIEW project we have the option of changing the FPGA base clock frequency to 80 or 120 MHz. This will operate the FPGA at a higher frequency and improve the timing resolution of the timing functions. However, the higher clock frequency will also reduce the amount of complexity that can be used in the FPGA VI diagram and compiled successfully for the FPGA.
When planning to implement a timed protocol you need to determine if the timing resolution of the FPGA is adequate for the needs of the protocol and application. This is especially important for the timing required to generate a protocol as the previous calculations have shown. For reading and decoding protocols it is often adequate to be able to read the signal lines at a higher rate than the highest update rate of the protocol. We should be able to sample the signals at least twice as fast as the update rate to be safe.
If we need to make time measurements on an input signal to decode the data contained in the protocol then we need to make additional calculations to determine the timing resolution required to accurately decode the protocol.
State machines can be a useful technique to implement the encoding or decoding of a digital communication protocol. In LabVIEW state machines can be easily implemented using the While loop and a Case structure to represent each of the different states.
The state machine helps in the development of communication protocols as it naturally breaks down the timing diagram into separate steps and we can convert each into a separate state in the LabVIEW FPGA diagram. This isolates each program step and reduces the programming complexity in any one part of the implementation. In addition it simplifies generic operations such as error and exception handling by allowing us to jump from anywhere in the protocol execution to special error handling states. Sometimes protocol specifications are written in term of a state machine, which you can then directly translate into a LabVIEW state diagram.
For the SPI timing diagram in figure 1 the following steps are a possible way to break down the timing diagram into a state machine.
- Set ChipSelect low
- Set Data (0)
- Set Clock high
- Set Clock low
- Set Data (1)
- Set Clock high
- Set Clock low
- (repeat Data and Clock for bits 2-15)
- Set ChipSelect high
We notice that there are five unique steps, though some of them are repeated for each data bit. Originally we implemented these repeating steps in a For loop. In the state machine we create a unique state for each of these five steps and then cycle through them. For the three repeating steps that update data line and toggle the clock line, we configure the state machine to repeat this sequence 16 times before proceeding to the last step. In the LabVIEW state machine this is implemented using a counter in a shift register of the While loop.
Use of the Single Cycle Timed Loop
Another advantage of the state machine architecture is that it allows us to implement a protocol implementation inside a LabVIEW FPGA Single Cycle Timed Loop (SCTL). The SCTL provides faster execution of the LV FPGA diagram, allowing each cycle of the loop to execute in one clock cycle. This enables us to update a signal line at the FPGA base clock frequency. The SCTL also optimizes the code generation so that the code on the FPGA is more efficient and uses less FPGA real estate. However, there are several restrictions on the code implemented inside of a SCTL. For example we can only access each of the signal lines one time per SCTL iteration. Therefore we need to define the states of our state machine such that we only read or update each signal lines once per state.
The following diagrams (figure 13) show the implementation of the SPI output protocol using a state machine inside of a SCTL. The Idle state waits for the Write command to start the output of the next data packet. It also converts the data value into a Boolean array for the output operation. The next step Set CS asserts the ChipSelect line. Then we start outputting the data bits. In Reset Clock we update the data line with the next bit value and reset the clock signal. In Set Clock we set the clock signal which triggers the receiver to read the data signal. In this pair of states we increment a counter in a shift register, which selects the proper data value from the Boolean array to send to the data line. After we output the 16th bit, we transition into the Reset CS state which resets all of the signal lines into the bus idle state. From there we go back to the Idle state and wait for the next Write command.
Figure 13: Implementation of SPI Output in a Single Cycle Timed Loop