Serializer and Deserializer

Serial Data Communication

There are many applications where a lot of data needs to be communicated between digital subsystems, and we want to avoid using too many wires. This is the case with a data communication bus, wherein a single group of wires is shared among many modules or subsystems.

As an example, consider a serial-to-parallel (S2P) data interface, also called a “serializer”. In an S2P converter, some N-bit data needs to be communicated from a data producer to a data consumer, but only two wires are available for communicating. The two wires are called the channel select (cs) wire and the serial data (sdata) wire. The whole system is organized like this:

System model with data producer, P2S, two-wire channel, S2P, and consumer.

In this assignment, we will use a “dummy” producer that generates random data at random times. To coordinate with the P2S interface, the producer uses a handshaking protocol like the one we developed previously. When the P2S is not busy, the producer can raise a send signal to initiate communication. When the P2S initiates the serial transmission, it raises the busy signal.

At this point the producer lowers its send request and waits for the transmission to conclude. After all N bits are transmitted, the P2S lowers the busy signal, and the channel is free to start another transmission. The signal timing for this process is shown in the figure below.

Signal timing for serial channel interface.

In the timing diagram, the posedge for each clk cycle is highlighted with a faint read line. All registers are loaded at those instants (corresponding to <= assignments in the Verilog code). For N=8, a total of 11 clock cycles are needed for the transmission: 2 cycles for sendbusy,cs, then 8 cycles for the sdata, then one cycle to conclude busy,cs. The bits are transmitted one-at-a-time, LSB first, on the sdata wire. In the example above, two 8-bit sequences are transmitted: 8'b01010011 and 8'b00100111. These match the pdata hex values 8'h53 and 8'h27.

To coordinate these activities, the P2S module needs two states:

This process is visualized in the state transition graph shown below.

State transition diagram for the P2S module.

Using the localparam syntax, we define a P2S module as shown in the code below. The named state values, along with descriptive comments, make the code easier to understand:

module P2S
  #(
    parameter N=8
  )
  (
    input clk,    // master system clock
    input rst_l,  // master reset (active low)

    // Parallel data interface:
    input [N-1:0] pdata,
    input         send,
    output reg    busy,

    // Serial data interface:
    output reg cs,     // channel select
    output reg sdata,  // serial data
  );

  reg       state;
  integer   bit_index;

  localparam IDLE     = 0;
  localparam WRITE    = 1;

  always @(posedge clk, negedge rst_l) begin
     if (!rst_l) begin
        state <= IDLE;
    busy  <= 0;
    sdata <= 0;
    cs    <= 0;
     end
     else begin
       case (state)
         ///////////////////////////////////////
         IDLE: 
       begin
          bit_index <= 0;
          if (send) begin
            busy  <= 1;
        cs    <= 1;
        state <= WRITE;
          end
       end
     //////////////////////////////////////
     WRITE:
       begin
         // Write the next bit onto the serial channel:
         sdata     <= pdata[bit_index];

         // See if we've written all the bits:
         if (bit_index == N-1) begin
           cs    <= 0;
           state <= IDLE;
           busy  <= 0;
         end
         // Else increment the bit index and keep writing:
         else begin
           bit_index <= bit_index + 1;
         end
       end
     /////////////////////////////////////
       endcase
     end
  end
endmodule

Assigned Tasks

Implement the S2P (receiver) Module

Using the P2S module as a template, implement the S2P module based on this state transition diagram:

State transition diagram for the S2P module.

Simulate the P2S/S2P Interface

Open the files src/testbench.v, src/producer.v and src/consumer.v. Study them line-by-line to ensure you understand how they are being used. The producer uses a random delay timer to simulate a digital subsystem:

   reg [1:0] r;

   always @(posedge clk) begin
      r <= $random();

      if (r == 0) begin
         // random event goes here 
      end
   end

In this code fragment, r is declared as a 2-bit vector, giving it 4 possible values. At each clock cycle, it gets assigned a random value. The probability that r==0 is 1/4. On average, there should be a delay of 4 clock cycles between events, but it could be longer or shorter, because it’s random.

The producer also contains some simple conditional logic to do the handshaking with P2S. You may also notice that the producer uses the $strobe system task to output data to the console. The $strobe task is similar to the $display or $write tasks, except $strobe prints signal values after the nonblocking assignments are completed. In other words, $write prints register values just prior to the clock edge, and $strobe prints their values just after the clock edge.

The testbench itself has a typical organization. It instantiates the producer, consumer, P2S and S2P modules. At every clock cycle, it prints the bus and handshaking signals, and the S2P states. The output should look like this:

clk:   0 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index x]
clk:   1 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index x]
clk:   2 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   3 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   4 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   5 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   6 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   7 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   8 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:   9 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  10 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  11 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  12 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  13 send:  0 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
Sending c6(11000110)
clk:  14 send:  1 busy:  0 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  15 send:  1 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  16 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 0]
clk:  17 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 1]
clk:  18 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 2]
clk:  19 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 3]
clk:  20 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 4]
clk:  21 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 5]
clk:  22 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 6]
clk:  23 send:  0 busy:  1 cs:    0 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 7]
clk:  24 send:  0 busy:  0 cs:    0 sdata: 1 ready:  1 ack:  0 S2P[state 2, bit_index 7]
Received c6(11000110)
clk:  25 send:  0 busy:  0 cs:    0 sdata: 1 ready:  1 ack:  1 S2P[state 2, bit_index 7]
Sending 2d(00101101)
clk:  26 send:  1 busy:  0 cs:    0 sdata: 1 ready:  0 ack:  0 S2P[state 0, bit_index 7]
clk:  27 send:  1 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 0, bit_index 0]
clk:  28 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 0]
clk:  29 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 1]
clk:  30 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 2]
clk:  31 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 3]
clk:  32 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 4]
clk:  33 send:  0 busy:  1 cs:    1 sdata: 1 ready:  0 ack:  0 S2P[state 1, bit_index 5]
clk:  34 send:  0 busy:  1 cs:    1 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 6]
clk:  35 send:  0 busy:  1 cs:    0 sdata: 0 ready:  0 ack:  0 S2P[state 1, bit_index 7]
clk:  36 send:  0 busy:  0 cs:    0 sdata: 0 ready:  1 ack:  0 S2P[state 2, bit_index 7]
Received 2d(00101101)

Notice that the channel sits idle for several lines until a transmission starts, then the interface becomes active for 13 lines (11 clock cycles), then returns to its idle state. If the design is correct, the producer and consumer should report the same data values.

Create a top Module

Next, to prepare for a physical test, create a top module to interconnect the P2S, S2P, and debouncer modules. Copy the Basys3_Master.xdc template into a new constraint file named serial_interface.xdc.

The top module should have these ports and XDC assignments:

Also create wires to interconnect your submodules:

   wire     rst_l;
   wire     cs;
   wire     sdata;
   wire     send;
   wire     ack;

Use two instances of your debouncer module with these connections:

You will need to copy your source code including debouncer.v and tcounter.v into this assignment’s src/ subdirectory.

Implement, Program and Test

After creating the necessary top module and xdc assignments, and after copying the code for debouncer and tcounter into your src/ directory, run make implement to synthesize, implement and generate a bitstream.

Program your Basys3 board with the resulting bitstream file, and test the following cases:

  1. Set switches to 8'b01101001; press btnU (send) but NOT btnL (ack). I want to see the ready light.
  2. Set switches to 8'b10000111; press btnU (send) and then btnL (ack). The ready light should turn off.

If your design works properly, the busy light will flicker so fast that you won’t be able to see it.

Photograph the test cases and save the photos in the working directory as case1 and case2, with an appropriate graphics filename suffix.

Turn in your work using git:

git add case* src/*.v *.v *.rpt *.txt *.tcl *.bit *.xdc
git commit . -m "Complete"
git push origin master

Indicate on Canvas that your assignment is done.