Overview
It's time to design the subsystem that ties the whole device together: the FSM (finite state machine). The FSM for SPI Master will control the data register's communication with peripherals: starting, stopping, and sychronizing the shifts between orchestrator and peripheral. We can define our FSM very informally:
When the RUNNING signal isn't asserted, we're idle. When RUNNING is asserted, we move to the shifting phase. We shift 8 times on the positive edge of SCLK after which TX_COMPLETE is asserted and we move back to idle. We'll go into more detail in the design section.
Design Considerations
Here are the constraints our FSM must conform to:
- it can be moved to the RUNNING state
- it signals to the data register to shift exactly 8 times
- it signals that transmission is complete and returns to idle state
Design
The first part of our design is reasonably straightforward: we need to be able to set and unset a running state. In chapter 1 designing the control register, I said we'd address the unused half of our 74HC74 in a later post. It's time to put this piece to use.
The idea is pretty simple here: when a write is done to the data register, RUNNING will get set. When transfer is complete, RUNNING will get reset. In this design, we are not qualifying setting or resetting with the clock, we are taking advantage of the asynchronous pins of the register. Will this come back to haunt us?
Before we can discuss the next part of the design, we need to dig into more detail on SPI as a protocol. In the project overview, we said this device was only going to support CPOL = 0 and CPHA = 0. What does this mean?
CPOL, Clock POLarity, describes what state the clock is in when idle. When CPOL is 0, clock will idle low. CPHA, Clock PHAse, describes when values are shifted and when values are sampled. When CPHA is 0, it means values are sampled on the positive edge of SCLK and values are shifted on the negative edge. To help illustrate this, here is some SPI traffic captured between an AVR and an SD card:
You can see at the bottom, SCLK idles on zero. It's hard to tell when values are sampled here but it can be seen that bit values are prepared on the negative edge. It can also be seen that bits are transferred in chunks of 8, a byte.
Now we can design the timing module. To start, we need a way to count to 8 and we need this to be final on the negative edge. In a previous chapter, we used a 74HC393 to divide the clock by powers of 2, but we can also use one to count up, that's it's primary function! When simulating it appears like we get what we want:
When cleared, after 8 clock cycles, we assert a transfer complete signal. So far so good. But what about when the device is first powered on? What about when the transfer is complete? There is no real CLR logic here. The original design looked like:
I'm not going to discuss this much because it's not the final design, but the idea was that either asserting /RES (active-low) or asserting TX_COMPLETE would reset the device.
One major problem with this, though, is that TX_COMPLETE is a one-time event. But go back to our FSM diagram. The diagram there shows /RUNNING as a valid (and let's say potentially continuous) state. As long as we're not running, we shouldn't be counting. We also already establish storage for RUNNING, so let's use that instead:
This is looking closer now. There's another major problem, however. Recall that values are sampled on a positive edge, and prepared on a negative edge. This means that our first bit actually needs to be prepared and valid before the first positive edge. This creates a complex and perhaps hard to predict race condition. Consider:
- RUNNING is set on a write of the data register
- the '165 shift register in the data register immediately puts the MSB of its contents on the serial output line
- the '393 immediately starts counting when RUNNING is set
In this model, we are trusting that the '165 will always puts its contents out before the first negative ('393 is clocked on the negative edge!) edge of the '393's clock. It might be a valid assumption, it might not, but it's difficult to model and test. So is there a better way?
There is but it comes with a cost. This idea (but not exact implementation) was inspired by the CPLD code from Daryl Rictor's 65SPIv2. For that project, he uses a 17 state FSM: Idle and 16 clocks. In this sense, we have currently attempted a 9 state FSM: Idle and 8 clocks. Let's change to a 17 state FSM and see why might it be better.
The first change we'll do is to make the SPI clock a product of the state machine, not an input! In the above design, the FSM takes the SPI clock in as an input as does the data register. This can create those race conditions we just talked about. By shifting the responsibility of generating the SPI clock to the FSM, we can have more control over it. This comes at the cost of the SPI clock being at best half the speed (depends on which tap from the '393 we use) of the incoming clock. We can make a change to our clock select module later to address this.
The second change we'll make is to use both counters in a '393 to get to 16 states. One counter in a '393 only counts to 15 then resets to zero, but we need to know when it rolls over to differentiate between reset/idle state and completed state. This comes at the cost of a 2nd '393 chip in the overall design (we still need one counter for our clock select circuit.)
We can see a simulation of the new design:
Here, when RUNNING and CLK are being asserted at the same time, the SPI clock doesn't happen until the negative edge of the first clock. This gives the '165 plenty of time to assert the first bit on its output before the SPI clock goes positive. As counterpoint, here's the old design when RUNNING and CLK are asserted at the same time:
In this design, the '165 shares the clock with the FSM so we can't be sure what went out.
These simulations have exposed another flaw in our data register. Recall that the next bit to be shifted is supposed to prepared on the next negative edge. Currently, the '165 is shifting on the positive edge of the clock! The simple fix here is to negate the SPI clock for the '165:
This takes advantage of the fact that the '165 only shifts on a negative edge as opposed to level. If it shifted on level, it would shift while idle because CPOL = 0. Instead, we see:
- CLK is low while idle but is negated so appears as a high level to the '165 (no shift)
- '165 is loaded with data and QH reflects MSB on load (no shift)
- CLK goes high causing the '165 to see a negative edge (no shift). Meanwhile, the peripheral just sampled QH because saw a positive edge on the SPI clock
- CLK goes low causing a positive edge when negated to the '165 (yes shift!)
And so on each period of the SPI clock, this design has the proper bit prepared for the positive edge, and shifts to the next bit on the negative edge of the clock. The FSM has complete control of the clock and stopping the shifting sequence.
There are more issues with our data register but at least the FSM is done.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.