EECS 270 Winter 2017, Lecture 22 Page 1 of 8

Killing Two Birds with One Stone:

Learning to Write Better Verilog While Learning About the SPI Bus.

So today we are going to look at better ways to write Verilog while doing so in the context of learning about the Serial Peripheral Interface (SPI) bus. We’ll start out by looking at some Verilog coding examples, introduce the SPI bus, and then start working on coding up an SPI master.

Verilog Example #1

We’ll look at some good and bad examples of Verilog code. It turns out it is a lot easier to find bad examples of Verilog code. Let’s start with something simple a 3 to 8 decoder.

//------

2// Design Name : decoder_using_case

3// File Name : decoder_using_case.v

4// Function : decoder using case

5// Coder : Deepak Kumar Tala (minor changes by Mark Brehob)

6//------

7module decoder_using_case (

8 binary_in , // 3-bit binary input

9 decoder_out , // 8-bit out

10 enable // Enable for the decoder

11 );

12input [3:0] binary_in ;

13input enable ;

14output [7:0] decoder_out ;

15

16reg [7:0] decoder_out ;

17

18always@ (enable or binary_in)

19begin

20 decoder_out = 0;

21 if (enable) begin

22 case (binary_in)

23 3'h0 : decoder_out = 8'h01;

24 3'h1 : decoder_out = 8'h02;

25 3'h2 : decoder_out = 8'h04;

26 3'h3 : decoder_out = 8'h08;

27 3'h4 : decoder_out = 8'h10;

28 3'h5 : decoder_out = 8'h20;

29 3'h6 : decoder_out = 8'h40;

30 3'h7 : decoder_out = 8'h80;

31

32 endcase

33 end

34end

35

36endmodule

Why is it hard to write Verilog?

There are a lot of reasons why writing Verilog code is harder than writing C code. These reasons include:

  • Verilog is an HDL, not a programming language. Among other things, that means that everything is happening at the same time.
  • The language has been changing at a fairly rapid rate and “good” style has changed as Verilog improves. This makes it hard to find good examples since a lot of examples that were “good” are now less-than-stellar since there is a better way to do things.
  • Frankly, this stuff is done by hardware people. And many of them don’t know what it means to write good code in C or anything else.
  • In addition to trying to make the code “pretty” (readable and maintainable) you’ve got to worry about a whole bunch of legal syntax that can ruin your design.

All this together means that it’s very hard to learn to write good Verilog. And there just aren’t a lot of good references.

Verilog Examples #2 and #3

1//------

2// Design Name : decoder_using_assign

3// File Name : decoder_using_assign.v

4// Function : decoder using assign

5// Coder : Deepak Kumar Tala (Minor changes by Mark Brehob)

6//------

7module decoder_using_assign (

8 binary_in , // 3-bit binary input

9 decoder_out , // 8-bit out

10 enable // Enable for the decoder

11 );

12input [2:0] binary_in ;

13input enable ;

14output [7:0] decoder_out ;

15

16wire [7:0] decoder_out ;

17

18assign decoder_out = (enable) ? (1 < binary_in) : 8'b0 ;

19

20endmodule

1//------

2// Design Name: 3 to 8 decoder

3// Coder: Deepak Kumar Tala (changes by Mark Brehob)

4//------

5module decoder_using_assign (

6 input[2:0]binary_in,

7 inputenable,

8 output[7:0]decoder_out

9 );

10

11assign decoder_out = (enable) ? (1 < binary_in) : 16'b0 ;

12

13endmodule

Module instantiation

Let’s briefly look at module instantiation for our decoder. There are two ways to do module instantiation in Verilog: by order and by name. We’ll do this for the last example from above.

Option 1:

  • decoder_using_assign inst1(in, enable, out);

Option 2a:

  • decoder_using_assign inst1(.binary_in(.in), .enable(enable),
    .decoder_out(out));

Option 2b:

  • decoder_using_assign inst1(
    .binary_in(in ), //input [2:0]
    .enable(enable ), //input
    .decoder_out(out ) //output [7:0]

);

In general, 2b is by far the best way to go. We generally prefer 2 over 1 because using names is just generally safer (it’s common to just add a parameter to a module and lose track of the order you added it.

Group exercise

Write a module named “sync_and_edges” which takes a single bit of input and A) uses two flip-flops to synchronize the data and B) outputs a single-cycle pulse for “rising edge” and “falling edge”. Draw a diagram (using flipflops and AND gates) then write the Verilog.

1modulesync_and_edges (

2 input clk,

3inputasync_in,

4output sync_out,

5output rising_edge,,

6output falling_edge

7 );

8reg [2:0]tmp;

9

10always@(posreg clk)

11tmp <= {tmp[1:0],async_in};

12

13assign rising_edge=tmp[2:1]==2’b01;

14assign falling_edge=tmp[2:1]==2’b10;

15assign sync_cout=tmp[1];

16 endmodule

We easily could have used an always @* block for the 3 outputs. If we’d done so, they would have been declared as “output reg” instead of just “output”. Also notice that we are using the non-blocking assignment in the always block (<= rather than just =). We also could have used a variable for each flip-flop.

always@(posreg clk) begin

tmp[2] <= tmp[1];

tmp[1] <= tmp[0];

tmp[0] <= async_in;

end

Question: in this case will it work if we write those lines in the always block in (say) the exact opposite order?

An introduction to SPI and interfacing options

As we’ve seen in class, there are a number of ways to connect a peripheral to a controller. We’ve seen that our distance sensor (in lab) speaks 3 different protocols: UART serial, PWM, and analog values (see lecture 19). Most devices don’t support so many interfacing options, but most do support some standard. SPI (pronounced either “spy” or just by saying the three letters) is probably the most common standard peripheral interface.

Interface standards

Let’s say you’ve finished lab 7 and later on you wanted to interface to a temperature sensor using our FPGA for a side project. If you found a sensor that used PWM to send the temperature (say the signal is high once/second for a number of ms equal to 5 times the temperature in degrees) and other that used UART. Which would you buy? Assumedly you’d pick the PWM one because A) you understand it and B) it should be fairly trivial to modify your Verilog to interface with it (what would you have to change?) And that’s pretty typical—people prefer to work with things that are similar to what they’ve done in the past.

As such, a few standard communication schemes exist. Now, not every device uses one of these, but most do. They include:

  • PWM: Generally only useful for fairly slow data rates (you are basically using time as your communication medium). But pretty simple and easy to work with.
  • Analog: Converting to and from digital values and analog values is a fairly significant undertaking. That said, most embedded processors and some FPGAs have analog inputs built in. Analog signals are also often very sensitive to noise (if a digital signal is supposed to be 5V and it’s 4.6V, it will be treated as 5V. If an analog value gets 0.4V error, that could be a significant problem).
  • UART: This used to be standard on all PCs, but isn’t any more. This protocol is old and has a lot of variations (including adding a clock, adding a “clear to send” signal and other signals) etc. The biggest advantage to using this today is that most everyone in embedded systems is familiar with it. Plus, no need for a clock!
  • USB: Very fast and a commonly found on PCs, so really useful if you want to talk to a PC. But also expensive and very complex. There are standard chips out there that convert from USB to UART. That greatly reduces the complexity but generally hurts your speed.
  • SPI: Simple, fairly fast, very common. Uses 4 wires, which can be more than the others (each of which can use just one).
  • I2C: A bit more complex, has some electrical issues, tends to be slower than SPI. But it sends messages with an address so that it can be connected to a lot of devices at the same time but it is only “heard” by the device it is targeting. Main advantage over SPI is a lower pin count. Much lower if there are a lot of devices.

SPI

An SPI bus consists of two (or more, but for now just two) devices connected by four wires. There is the master (which initiates all transactions and supplies the clock) and the slave (which just listens and responds). One interesting thing is that SPI is “full duplex”. That is, data moves both ways at the same time.

In particular, when the master wants to read or write, it just sends data and gets data. If it only wanted to read, it just sends junk (and the slave assumedly ignores the data). If it only wants to write, it ignores the data sent to it.

Another interesting thing is that the clock is only active (clocking) during a transaction. Otherwise it’s low.

The four wires are:

  • SCLK: the clock. Again, the master drives it and it only is active if there is a transaction in progress.
  • MOSI: Master Out, Slave In. Basically data going from the master to the slave.
  • MISO: Master In, Slave Out. Data from the slave to the master.
  • SS#: Tells the device a transaction directed to it is in progress.

So say we wanted to read from a distance sensor and that distance sensor indicated that it saw something 10 inches away. When a transaction occurs it will shift a “10” back (assuming it responds in inches). It will also be sent data, which it will likely just ignore.

Creating an SPI slave in Verilog

Let’s think about what we need to do. Assume the slave wants to both receive and send data. So let’s create a module. How does the below seem? (We are assuming this module is passed what to send and passes out the received data.)

1moduleSPI_slave (

2 input clk,

3inputSCK,

4inputMOSI,

5inputSS_n,

6output MISO,

7output[7:0]data_to_send,

8output[7:0]data_receieved,

9output byte_valid

10 );