SPI Transmitter

Good Old SPI

The questions below are due on Friday September 19, 2025; 04:59:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

In this section, you're going to write a data serializer that takes in a certain number of bits in parallel (the "data") at once, and then transmits them out, one bit at a time for downstream consumption. If you took 6.205 in 2023 or 2024, you basically already wrote this, albeit with some minor updates, so this should be a review. There's a couple changes, so you should still read. This module will implement a variant of the Serial Peripheral Interface (SPI) protocol, a de-facto standard for synchronous, serial communication between modules. You'll often find it used for communication between different chips or devices. It is one of the easier protocols to get going since it uses several wires for different purposes making synchronization easier.

The module, called spi_tx will look like the following:

The spi_tx module. Arrows (and names) indicate the direction of data into and out of this device. Signals on the left side are intended for internal-to-the-FPGA controls. Signals on the right will interface with external devices over SPI.

The module is effectively a Transmit-Only implementation of a particular Serial Peripheral Interface mode. If you forget about SPI, you're welcome to refer back to the slightly more in-depth discussion of it on the 6.205 Lab page here. Keep in mind, on that page, students are building a slightly more general SPI controller. In our class, for this week, we're going to control a display, and the display (the peripheral) has nothing to say back to the Zynq (the Controller), so there's no need for the Controller-In-Peripheral-Out (CIPO) data line here.

It should have the following two standard "utilty" inputs. These control its overall operation and give it its heartbeat/life:

  • clk: The system clock for the system
  • rst: The reset signal for the system (assume from a clk synchronized system)

The module should have two parameters:

  • DATA_WIDTH: that specifies how many bits the message to be transmitted is. Default value should be 8.
  • DATA_CLK_PERIOD: that specifies how long (in clk clock cycles), each bit of data should be on the transmission line while being sent. Default value should be 100. On a 100 MHz system, this corresponds to 1 microsecond per bit or a data rate of 1 Mbps.

In addition, the module should have the following two data-oriented inputs:

  • [DATA_WIDTH-1:0] data_in: The data to be serialized out. This data must be in place when the trigger is pulled high.
  • trigger: A signal that will trigger the transmission of the serialized data. When high, if the module is not already transmitting data, the module should grab the data in data_in, and start to transmit it beginning with the Most Significant Bit and going downwards.
  • busy: A signal used to indicate the module is transmitting. Hi when transmitting, low when not.

The module must have three output ports (all one bit) that together comprise the serial communication "channel". They are:

  • copi: (standing for Controller Out Peripheral In) that contains the actual data being sent. During transmission, each bit (starting with the msb) will exist on this line for approximately DATA_CLK_PERIOD clock cycles. Read the dclk pin information carefully for caveats about this timing.
  • dclk: a 50% duty cycle synchronization signal. We call it a "clock" but it shouldn't be used the same as your system clock. No Flip-Flops should be clocked off of this signal. Instead what it does is provide the receiving party downstream a signal to know when to sample the values on the copi line. In particular, when dclk transitions from low to high, that is when the receiving party should sample the value this module has placed on copi. In order to ensure that data is maximally stable around this sample point, new bits of data should be placed on copi when dclk falls from high to low. The duty cycle of this signal must be exactly 50%. Any deviation (even to 49%) will result in additional harmonic distortion noise from the clock line which can impede the reading of data. Consequently, the module must find the closest lower even-count period for a given DATA_CLK_PERIOD. This sounds confusing, but shouldn't be. Here's an example:
    • If DATA_CLK_PERIOD is specified to be 42, the module can implement the data period as requested (high for 21 clock cycles, and low for 21 clock cycles).
    • If DATA_CLK_PERIOD is specified to be 39, the module must actually implement a data period of 38, so that it can cleanly have its signal on for 19 clock cycles and off for 19 clock cycles.
  • cs: A signal that normally sits high, but is dropped low before the start of transmission of data and is brought up after the completion of the transmission of data.

All together a skeleton of the module is shown below:

`timescale 1ns / 1ps
`default_nettype none // prevents system from inferring an undeclared logic (good practice)
module spi_tx
       #(   parameter DATA_WIDTH = 8,
            parameter DATA_CLK_PERIOD = 100
        )
        ( input wire clk,
          input wire rst,
          input wire [DATA_WIDTH-1:0] data_in,
          input wire trigger,
          output logic busy,
          output logic copi, //Controller Out Peripheral In
          output logic dclk, //Data Clock
          output logic cs //Chip Select
        );
  // your code here
endmodule
`default_nettype wire // prevents system from inferring an undeclared logic (good practice)

An example of the signals generated by this module is shown below starting in an idle state.

An example timing diagram for a system instantiated with DATA_WIDTH=16 and DATA_CLK_PERIOD=4. Note the dashed boxes are "Don't Care" regions where the value isn't specified, and which means you can do whatever there.

A few edge-cases that might jump out at you:

  • Once the module is triggered to start transmitting it should continue to do so until either complete or the system is reset (via rst).
  • If the module is triggered while it is still transmitting a previous message, it should ignore this trigger.
  • The module should grab the data off of data_in at the time of triggering and store it internally. Avoid assuming that data stays on the inputs after a trigger.
  • The module must pull cs low prior to sending the data and must pull it high after the completion of sending data. There is a little bit of flexibilty on the timing for that, but do not start or finish while data is being sent (as dictated by the clock edges).

Once you have what you think is maybe working, upload it below. We'll actually focus on trying to test this on the next page, so tbh your module doesn't need to be "working" for this upload...consider this just your first pass at it.

You have a SPI TX module written. You haven't verified if it is good yet. That's ok. You can upload what you have here.
 No file selected