Electrical – Verilog SPI module functioning in unpredicted ways

intel-fpgaquartussimulationspiverilog

I am currently trying to implement a simple SPI Master module in Verilog using Quartus Prime Lite V15.1.0 Build 185 for compilation and Simulation Waveform Editor as my simulation tool. The module has been designed to ideally work in such a way that you load an 8-bit value onto a bus, toggle a 'transmit' bit telling the module to send out the value, and finally the module replies by toggling a 'done' bit to notify the host module that the byte was sent out. The SPI module doesn't control CS lines, as it is assumed these will be controlled by the master module.

//CPOL 0, CPHA 0 RIC60 SPI Master module

module SPI (
output reg MOSI,                        // Map to external I/O pin that will act as MOSI for the spi interface
output wire SCLK,                   // Map to external I/O pin that will act as SCLK for the spi interface
output wire DATAEN,                 // Map to external I/O pin that will act as DATAEN for the spi interface

input wire [7:0] inData,// Data to be sent over SPI

input wire CLK,                     // System clock 
input wire datCLK,                  // Clock to be used for SPI
input wire transmit,            // Trigger for when to send out SPI command
output reg done,                        // signal to trigger finished SPI comm
input wire reset);                  // System reset input


    reg _moduleActive, _moduleActiveQue; 
    reg [7:0] _inData_INT;
    reg [7:0] _countDown;
    wire _shiftData;

    reg _DCLK2, _DCLK3;
    wire _DCLK_FALL, _DCLK_RISE;

    reg _Transmit2, _Transmit3;
    wire _Transmit_FALL, _Transmit_RISE;

//Initialize
    initial begin
        _moduleActive = 0;
        _moduleActiveQue = 0;
        //_countDown[7:0] = 8'b01001100;
        MOSI = 0;
    end

//SPI Clock catch edges
    always@(posedge CLK)
   begin
      _DCLK2 <= datCLK;
   end

   assign _DCLK_FALL = (_DCLK2)&(~datCLK);
   assign _DCLK_RISE = (~_DCLK2)&(datCLK);

//Transmit catch edges
    always@(posedge CLK)
   begin
      _Transmit2 <= transmit;
   end

   assign _Transmit_RISE = (!_Transmit2)&(transmit);

//que module start at next rising edge of SPI clock
    always @ (posedge CLK) begin
        if (_Transmit_RISE) begin
            _moduleActiveQue = 1;
        end else if (_moduleActive) begin
            _moduleActiveQue = 0;
        end
    end
//put module into 'active' state
    always @ (posedge CLK) begin
        if (_moduleActiveQue && _DCLK_FALL) begin
            _moduleActive = 1;
        end else if (_countDown == 0) begin
            _moduleActive = 0;
        end
    end
//set SPI clock
    assign SCLK = datCLK && _moduleActive;

//clock out data
    always @ (posedge CLK) begin
        if (_moduleActiveQue && _DCLK_FALL) begin
            _inData_INT = inData;
            _countDown = 8'b10101010;
        end else if (_moduleActive && _DCLK_RISE) begin
            MOSI <= _inData_INT[7];
            _inData_INT <= _inData_INT << 1;
            _countDown <= _countDown << 1;
        end
    end
    endmodule

The module works using a 'counter' comprised of a value stuffed with 1's, and bitshifts on each clock until it equates to 0. Currently this counter bus, denounced _countDown, is what is proving to provide the biggest headache. When the above code is simulates it produces the waveforms as seen in the waveform window below.
Simulation

The least significant bit of _countDown seems to be stuck at high impedance and it does not count down as I would expect form the given code. Also as you may have noticed, in the 'initial begin' block the initialization of _countDown has been commented out. If this line of code is uncommented I find that _countDown always stays in a state of high impedance. I am relatively unexperienced with Verilog (which may show itself in the code) so any pointers would be highly appreciated.

Best Answer

As per our discussion in the chat, the issue appears to be down to the built in simulator. When I first started using it, I ran into some weird issues as well, so quickly moved over to using ModelSim (which comes with Quartus).

I think the issue is basically down to how you enter the waveforms in the built-in simulator - graphically. This can confuse the simulator, especially if you have mixtures of non-blocking/blocking assignments, and are using lots of clock edges - basically the cause-effect appears to get a bit mixed up.

This becomes clear when using ModelSim to simulate the same design. We get the following waveform:

ModelSim Trace

Which is very different in many places.

The results here are from the module in your post being controlled by a Verilog testbench rather than a graphical waveform editor. This not only makes editing the simulation waveforms easier, but also ensures cause-effect of signals is maintained as you can guarantee everything is done correctly at clock edges.


As you are coming from Xilinx/Vivado and testbenches are familiar, I won't go into much detail on them. What I will instead do is point out a couple of useful links:

  1. Getting Started with Quartus II Simulation Using the ModelSim-Altera Software
  2. Simulation With the NativeLink Feature in Quartus II Software

These basically are breadcrumbs in setting up ModelSim to be run directly from Quartus. This will allow you to ditch the graphical simulator and use ModelSim. I say breadcrumbs because the information is very scattered/incomplete. I have been successful at using testbenches directly from Quartus with some minor annoyance. I actually find it much easier to simulate directly with ModelSim using simple commands for compiling and simulating.

An example of how to load a quick simulation (which is of the load_sim.tcl script I attached in our discussion):

# Location of simulation files
set SIMDIR "../"

# Create a work library - lets call it "spi", but you could call it anything
vlib spi

# Map the library to "work"
vmap work ./spi

# Compile the the simulation files:
# vlog filename.v tells modelsim to compile a Verilog file. Add an entry for each file
vlog $SIMDIR/spi.v

# Compile the testbench
# I wrote a quick SystemVerilog called test_program.sv, so lets compile that using the -sv flag (to indicate the language is SV).
vlog -sv ./test_program.sv

# Elaborate top level design
# test_program is the name of the testbench module
eval vsim -t ns -L work -L spi test_program

# Load the waveform "do file" Tcl script
# This is an optional file which stores things like which waveforms you want to display
# and what radix they use. You can generate this after the first run by clicking in the
# wave view and pressing CTRL-S. This will save a .do file with the setup

do ./wave.do

# Run the simulation for 1000 ns. You could do "run -all" if you have a $stop command in
# your testbench.
run 1000ns

With this script it is quite easy to set up a simulation for ModelSim and use a testbench in a similar way to what you would do in Vivado. You simply change to the correct directory in ModelSim, and then enter at the prompt do load_sim.tcl and the simulation will run.