Skip to contentSkip to author details

A Saturday morning puzzle on my FPGA

Written by Michael Earls
 FPGA  electronics  hobbies

I woke up this morning with a desire to solve a puzzle. I decided that I'd make a simple LED chaser on a 7-segment LED display using my Mojo FPGA (now that I have everything working in Windows 10 again thanks to the awesome customer support from Justin at Embedded Micro).

Here's a video of the results:

Here is a breadboard image showing how I've wired up the circuit:

Mojo 7-segment chaser breadboard circuit

Here's my constraints file that shows the wiring of the 7-segment display:

NET "seg<0>" LOC = P51 | IOSTANDARD = LVTTL;  
NET "seg<1>" LOC = P50 | IOSTANDARD = LVTTL;  
NET "seg<2>" LOC = P41 | IOSTANDARD = LVTTL;  
NET "seg<3>" LOC = P40 | IOSTANDARD = LVTTL;  
NET "seg<4>" LOC = P35 | IOSTANDARD = LVTTL;  
NET "seg<5>" LOC = P34 | IOSTANDARD = LVTTL;

Update (8:00 PM): I was able to reach my goal and pull this off using VHDL instead of Lucid. Here is the entire program (no dependencies required). This is much more precise. I really like VHDL.

library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity circle_chaser is  
    Port ( clk : in  STD_LOGIC;
             rst_n : in STD_LOGIC;            -- reset button (active low)
             led : out STD_LOGIC_VECTOR (7 downto 0); --8 user controllable LEDs
             cclk : in STD_LOGIC;             -- configuration clock, AVR ready when high
             spi_miso : out STD_LOGIC;       -- AVR SPI MISO
             spi_ss : in STD_LOGIC;           -- AVR SPI Slave Select
             spi_mosi : in STD_LOGIC;         -- AVR SPI MOSI
             spi_sck : in STD_LOGIC;          -- AVR SPI Clock
             spi_channel : in STD_LOGIC_VECTOR (3 downto 0); --AVR general purpose pins (used by default to select ADC channel)
             avr_tx : in STD_LOGIC;           -- AVR TX (FPGA RX)
             avr_rx : out STD_LOGIC;          -- AVR RX (FPGA TX)
             avr_rx_busy : in STD_LOGIC;
             seg : out  STD_LOGIC_VECTOR (5 downto 0)
     );
end circle_chaser;

architecture Behavioral of circle_chaser is

signal clk_div   : std_logic_vector(21 downto 0);  
signal shift_reg : std_logic_vector(5 downto 0) := "000001";

begin  
    led <= X"00";
    spi_miso <= '0';
    avr_rx <= '0';

    -- clock divider
    process (clk)
    begin
      if (clk'Event and clk = '1') then
            clk_div <= clk_div + '1';
      end if;
    end process;

    -- LED chaser
    process (clk_div(21))
    begin
      if (clk_div(21)'Event and clk_div(21) = '1') then
            shift_reg <= shift_reg(4 downto 0) & '0';
            if (shift_reg(5) = '1') then
                shift_reg <= "000001";
            end if;
      end if;
    end process;

    -- display the result on the LEDs
    seg <= shift_reg;

end Behavioral;  

Update (6/18 10:00 AM): I completed the HDL trilogy this morning and implemented this chaser in Verilog. Here is the code:

module mojo_top(  
    // 50MHz clock input
    input clk,
    // Input from reset button (active low)
    input rst_n,
    // cclk input from AVR, high when AVR is ready
    input cclk,
    // Outputs to the 8 onboard LEDs
    output[7:0]led,
    // AVR SPI connections
    output spi_miso,
    input spi_ss,
    input spi_mosi,
    input spi_sck,
    // AVR ADC channel select
    output [3:0] spi_channel,
    // Serial connections
    input avr_tx, // AVR Tx => FPGA Rx
    output avr_rx, // AVR Rx => FPGA Tx
    input avr_rx_busy, // AVR Rx buffer full
     output [5:0] seg
    );

wire rst = ~rst_n; // make reset active high  
reg [22:0] clk_div = 22'b0;

// these signals should be high-z when not used
assign spi_miso = 1'bz;  
assign avr_rx = 1'bz;  
assign spi_channel = 4'bzzzz;

assign led = 8'b0;

parameter REG_INIT = 7'b0000001;

    reg [6:0] shift_reg = REG_INIT; // register for led output

    always @ (posedge clk) 
    begin
        clk_div = clk_div + 1;
        if (clk_div[22] == 1) begin
            clk_div = 0;
            shift_reg = shift_reg << 1;
            if (shift_reg[6] == 1) begin
                shift_reg = REG_INIT;
            end
        end
    end

    assign seg = shift_reg[5:0]; // wire output and leds register

endmodule

I had to take a different approach with the shift register to allow for an additional bit to check to see when to reset the shift register (and mirrored LED segments) back to the start. I made the shift register 1 bit larger (line 36) than the LED segments so I could check to see when the MSB was high and then reset. When the shift register was the same size as the LED segments, it would skip lighting one because it got reset before it could be lit up.

Here is the Lucid code (the Mojo-specific HDL):

  
.clk(clk) {
  .rst(rst) {
    circleChase chase(#SPEED(22));
  }
}

always {  
  seg = chase.out;
}

The chaser was a lot easier to implement than I expected...

module circleChase #(  
    MAX_OUT = 6: MAX_OUT > 2,
    SPEED = 25: SPEED > 0 // the lower the SPEED, the faster the counter
  )(
    input clk,  // clock
    input rst,  // reset
    output out[6]
  ) {

  .clk(clk) {   
    .rst(rst) {    
      slowCount count(#SPEED(SPEED));
    }
  }

  const TOP = MAX_OUT;
  counter outCount(.rst(rst), .clk(count.q), #TOP(TOP - 1));

  always {

    out = 6h0; 

    if(count.q) {
        out[outCount.value] = 1;
    }
  }
}

Notice how the clock to the outCount module on line 17 is coming from the slowCount's .q trigger. This goes high each time the slowCount reaches it's limit (the code for this is at the bottom of this post).

I've covered it before, but for completeness, here is the code for the slowCount module (in Lucid, the Mojo-specific HDL):

module slowCount #(  
      SPEED = 25 : SPEED > 0
    )(
    input clk,  // clock
    input rst,  // reset
    output value[8],
    output q
  ) {

  .clk(clk), .rst(rst) {
    dff flip[SPEED];
  }

  const LIMIT = SPEED - 1;
  counter ctr(.rst(rst));

  always {
    ctr.clk = flip.q[LIMIT];

    // toggle the q bit when the counter reaches each end
    q = flip.q[LIMIT] == 0 ? 0 : 1;

    flip.d = flip.q + 1;
    value = ctr.value;
  }
}