iCE Bling FPGA – Beautiful LED Earrings with Lattice iCE40



It’s the same story every year. At the horizon is a loved one’s birthday, or an anniversary, and I want to make them something special. Buying something won’t do. Oh no, I have to design and build it myself. I would then start with a simple idea, and then complicate it progressively to the point where it would take several anniversaries to finish the project.


This time, I wanted to build a pair of earrings for my wife’s birthday. Since I am learning about FPGAs these days, I wanted to incorporate one into the design. Having gotten older and wiser, I decided to enlist help early on. I would focus on the overall design and the programming part, and leave the PCB design and assembly to my trusted friend and engineer Siva.


Objective


Build a pair of LED earrings using the Lattice iCE40UP5k FPGA. The earrings would have an 8 x 8 grid of LEDs, and would be powered by a CR2032 coin cell.


Design Notes



My wife likes to wear big earrings, so using a CR2032 was not a problem. The earrings needed to balance nicely, so the battery had to be at the bottom. After considering many crazy shapes, I settled on basic geometry – three circles intersecting slightly.


The 64 LEDs (0402 package) would be arranged in a square grid of 8 x 8. To keep things simple, and since we had a space constraint, I decided not to use shift registers. I would drive the LEDs directly with the FPGA pins, which meant that I had restrictions on how many LEDs could be lit at a given instance. (Due to the current limit on the FPGA GPIO pins.)


Choosing green LEDs had two advantages. It matched my wife’s birthstone colour, and the lower turn-on voltage meant I could safely use the 3V coin cell.


The PCB needs to have a quick way to upload the code, and for that we decided on a 2 x 5, 1.27 mm pitch SMD header. More on that later.


Prototyping with Lattice iCE40 FPGA



I knew that developing and testing code directly on the iCE Bling PCB would be painful, so I used an 8 x 8 LED dot grid display to speed up development. Siva was kind enough to make an adapter PCB for me.


Schematic


Here’s the schematic for iCE Bling. (PDF available at the download link at the end of this article.)


 


So as you can see, not too many components. Just what’s necessary to supply voltages for the FPGA, an SPI flash chip to store the bitstream (program), and an oscillator for the clock. The LEDs are directly connected to the FPGA pins, as I mentioned earlier.


PCB Design


Here’s what the PCB looks like, in Altium Designer. We had to use a 4-layer design due to restrictions on size and the considerable number of lines that need to be routed.


 


As you can see above, the design uses classic shapes, with a hole for a steel hook, and there’s also a gold trace than runs along the boundary, which I think goes well with a black PCB.


Programmer PCB



I wanted to make it easy to program iCE Bling, so we made special Pogo Pin adapter for it, similar to PogoProg sold by Electronut Labs. It’s designed to be used with an FT232H board, which we will look at further down in the article.


Logic Design


I used Verilog HDL for this project. Let’s look at the various pieces of logic needed to achieve our goal.


Frame Buffer


Since I did not use a shift register, and the FPGA has a current limit of less than 8 mA per GPIO pin, the strategy used was to go through the grid and pulse every LED for a short period of time.


The 8 x 8 grid is represented as a Verilog array reg [63:0] fb. Here are the highlights from the frame buffer display logic in dot88.v.


  always @ (posedge clk)
    begin
      
      // initialise 
      if (!resetn) 
          begin
              pcounter <= 0;
              d88_counter <= 0;
              xp <= 0;
              yp <= 0;
          end

      d88_counter <= d88_counter + 1;

      if (!d88_counter)
        begin
          pcounter <= pcounter + 1;
      
          // calculate position
          xp <= pcounter/8;
          yp <= pcounter - 8*(pcounter/8);  

          // update row/col
          c <= fb[pcounter-1] ? (8'd1 << (7 - xp)) : 8'd0;
          r <= ~(8'd1 << (7 - yp));
        end
        
    end

  assign {row, col} = {r, c};

In the above code, all we’re doing is turning on the correct LEDs very fast. We do that by incrementing a mod-64 counter, and setting the correct bits in the row (r) and column (c) buses. If you increase the bit width of d88_counter, you can observe how the display is working.


Now that we know how to display an 8 x 8 “frame” of 64 bits, let’s create some content for it.


Scrolling Letters


The first thing I wanted was to scroll the initials of my wife’s name. For this, we define two “frame buffers” and switch between them. The code for this module is in letters88.v.


// "H"
r_fb1[0:7]   = 8'b01000010;    
r_fb1[8:15]  = 8'b01000010; 
r_fb1[16:23] = 8'b01000010; 
r_fb1[24:31] = 8'b01111110; 
r_fb1[32:39] = 8'b01111110; 
r_fb1[40:47] = 8'b01000010; 
r_fb1[48:55] = 8'b01000010; 
r_fb1[56:63] = 8'b01000010; 

The above code shows how the letter “H” is stored.


// scroll letter 1 
r_fb1[0:7]   <= {r_fb1[6:0],  r_fb1[7]};
r_fb1[8:15]  <= {r_fb1[14:8], r_fb1[15]};
r_fb1[16:23] <= {r_fb1[22:16],r_fb1[23]};
r_fb1[24:31] <= {r_fb1[30:24], r_fb1[31]};
r_fb1[32:39] <= {r_fb1[38:32], r_fb1[39]};
r_fb1[40:47] <= {r_fb1[46:40], r_fb1[47]};
r_fb1[48:55] <= {r_fb1[54:48], r_fb1[55]};
r_fb1[56:63] <= {r_fb1[62:56], r_fb1[63]};

The above code shows how the scrolling is done on a clock pulse. Each line is just rotated by one bit.


Now let’s look at the next frame buffer animation.


Conway’s Game of Life


I’ve had a fascination for Conway’s Game of Life (GOL) for a while now. It appears in my book Python Playground, as well as some of my blog articles on this website. Not that I pretend to understand what’s going on in GOL – darn thing has a Universal Turing Machine inside of it! Anyway, give me an X/Y grid of anything, and I am likely to try and run GOL on it. The code for this module is in conway88.v.


Here are the rules for GOL on a discrete grid, assuming a value of 1 means “ON” and 0 means “OFF”.


1. Compute 8 nearest neighbour sum of a pixel at (i, j).
2. If (i, j) is ON, and if sum is less than 2 or greater than 3, turn (i, j) to OFF.
3. if (i, j )is OFF, if sum is equal to 3, set (i, j) to ON.


Writing code for an FPGA is very different from writing code for a CPU. In the FPGA world, you have limited constructs, and need to think in terms of registers, clocks, counters, and state machines. Here’s the state machine I used for GOL.


 


Game of Life – State Machine


On start, the state changes to “New Frame”. This initialises the simulation grid and changes state to “Compute 8NN”, which computes the sum of the 8 nearest neighbours of the current pixel. The state then changes to “Update Grid” which keeps switching back and forth with “Compute 8NN” till the whole frame is done, after which state is reset to “New Frame”. Some code snippets of this state machine can be seen below.


// handle state machine
case (curr_state)

    sSTART:
        begin 
            // start new frame
            curr_state <= sNEW_FRAME;
        end

    sNEW_FRAME:
        begin 
            // start new frame
            if (!cnewframe)
                begin
                    // copy grid
                    grid_cpy <= grid;

                    // reset current position
                    i <= 0;

                    // change state to compute sum
                    curr_state <= sCOMPUTE_8NN;
                end
        end

    sUPDATE:
        begin 

            // increment position 
            i <= i + 1;

            // set grid value based on sum
            if (grid[i])
                begin 
                    if (sum < 2 || sum > 3)
                        begin 
                            grid[i] <= 0;
                        end
                end 
            else if (sum == 3) 
                begin 
                    grid[i] <= 1;   
                end
            
            // done with frame?
            if (i == 63)
                begin 
                    // go to new frame
                    curr_state <= sNEW_FRAME;
                end
            else 
                begin 
                    // reset sum
                    sum <= 0;
                    // change state to compute
                    curr_state <= sCOMPUTE_8NN;
                end
        end

    sCOMPUTE_8NN:
        begin 
            // automatic toroidal boundary conditions because of 
            // index overflow
            sum <=  grid_cpy[i-N-1] + grid_cpy[i-N] + 
                    grid_cpy[i-N+1] + 
                    grid_cpy[i-1] + grid_cpy[i+1] + 
                    grid_cpy[i+N-1] + grid_cpy[i+N] + 
                    grid_cpy[i+N+1];

            // change state to update
            curr_state <= sUPDATE;
        end

    default:
        curr_state <= sSTART;

endcase

Coming from the microcontroller world, writing code for an FPGA in Verilog is a bit weird for me. But it’s also strangely simpler, and one develops a heightened awareness of what’s happening at every tick of the clock, so to speak.


Blinky Grid


In this pattern, we just want to blink alternate LEDs on the grid. The code for this module is in rain88.v. (Yes, I had fantasies of making it look like rain. It didn’t.)


always @ (posedge clk)
    begin 
        if (!resetn)
            begin

                // reset counters
                refresh_counter <= 0;

                // initialise grid
                grid[0:7]   <= 8'b01010101;    
                grid[8:15]  <= 8'b10101010; 
                grid[16:23] <= 8'b01010101; 
                grid[24:31] <= 8'b10101010; 
                grid[32:39] <= 8'b01010101; 
                grid[40:47] <= 8'b10101010; 
                grid[48:55] <= 8'b01010101; 
                grid[56:63] <= 8'b10101010; 
            end

        // update refresh counter 
        refresh_counter <= refresh_counter + 1;

        // update frame
        if (!refresh_counter)
            begin 
                grid <= ~grid;
            end
    end

After initialising a “0101…” pattern, on each clock edge, we just invert the bits to get a blinky grid.


Putting it all together


Now that we have our three patterns, all we need is some code to cycle through them. This is done by the module in top.v.


// LED pattern
parameter PAT_LETTERS   = 2'b00;
parameter PAT_CONWAY    = 2'b01;
parameter PAT_RAIN      = 2'b10;
reg [1:0] curr_patt;
...

// assign buffer based on curent pattern
wire [63:0] w_fb = (curr_patt == PAT_LETTERS) ? w_fb_lett : 
                            ((curr_patt == PAT_CONWAY) ? 
                              w_fb_conway : w_fb_rain);
...
    // letters module
    wire [63:0] w_fb_lett;
    letters88 let (
        .resetn(resetn),
        .clk(clk),
        .fb(w_fb_lett)
    );

    // rain module
    wire [63:0] w_fb_rain;
    rain88 rn(
        .resetn(resetn),
        .clk(clk),
        .fb(w_fb_rain)
    );

    // Conway's GOL
    wire [63:0] w_fb_conway;
    conway88 c88 (
        .resetn(resetn),
        .clk(clk),
        .fb(w_fb_conway)
    );

    // instatiate dot88
    dot88 d88(
        .resetn(resetn),
        .clk(clk),
        .fb(w_fb),
        .row(row),
        .col(col)
    );
 
always @(posedge clk) 
    begin
        // initialise rot
        if (!resetn) 
            begin
                counter <= 0;
                curr_patt <= PAT_LETTERS;
                sw_counter <= 0;
            end
        else  
            begin
                
                // increment counters
                counter <= counter + 1;
                sw_counter <= sw_counter + 1;

                // switch pattern 
                if (!sw_counter)
                    curr_patt <= curr_patt + 1;

                // blink LED
                if (!counter) 
                    begin
                        L2 = ~L2;
                    end
            end 


The above shows code snippets for setting up the current pattern and module instantiation. Notice how the wire w_fb, the input to dot88 changes combinatorially based on the current pattern. Every 10 seconds or so, we switch the current pattern which is sent to the dot88.v module we discussed in the beginning.


Using icestorm tools


For this project, I’ve use the open source icestorm tools from Clifford Wolf, on Linux. These tools are much more convenient to use than the clunky official software put out by Lattice Semiconductor.


To build the project, install icestorm tools, use ‘make‘, and to upload the code, use ‘make sudo-prog‘.


To upload code to iCEBling, we used the Adafruit FT232H board. Here is the pin mapping from FT232H to iCE Bling.


**iCE Bling FT232H
SS D4
SCK D0
MOSI D2
MISO D1
CRESET D7
CDONE D6
GND GND

(Power iCE Bling using coin cell while programming.)


In Action


You can see iCE Bling in action below:


 


Conclusion


So I did it. FPGA earrings, and on time for her birthday. She is thrilled! In retrospect, I should’ve used a shift register. Well, maybe in the next revision.


Downloads


You can find code and design files for this project at the link below:

https://gitlab.com/electronutlabs-public/ice-bling


Acknowledgements


I could not have completed this project in time without help from Sivaprakash S – thank you! I also would like to thank Tavish Naruka for his design review and other members of Electronut Labs for their support and encouragement.