Forum: FPGA, VHDL & Verilog [newbie] chip select - unexpected result?

Author: Kenny Millar (kennym)
Posted on:
Attached files:

Rate this post
0 useful
not useful
Please forgive my lack of knowledge!

I am trying to create an OLED controller.
It will control a Newhaven NHD-1.5-128128UGC3 Oled which is a 218x128 
colour oled. I already have a good controller for this written in C for 
an ATMEGA, but thought it would be a great exercise for me to brush up 
my verilog skills and get it working on an FPGA.

The FPGA I'm using is a Lattice MACHX03 on a MachX03 starter kit.

The issue I am seeing is that I'm getting on glitch^H^H^H^H^H^H logic 
error on my chip-select line (wire oled_cs in my verilog code) and was 
hoping someone can help me understand why.

Below is my verilog code for the top module, which is the only thing 
driving oled_cs. I will try to attache a screen grab of the logic 
anylzer which is seeing the oled_cs glitch.

The theory is that the top module will assert oled_cs (active low) and 
the oled_dc line, then place a byte into oled_data and send a start 
signal to the spi_master module, and then wait for "oled_busy" to be 
released before sending the next byte via spi, and so on until all the 
bytes in the initialisation sequence have been sent.

NOTE, I'm not trying to debug the SPI master yet, it's oled_busy signal 
can also be seen in the logic analyzer output, and that's all I need for 

The big issue is that the oled_cs signal seems to be released (logic 
high) after 2 clocks and I can't see why!

I'm just learning verilog, so you might need to be patient with me.

Here is the top module code, which is the only thing that drives oled_cs
module oled_ctrl (
  input  wire rst_n,
  input  wire miso,
  output wire [9:0]gnd,
  output wire mosi,
  output wire sck,
  output wire oled_dc,
  output wire oled_cs,
  output wire oled_rst,
  output led_d2,
  output led_d3,
  output led_d4

  localparam RESET_HOLD_COUNT = 16'h1000;
  localparam STATE_RESET     = 8'h01;
  localparam STATE_SET_PINS  = 8'h02;
  localparam STATE_START_SPI  = 8'h04;
  localparam STATE_WAIT_SPI  = 8'h08;
  localparam STATE_SPI_DONE  = 8'h10;
  localparam STATE_HOLD    = 8'h20;

  wire clk;
  wire oled_busy;
  reg [7:0] LUT_INDEX = 8'd0;
  reg [15:0] LUT_DATA;
  reg spim_start;
  reg [7:0] oled_data;
  reg [5:0] machine_state = STATE_RESET;
  reg [15:0] loop_counter = 16'd0;
  reg o_rst = 1'b1;
  reg o_cs = 1'b1;
  reg o_dc = 1'b1;
  assign gnd = 10'd0;
  assign oled_rst = o_rst;
  assign oled_cs = o_cs;
  assign oled_dc = oled_busy;
  // Tell-tale LEDS
  assign led_d2 = rst_n;
  assign led_d3 = ~sck;
  assign led_d4 = ~mosi;

  always @(posedge clk) begin
    if(!rst_n) begin
      oled_data     <= 8'h00;
      spim_start     <= 1'b0;
      machine_state   <= STATE_RESET;
      loop_counter   <= 16'h00;
      LUT_INDEX     <= 0;
    else begin
      // state machine
      case (machine_state)
      STATE_RESET: begin
        if(loop_counter==16'd0) begin
          o_rst <= 1'b0; // assert oled reset
          o_cs <= 1'b1;
          o_dc <= 1'b1;
          loop_counter <= loop_counter + 16'd1;
        else if(loop_counter == RESET_HOLD_COUNT) begin
          o_rst <= 1'b1; // release oled reset
          machine_state <= STATE_SET_PINS;
        end else begin
          loop_counter <= loop_counter + 16'd1;
      STATE_SET_PINS: begin
        o_cs <= 1'b0; // assert oled chip select
        o_dc <= LUT_DATA[8];  // command or data pin (0:CMD, 1:DATA)
        oled_data <= LUT_DATA[7:0];
        machine_state <= STATE_START_SPI;
      STATE_START_SPI: begin
        spim_start <= 1'b1;
        o_cs <= 1'b0;
        machine_state <= STATE_WAIT_SPI;
      STATE_WAIT_SPI: begin
        spim_start <= 0; // release spim start
        o_cs <= 1'b0;
        if(!oled_busy) begin
          LUT_INDEX <= LUT_INDEX + 1'd1;
          machine_state <= STATE_SPI_DONE;
      STATE_SPI_DONE: begin
        o_cs <= 1'b1;
        spim_start <= 0;
        if(LUT_INDEX <= 8'd42) begin
          machine_state <= STATE_SET_PINS;
        end else begin
          machine_state <= STATE_HOLD;
        o_cs <= 1'b1;
        o_cs <= 1'b1;

always @(posedge clk) begin
  // Initialisation sequence for 128x128 OLED module
  0:  LUT_DATA <= 16'h00fd;  // CMD lock   =|
  1:  LUT_DATA <= 16'h0112;  // Lock       |  
  2:  LUT_DATA <= 16'h00fd;  // CMD lock     |
  3:  LUT_DATA <= 16'h01b1;  // Unlock    =| Unlock sequence
  4:  LUT_DATA <= 16'h00AE;  // Display off, sleep on
  5:  LUT_DATA <= 16'h00B3;  // CMD DCLK Divide Ratio
  6:  LUT_DATA <= 16'h01f1;  // clock = diviser+1
  7:  LUT_DATA <= 16'h00CA;  // CMD Set MUX ratio
  8:  LUT_DATA <= 16'h017f;  // OLED_END + 1
  9:  LUT_DATA <= 16'h00a2;  // CMD Display offset
  10:  LUT_DATA <= 16'h0100;  //  00
  11:  LUT_DATA <= 16'h00a1;  // Display start line
  12:  LUT_DATA <= 16'h0100;

  13:  LUT_DATA <= 16'h00A0;  // Set re-map, colour depth
  14:  LUT_DATA <= 16'h0162;  // 8-Bit, 256 colours
  15:  LUT_DATA <= 16'h00B5;  // setGPIO
  16:  LUT_DATA <= 16'h0100;  // Disabled

  17:  LUT_DATA <= 16'h00AB;  // Function set
  18:  LUT_DATA <= 16'h0101;  // 8-Bit interface, internal VDD regulator

  19:  LUT_DATA <= 16'h00b4;  // Set VSL (voltage-segment-low)
  20:  LUT_DATA <= 16'h01A0;  //   External VSL
  21:  LUT_DATA <= 16'h01B5;
  22:  LUT_DATA <= 16'h0155;

  23:  LUT_DATA <= 16'h00C1;  // Set Contrast current for A,B,C
  24:  LUT_DATA <= 16'h018A;  // A
  25:  LUT_DATA <= 16'h018a;   // B
  26:  LUT_DATA <= 16'h018A;  // C

  27:  LUT_DATA <= 16'h00c7;  // Set mster Contrast
  28:  LUT_DATA <= 16'h010f;
  29:  LUT_DATA <= 16'h00B9;  // reset to default LUT
  30:  LUT_DATA <= 16'h00B1;  // Set pre and discharge phase length
  31:  LUT_DATA <= 16'h0132;  //
  32:  LUT_DATA <= 16'h00BB;  // Set pre-charge voltage of colour A,B,C
  33:  LUT_DATA <= 16'h0107;
  34:  LUT_DATA <= 16'h00B2;  // Set enhanced driving scheme
  35:  LUT_DATA <= 16'h01a4;  //
  36:  LUT_DATA <= 16'h0100;  
  37:  LUT_DATA <= 16'h0100;
  38:  LUT_DATA <= 16'h00B6;  // second precharge period
  39:  LUT_DATA <= 16'h0101;
  40:  LUT_DATA <= 16'h00BE;  // Set VCOMH Voltage
  41:  LUT_DATA <= 16'h0107;
  42:  LUT_DATA <= 16'h00a6;  // Normal display mode.
  default:  LUT_DATA  <=  16'h0100;

  /* Instances of modules. */
  // Lattice OSCH Clock Module
  defparam   OSCH_inst.NOM_FREQ = "2.08";  
  OSCH     OSCH_inst  (.STDBY(1'b0), .OSC(clk), .SEDSTDBY() );
  spi_master  spim(.clk(clk), .rst_n(rst_n), .miso(miso), .mosi(mosi), .sck(sck), .start(spim_start), .data_in(oled_data), .data_out(), .busy(oled_busy), .new_data()) ;

: Edited by User
Author: Lothar Miller (lkmiller) (Moderator)
Posted on:

Rate this post
0 useful
not useful
Kenny M. wrote:
> the oled_cs glitch
That for sure is not a glitch. A glitch is something that occurs due to 
propagation delays in combinatorial logik. What we se here is a fairly 
stable and synchronous state. and fairly funny: its always on the 
first transmitted bit of a SPI transmission.

Did you do a functional simulation of your code? Thats the very best way 
to ensure that the module is fit to be used. And there you can easily 
see the current state of each FSM...

: Edited by Moderator
Author: Kenny Millar (kennym)
Posted on:

Rate this post
0 useful
not useful
>That for sure is not a glitch.
Sorry. I used the wrong term, but I thought the meaning was clear. I 
will try to be more precise in future.

I have tried to simulate this. I am using Lattice Diamond 3.7 (64 Bit) 
on Windows 10. I run the simulation wizard and import the code, and it 
creates a simulation and waveform that extends to 1us.  For the life of 
me I can't figure out how to expand it to cover the areas I need. I 
guess I need to go read up on the simulator and how it works.

**EDIT** Changed 100ns to 1us

: Edited by User
Author: Kenny Millar (kennym)
Posted on:

Rate this post
0 useful
not useful
I persevered with the simulator, and it led me to the problem, which is 
now solved :-)

In my code there is a race condition between the spim_master module 
asserting the oled_busy signal and my top module checking the signal in 
        spim_start <= 0; // release spim start
        o_cs <= 1'b0;
        if(!oled_busy) begin
          LUT_INDEX <= LUT_INDEX + 1'd1;
          machine_state <= STATE_SPI_DONE;

One solution, which worked for me, was to invert the clock going to the 
spim_master, which put the spim_master half a clock pulse out of phase. 
A better solution would be to add a state to my state machine that waits 
for the oled_busy to be asserted, before waiting for it to be released 


Entering an e-mail address is optional. If you want to receive reply notifications by e-mail, please log in.

Rules — please read before posting

  • Post long source code as attachment, not in the text
  • Posting advertisements is forbidden.

Formatting options

  • [c]C code[/c]
  • [avrasm]AVR assembler code[/avrasm]
  • [vhdl]VHDL code[/vhdl]
  • [code]code in other languages, ASCII drawings[/code]
  • [math]formula (LaTeX syntax)[/math]

Bild automatisch verkleinern, falls nötig
Note: the original post is older than 6 months. Please don't ask any new questions in this thread, but start a new one.