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.
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:
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:
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):
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.