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:
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.
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
send→busy,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:
busy is 0. When send goes to 1, the FSM
moves into the WRITE state,
initializes the bit_index to
0 and
sets busy to 1.bit_index, and
increment bit_index. If bit_index == N,
then busy is set to 0 and the FSM
returns to the IDLE state.This process is visualized in the state transition graph shown below.
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, // main system clock
input rst, // main reset (active high)
// 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) begin
if (rst) 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
endmoduleS2P (receiver) ModuleUsing the P2S module as a
template, implement the S2P
module based on this state transition diagram:
P2S/S2P InterfaceOpen 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
endIn 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.
top ModuleNext, 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:
clkrst – map to btnCbtn_send – map to btnUbtn_ack – map to btnLpdata_in – map to sw[0]
through sw[7]pdata_out – map to led[0]
through led[7]busy – map to led[15]ready – map to led[14]Also create wires to interconnect your submodules:
wire cs;
wire sdata;
wire send;
wire ack;Use two instances of your debouncer module with these
connections:
debouncer instance named
db_send
btn to wire
btn_sendbtn_db to wire
senddebouncer instance named
db_ack
btn to wire
btn_ackbtn_db to wire
ackYou will need to copy your source code including debouncer.v and tcounter.v into this assignment’s
src/
subdirectory.
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:
8'b01101001;
press btnU (send) but NOT btnL (ack). I want to see the ready light.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 mainIndicate on Canvas that your assignment is done.