A DIY Community for Tech, Science, and R&D

Join a culture of sharing, growth and collaboration. You can be a part of the maker movement!

4.1. Blinky

Fipsy FPGA / Learning  / 4. Learning Projects / 4.1. Blinky

Blinky – or flashing the onboard LED is the baseline example used in the ‘starter project’. We encourage all users to start from the starter project, and modify out.

This is because there is a way to brick, or ruin, the device by closing the port needed to program the device in the future. See more about this point here: Enabling SPI

If this is your first time uploading code onto the Fipsy, please follow these directions first. Here we are only describing how the Blinky code works.

Learn about ‘Black Box’ design concepts for an FPGA on the Brief Theory page.

Grab the Blinky code from our GitHub.

Specifically, if you have Fipsy Ver 1 – MachXO2-256 then use this link.
If you have the Fipsy Ver 2 – MachX02-1200 then use this link.

Notice there is a project_files folder with a .lpf file you can open (Lattice Project File – in this case FipsyBaseline.lpf) using Lattice Diamond.

 

Here is the start of the example Verilog code:

module Fipsy_Top(
PIN7, PIN8, PIN9, PIN10,
PIN11, PIN12, PIN13, PIN14, PIN17, PIN18, PIN19, PIN20,
LEDn
);

Every ‘black box’ is a module, and you declare a module for every set of functionality. The top most module is the one that no other module instantiates, or calls. Here we see how Fipysy_Top is the name of a module, and it is in fact the module that is not called by any other module. It must be declared however. What you see is a list of ports – think wires connecting sections of a motherboard. Ports can be one wire wide, or multiple wires wide.

In the Fipsy starter project, you are using names like PIN7, a variable name, because the example project sets these up for you ahead of time. In fact, the PIN# variables are tied to true numbered physical pins in a configuration file (MakeFPGA.lpf). For your purposes, it’s easier to use these variable names, and know they tie to the same numbered pins on the Fipsy, in the top level module (if you want to use them in lower-level modules, you need to pass them as ports, more on this in later projects).

// Define all of the above pins as available for general I/O
inout PIN7, PIN8, PIN9, PIN10, PIN11, PIN12, PIN13, PIN14, PIN17, PIN18, PIN19, PIN20;
// Except the LED, which is only an output, for which driving low lights the LED
output LEDn;

Here we declare the ports the module uses as input, output, or inout. Data either comes in (to an input port), leaves from the module (as an output port), or can switch states to do both over time.

// At this level, all named signals are wires
wire PIN7, PIN8, PIN9, PIN10, PIN11, PIN12, PIN13, PIN14, PIN17, PIN18, PIN19, PIN20, LEDn;

// Instantiate a connection to the internal oscillator
wire INTERNAL_OSC;

Now you want to declare wires and registers to be used in the module’s logic. If wires or registers share the same name as the port names declared in the module, they will be physically (electrically) connected. Registers, are persistent 1 or 0 values (current is drawn to voltage, or drawn to ground respectively). Wires are passive, instantaneous state transmitters. There are basically two situations that a wire becomes active: it is wired to an input pin, somewhere along the way, and gets voltage from that, or it is wired to a register (usually via a module’s port), and that drives it high. At no point do you assign a value or state to a wire. It must be wired to something that is driving the state. It can, however, represent the state of some other part of the device, or an input.

The difference between registers and wires is critical knowledge, so look into it.

Registers store values as long as the device has power (and you can change from 1 to 0 or 0 to 1 pretty easily). Most FPGAs lose all state and configuration at power-off, and must be reprogrammed from nearby chips at boot-up. The Mach XO2 inside the Fipsy, however, does keep it’s configuration, but on power-on it will reset it’s state to the stored initial configuration (what you program it to).

Registers, just like ports, can represent one state or a set of states (similar to a list or array in programming). Think of it as, you can have one wire, or you can have multiple wires in a bundle that connects to a port.

Eg.

reg my_var = 1'b1;

Means a register named my_var is assigned to a binary value 1 length long (the first 1), with value of 1 (the second 1).

You could also have:

reg my_vars[4:0]= 5'b01101;

which means a registry called my_vars with a length of 5 binary values, with ordered values 01101. The [4:0] part sets up the storage length, and is similar to saying ‘give me storage from index 4 to index 0’, which is 5 bits long. In Verilog, you have to know the maximum size of the data you are working with ahead of time.

Notice in the block of code for the Blinky example we declare a wire INTERNAL_OSC which stands for internal oscillator. This wire is declared, but it was not part of the module declaration. This is the right part of the code to set up wires and registers your current module uses.

Note that

wire INTERNAL_OSC;

sets up a wire that is 1 wire wide (one wire). But you could also have something more like a bundle of wires by declaring:

wire [7:0] data;

Continuing with the Blinky example, we have:

// In general the oscillator will be running, and should be kept running in case it is needed
// in logic or for aspects of configuration. The frequency can be set in the tool or at
// this level with a parameter.
// IMPORTANT - This oscillator has very poor accuracy - do not depend on it for critical timing.
defparam OSCH_inst.NOM_FREQ = "2.08"; // Must be one of the allowed frequencies (2.08MHz default)
OSCH OSCH_inst( .STDBY(1'b0), // Enable input, 0=Enabled, 1=Disabled (also disabled if Bandgap=OFF)
.OSC(INTERNAL_OSC), // Set the oscillator output to appear on the wire defined above
.SEDSTDBY() // This output is not required for normal use
);

When it says OSCH OSCH_inst( …); what it’s doing is it’s setting up a module named OSCH and importing its design from the module declaration called OSCH_inst, which is built into Lattice Diamond (included in the project by default, when you set up the chip-type used by the software; a step done for you in the example project files). Inside the parenthesis you take the time to wire elements (registers or wires) from the imported module to the current module.

Basically it’s like this:

variable_name Imported_Module(
.imported_modules_wire_or_reg(current_modules_wire_or_reg),
.imported_wire2(current_module_wire_2),
...

);

You are proactively assigning connections between module ports. Later, when you are synthesizing the design for a device, the Lattice Diamond program (or other synthesizer program) will calculate physical routes to achieve connections you declare.

This is a one-off, block of code that uses Lattice design concepts to establish the internal oscillator. Briefly, 2.08 sets up 2.08MHz as the clock frequency. The frequency here must be one of the supported frequencies listed in the Mach XO2 datasheet (you can take a faster clock-rate and do some logic to break it down into a slower frequency). The line

.OSC(INTERNAL_OSC),

says the module declaration for OSCH_inst has a port named OSC.. to that port connect the wire INTERNAL_OSC, which we declared in the current module.

At a high level, we are setting a variable equal to an expected frequency, and we are getting a wire named INTERNAL_OSC that will pulsate at that given frequency. We can now use this wire as a driver for other logic that must operate on ticks of a clock (the wire can essentially act as microchip clock).

// Create wires to connect the counter (see example application)
wire Out2Hz;
----------------------------------------------------------------------------------------------

/* Default assignments -
If pins are not used, it may be a good idea to drive them to a known state.
*/
assign PIN7 = 0;
assign PIN8 = 0;
assign PIN9 = 0;
//assign PIN10 = 0;
//assign PIN11 = 0;
assign PIN12 = 0;
assign PIN13 = 0;
assign PIN14 = 0;
assign PIN17 = 0;
assign PIN18 = 0;
assign PIN19 = 0;
//assign PIN20 = 0;
//assign LEDn = 0;

Out2Hz is a wire we set up for later. We have a block of assign PIN=0;  with some commented out. The idea is to assign to 0 (ground), any pin we AREN’T using in the project. Pins that are unassigned will float between high and low states unpredictably. It’s just good practice to ground out unused pins, which can help protect the electronics in some circumstances.

You comment out the pins we ARE using, so they can be set to the states referred to in other parts of the module.

// Instantiate the counter/divider
FreqDiv20Bit FreqDiv20Bit_inst(
.CLOCK(INTERNAL_OSC), // Drive with internal oscillator
.RESET(PIN10), // Reset when chosen reset pin is high
.MSB(Out2Hz) // We expect 2 Hz out with 2MHz in
);

This declares another module of type FreqDiv20Bit_inst, which IS part of our code base in the example project. It serves to take a faster clock rate 2.08 MHz, and slow it down to (close to but not exactly) 2Hz.

This module has an input wire named CLOCK which receives the 2.08MHz pulses from INTERNAL_OSC. The code then uses a binary counter, and when the counter reaches a certain threshold, one of the bits is set to 1, and acts to drive an output signal. The threshold is when the Most Significant Bit (in the counter) is one (the MSB is the side of an array of bits with the greatest binary value, for example in binary 3’b100 — here the 1 represents a value of 4, and so the left most bit is the Most Significant Bit). So you have an output port called MSB that connects to a wire named Out2Hz, which is usable in the module as a source for pulses at 2Hz.

Explore the file ‘FreqDiv20Bit.v’ to see how the counter works to generate pulses.

// Route the output signal to a pin and the LED
// The LED lights when the signal driving it is low, so inverting the signal will make it light
// when the signal is high
assign LEDn = !Out2Hz;
// View the signal on a chosen pin
assign PIN20 = Out2Hz;
// Also put the oscillator signal itself on a pin
assign PIN11 = INTERNAL_OSC;

endmodule

As the comments say, the LED lights up when the value of LEDn is zero, or drawn to ground. So if we want the LED to light up when the value of Out2Hz is high, then we should set it to the ! value, or ‘not operation’ value of of Out2Hz. That is the line

assign LEDn = !Out2Hz;

Take a moment to appreciate that we are assigning LEDn here, low in the code, but it is getting it’s state from Out2Hz, higher in the code, which is getting it’s state from the MSB port in another module. Realize that your code represents a design, not a sequence of events. The design processes everything instantaneously, and in parallel. It’s a BIG change in the way of thinking for most programmers. You have to break out of the concept of linear control logic, and think more about connecting wires from here to there, as you would while doing circuit board design.

For debugging and testing purposes, PIN20 is set to Out2Hz, so you can tests that with a multimeter. The clock signal is also tied to PIN11, which is common for electronics, since a lot of times you want one clock to be driving multiple devices, so they are in sync. It’s not really needed, but is there as a demonstration of this concept. The ‘endmodule’ line is important. Technically you can have more than one module per text file, so having proper ‘module’ and ‘endmodule’ nesting is important.

 

Here is the full code:

// Fipsy_Top.v

/* BASELINE EXAMPLE - Blinky
Use a 20-bit counter to divide the 2.08 MHz internal clock by 2^20 to get an output of
about 2 Hz. Use this output to drive the LED. Locally connect the oscillator and
the 2 Hz output to pins so they can be viewed on an oscilloscope. Hold the counter
in reset when a chosen pin is high (pull down should be active so this will not be true
when the pin is left open). Drive the LED with the output of the counter inverted so
that when reset is true the LED will be off.
*/

module Fipsy_Top(
PIN7, PIN8, PIN9, PIN10,
PIN11, PIN12, PIN13, PIN14, PIN17, PIN18, PIN19, PIN20,
LEDn
);

// Define all of the above pins as available for general I/O
inout PIN7, PIN8, PIN9, PIN10, PIN11, PIN12, PIN13, PIN14, PIN17, PIN18, PIN19, PIN20;
// Except the LED, which is only an output, for which driving low lights the LED
output LEDn;

// At this level, all named signals are wires
wire PIN7, PIN8, PIN9, PIN10, PIN11, PIN12, PIN13, PIN14, PIN17, PIN18, PIN19, PIN20, LEDn;

// Instantiate a connection to the internal oscillator
wire INTERNAL_OSC;
// In general the oscillator will be running, and should be kept running in case it is needed
// in logic or for aspects of configuration. The frequency can be set in the tool or at
// this level with a parameter.
// IMPORTANT - This oscillator has very poor accuracy - do not depend on it for critical timing.
defparam OSCH_inst.NOM_FREQ = "2.08"; // Must be one of the allowed frequencies (2.08MHz default)
OSCH OSCH_inst( .STDBY(1'b0), // Enable input, 0=Enabled, 1=Disabled (also disabled if Bandgap=OFF)
.OSC(INTERNAL_OSC), // Set the oscillator output to appear on the wire defined above
.SEDSTDBY() // This output is not required for normal use
);

// Create wires to connect the counter (see example application)
wire Out2Hz;
----------------------------------------------------------------------------------------------

/* Default assignments -
If pins are not used, it may be a good idea to drive them to a known state.
*/
assign PIN7 = 0;
assign PIN8 = 0;
assign PIN9 = 0;
//assign PIN10 = 0;
//assign PIN11 = 0;
assign PIN12 = 0;
assign PIN13 = 0;
assign PIN14 = 0;
assign PIN17 = 0;
assign PIN18 = 0;
assign PIN19 = 0;
//assign PIN20 = 0;
//assign LEDn = 0;

// Instantiate the counter/divider
FreqDiv20Bit FreqDiv20Bit_inst(
.CLOCK(INTERNAL_OSC), // Drive with internal oscillator
.RESET(PIN10), // Reset when chosen reset pin is high
.MSB(Out2Hz) // We expect 2 Hz out with 2MHz in
);

// Route the output signal to a pin and the LED
// The LED lights when the signal driving it is low, so inverting the signal will make it light
// when the signal is high
assign LEDn = !Out2Hz;
// View the signal on a chosen pin
assign PIN20 = Out2Hz;
// Also put the oscillator signal itself on a pin
assign PIN11 = INTERNAL_OSC;

endmodule
/* FreqDiv20Bit.v

This module implements a 20-bit counter with only the most significant bit provided as
an output. In effect, this is a frequency divider for which the output toggles at a
rate equal to the clock frequency divided by 2^20. The clock is an input and is
applied to the sequential logic clock. Technically, the output is not a clock within
the FPGA, but a logic signal, although it will toggle like a clock. There is a reset
input that will set the count to zero on each clock edge for which it is set (logic high).
This reset will clear the output to logic low, and the count and output will remain
that way as long as reset is active. The reset input can therefore be used to stop the
output signal from toggling at an unpredictable point in the cycle. More complex logic
could be developed to provide a more predictable control.

*/

//-----------------------------------------------------------------------------------------------
// Module header with identification of connected signals
//-----------------------------------------------------------------------------------------------

module FreqDiv20Bit(CLOCK, RESET, MSB);

input CLOCK;
input RESET;
output MSB;

//-----------------------------------------------------------------------------------------------
// Signal definitions, registers, variables
//-----------------------------------------------------------------------------------------------

// Signal types for I/O
wire CLOCK;
wire RESET;
wire MSB;

// Internal count bits
reg [19:0] count;

//-----------------------------------------------------------------------------------------------
// Module logic definition
//-----------------------------------------------------------------------------------------------

// 20-Bit counter with synchronous reset
// With reset not in the sensitivity list, the reset is synchronous (on next clock edge)
// If reset were in the sensitivity list, the reset would be asynchronous, which might
// not be supported by the tool or the underlying hardware of the chip or both
// Without specifying bits and bit sizes of constants and variables, all defined bits are used.
// The tool logic synthesizer will warn that constants are 32-bit by default and it has
// to work out the impact for you. Yet writing it like this makes the code easier to
// understand in this simple example.
always @(posedge CLOCK)
begin
if(RESET)
count <= 0;
else
count <= count + 1;
end

// Connect the output
assign MSB = count[19];

//-----------------------------------------------------------------------------------------------
// End of module
//-----------------------------------------------------------------------------------------------

endmodule