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, // 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;0;
busy <= 0;
sdata <= 0;
cs <= end
else begin
case (state)
///////////////////////////////////////
IDLE:
begin
0;
bit_index <= if (send) begin
1;
busy <= 1;
cs <=
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
0;
cs <=
state <= IDLE;0;
busy <= end
// Else increment the bit index and keep writing:
else begin
1;
bit_index <= bit_index + end
end
/////////////////////////////////////
endcase
end
end
endmodule
S2P
(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
$random();
r <=
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.
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:
clk
rst
– map to btnC
btn_send
– map to btnU
btn_ack
– map to btnL
pdata_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 rst_l;
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_send
btn_db
to wire send
debouncer
instance named db_ack
btn
to wire btn_ack
btn_db
to wire ack
You 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 master
Indicate on Canvas that your assignment is done.