Introduction: INTRODUCTION TO FPGAS USING TINY FPGA BX!

About: Electronics engineer, + Machine Learning and Cloud Computing. I am brewing a stew

Have you ever wondered that you can create your own microprocessor? Have you dreamed of creating digital circuits in software? All this is possible thanks to the FPGAs. With them, you can fully immerse yourself in the design of digital electronics.

From the creator (Xilinx) of the FPGA,
Field Programmable Gate Arrays (FPGAs) are semiconductor devices that are based around a matrix of configurable logic blocks (CLBs) connected via programmable interconnects.

Every FPGA is made up of a definite number of predefined resources with programmable interconnects to implement a reconfigurable digital circuit and I/O blocks to allow the circuit to access the outside world. FPGAs can be configured by the user of the device rather than the creators of FPGA.

Digital circuits are made up of 3 elements: logic gates to manipulate bits, flip-flops to store them and cables to connect the components and transport the bits. These form the basis for digital circuitry.

The Configurable Logic Blocks (CLBs) aka slices or logic cells are the basic logic unit of an FPGA. CLBs are made up of 2 basic components: flip-flops and lookup tables (LUTs). The CLBs allow the FPGA to perform arithmetic and memory storage operations.

Flip Flops are binary shift registers used to synchronize logic and save logical states between clock cycles within an FPGA circuit. The flip flop will latch a 1/0 bit on its input and holds that value constant until the next clock edge.

It is a misconception to say that a FPGA is just a vast collection of individual Boolean logic gates. FPGAs are capable of implementing complex functions more efficiently with fixed modules (ROM,RAM module). It would be very time consuming implementing a ROM with only basic logic gates. However, all combinatorial logic (ANDs, ORs, NANDs, XORs, and so on) is implemented as truth tables within LUT memory. A truth table is a predefined list of outputs for every combination of inputs.

LUTs comprise of 1-bit memory cells (programmable to hold either ‘0’ or ‘1’) and a set of multiplexers. The output values of the truth table are stored in the SRAM cells of the LUT. Depending on the values sent to the LUT inputs of the multiplexers, 1 of the SRAM bits will be sent to the output.

Supplies

Tiny FPBA BX board X 1

VGA cable X 1

Jumper wires X 1

Step 1: Understanding the TinyFPGA BX

The TinyFPGA BX boards use Lattice Semiconductor’s ICE40LP8K FPGA. This FPGA is supported by a fully open source toolchain consisting of Yosys, ice-storm , and NextPNR.

The TinyFPGA BX consists of :

  • ICE40LP8K FPGA
    • 7,680 four-input look-up-tables
    • 128 KBit block RAM
    • Phase-Locked Loop
    • 41 user IO pins
  • An ultra low power 16MHz clock MEMs oscillator,
  • Onboard 3.3 V (300 mA) and 1.2 V (150 mA) LDO regulators,
  • An 8 MBit of SPI Flash QSPI mode,
  • A power LED and an onboard LED,
  • A reset button to reload the FPGA from flash,
  • Height: 1.4 inches, width: 0.7 inches,
  • Programming interface: USB 2.0 full-speed (12 Mbit/sec)

An interesting thing about TinyFPGA BX is that it does not include an FTDI FT2232H chip connected to the USB port. The FPGA contains all the hardware necessary to both program the FPGA, and to connect a basic serial port from your FPGA design to your host computer. ZipCPU explains in greater detail how the TinyFPGA requires FPGA design logic to communicate over the USB port here.

Step 2: HOW DO WE “PROGRAM” OR CONFIGURE FPGAS

Hardware Description languages like VHDL, Verilog, System Verilog are the most widespread languages used in FPGAs. Don’t be fooled by the similarity of high-level programming languages and HDLs. They are fundamentally different and require the hardware designer to think in “hardware”.

Before we move on to HDLs, let’s understand bitstream. The bitstream consists all the values for the FPGA connections which are grouped into a bit strip. The bitstream is transmitted over a SPI - Serial Peripheral Interface bus. FPGAs are volatile meaning they do not retain the bitstream when there is no electrical supply. As a result, there is a external , non-volatile memory ,called configuration memory that stores the bitstream. The FPGA reconfigures itself with the bitstream from the configuration memory when it boots up for the first time.

The HDL is used to describe a digital circuit in software, then moving on to simulating the circuit and finally generating the bitstream. This process is called synthesis.
There are many ways to configure a FPGA. They are Verilog / VHDL (standard tools), Migen (Python programming), IceStudio graphical schematic entry tool and Chisel- Scala programming.

IceStudio is a visual editor for open FPGA boards built with Electron framework. It is built on top of the Icestorm project using Apio.

Migen is a Python-based tool that automates further the VLSI design process. It enables hardware designers to take advantage of the richness of the Python language - object-oriented programming, function parameters, generators, operator overloading, libraries, etc. - to build well organized, reusable and elegant designs.

Chiselis a hardware design language that facilitates advanced circuit generation and design reuse for both ASIC and FPGA digital logic designs using Scala programming language.

Step 3: SOFTWARE INSTALLATION

This installation uses apio, an open-source ecosystem for open FPGA boards with Atom IDE. This creates a great beginner environment to start hacking FPGAs immediately.

Setup instructions for all major Operating systems (Windows, macOS and Linux based OS):

  1. Install Python 3 by following the setup instructions and check the “Add Python 3.xx to PATH” checkbox.
  2. Install APIO and tinyprog by opening a terminal and run the following commands:
pip install apio==0.4.0b5 tinyprog
apio install system scons icestorm iverilog 
apio drivers --serial-enable

These commands install APIO, tinyprog, as well as all of the necessary tools to actually program the FPGA. On Unix systems, you may need to add yourself to the dialout group in order for your user to be able to access serial ports. You can do that by running:

sudo usermod -a -G dialout $USER

Connect your TinyFPGA BX board(s) and make sure the bootloader is up to date by running the following command:

tinyprog --update-bootloader

This command will check for bootloader updates for all of the connected boards. This is important to do to ensure your boards have the latest bootloaders with any known bugs fixed.

3) Download and install Atom. The authors of APIO have created the APIO-IDE plugin that enables APIO to be used from within Atom. Install the following package apio-ide. Click yes for any dependencies. Ignore any warnings about the APIO version.

If you want to use ICEstudio, download at here https://icestudio.io/

Congratulations, you are on your way to becoming a whizz in digital design!

Step 4: Try It Out !

Ok enough talk, show me something !

Blinky example

Let’s get started with a Hello World example :

  1. Copy the apio template project from the TinyFPGA BX Repository and rename it anything.
  2. Open your newly copied template project using atom text editor.
  3. From the “Apio” menu, select “Upload”. The project will automatically be built and uploaded to the TinyFPGA BX board.
  4. If everything is working as it should, you should see the user LED on the board blinking a “SOS” in morse code.

VGA Example - Pong game

This is a more advanced example but it will be an exciting one!

Components required:

  1. TinyFPGA BX board or another FPGA compatible board X 1
  2. 270 ohm (1/4 W) resistors X 3
  3. VGA female connector for printed circuit board (optional) X 1

  4. VGA display monitor X 1

This is the arrangement on the VGA connector. We are only interested in 6 pins.

  1. R , G , B : (Red, Green, Blue) They are the 3 pins through which an analogue signal of 75ohm and 0.7v enters, representing the 3 colours: red, green and blue.
  2. HS: Horizontal Sync Digital Signal
  3. VS : Vertical Sync Digital Signal
  4. GND

Note: You only have to connect one of the ground pins ( GND ), because they are connected internally. I have chosen pin 5 of the VGA connector.

Upload the code to the Tiny FPGA BX and play a game of pong. Have fun!

// top.v
// Pong VGA game
// (c) fpga4fun.com

module pong(clk_16, vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B, quadA, quadB, USBPU);
input clk_16;
output vga_h_sync, vga_v_sync, vga_R, vga_G, vga_B;
input quadA, quadB;
output USBPU;

wire inDisplayArea;
wire [9:0] CounterX;
wire [8:0] CounterY;
wire locked, clk;

assign USBPU = 0;

SB_PLL40_CORE #(
                .FEEDBACK_PATH("SIMPLE"),
                .DIVR(4'b0000),         // DIVR =  0
                .DIVF(7'b0110001),      // DIVF = 49
                .DIVQ(3'b101),          // DIVQ =  5
                .FILTER_RANGE(3'b001)   // FILTER_RANGE = 1
        ) uut (
                .LOCK(locked),
                .RESETB(1'b1),
                .BYPASS(1'b0),
                .REFERENCECLK(clk_16),
                .PLLOUTCORE(clk)
                );


hvsync_generator syncgen(.clk(clk), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync),
  .inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY));

/////////////////////////////////////////////////////////////////
reg [8:0] PaddlePosition;
reg [2:0] quadAr, quadBr;
always @(posedge clk) quadAr <= {quadAr[1:0], quadA};
always @(posedge clk) quadBr <= {quadBr[1:0], quadB};

always @(posedge clk)
if(quadAr[2] ^ quadAr[1] ^ quadBr[2] ^ quadBr[1])
begin
	if(quadAr[2] ^ quadBr[1])
	begin
		if(~&PaddlePosition)        // make sure the value doesn't overflow
			PaddlePosition <= PaddlePosition + 1;
	end
	else
	begin
		if(|PaddlePosition)        // make sure the value doesn't underflow
			PaddlePosition <= PaddlePosition - 1;
	end
end

/////////////////////////////////////////////////////////////////
reg [9:0] ballX;
reg [8:0] ballY;
reg ball_inX, ball_inY;

always @(posedge clk)
if(ball_inX==0) ball_inX <= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16);

always @(posedge clk)
if(ball_inY==0) ball_inY <= (CounterY==ballY); else ball_inY <= !(CounterY==ballY+16);

wire ball = ball_inX & ball_inY;

/////////////////////////////////////////////////////////////////
wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59);
wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27);
wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself

reg ResetCollision;
always @(posedge clk) ResetCollision <= (CounterY==500) & (CounterX==0);  // active only once for every video frame

reg CollisionX1, CollisionX2, CollisionY1, CollisionY2;
always @(posedge clk) if(ResetCollision) CollisionX1<=0; else if(BouncingObject & (CounterX==ballX   ) & (CounterY==ballY+ 8)) CollisionX1<=1;
always @(posedge clk) if(ResetCollision) CollisionX2<=0; else if(BouncingObject & (CounterX==ballX+16) & (CounterY==ballY+ 8)) CollisionX2<=1;
always @(posedge clk) if(ResetCollision) CollisionY1<=0; else if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY   )) CollisionY1<=1;
always @(posedge clk) if(ResetCollision) CollisionY2<=0; else if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY+16)) CollisionY2<=1;

/////////////////////////////////////////////////////////////////
wire UpdateBallPosition = ResetCollision;  // update the ball position at the same time that we reset the collision detectors

reg ball_dirX, ball_dirY;
always @(posedge clk)
if(UpdateBallPosition)
begin
	if(~(CollisionX1 & CollisionX2))        // if collision on both X-sides, don't move in the X direction
	begin
		ballX <= ballX + (ball_dirX ? -1 : 1);
		if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0;
	end

	if(~(CollisionY1 & CollisionY2))        // if collision on both Y-sides, don't move in the Y direction
	begin
		ballY <= ballY + (ball_dirY ? -1 : 1);
		if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
	end
end

/////////////////////////////////////////////////////////////////
wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]);
wire G = BouncingObject | ball;
wire B = BouncingObject | ball;

reg vga_R, vga_G, vga_B;
always @(posedge clk)
begin
	vga_R <= R & inDisplayArea;
	vga_G <= G & inDisplayArea;
	vga_B <= B & inDisplayArea;
end

endmodule
<p>// hvsync_generator.v<br>
module hvsync_generator(clk, vga_h_sync, vga_v_sync, inDisplayArea, CounterX, CounterY);
input clk;
output vga_h_sync, vga_v_sync;
output inDisplayArea;
output [9:0] CounterX;
output [8:0] CounterY;</p><p>//////////////////////////////////////////////////
reg [9:0] CounterX;
reg [8:0] CounterY;
wire CounterXmaxed = (CounterX==10'h2FF);</p><p>always @(posedge clk)
if(CounterXmaxed)
	CounterX <= 0;
else
	CounterX <= CounterX + 1;</p><p>always @(posedge clk)
if(CounterXmaxed) CounterY <= CounterY + 1;</p><p>reg	vga_HS, vga_VS;
always @(posedge clk)
begin
	vga_HS <= (CounterX[9:4]==6'h2D); // change this value to move the display horizontally
	vga_VS <= (CounterY==500); // change this value to move the display vertically
end</p><p>reg inDisplayArea;
always @(posedge clk)
if(inDisplayArea==0)
	inDisplayArea <= (CounterXmaxed) && (CounterY<480);
else
	inDisplayArea <= !(CounterX==639);</p><p>assign vga_h_sync = ~vga_HS;
assign vga_v_sync = ~vga_VS;</p><p>endmodule</p>

Step 5: FAQ

There just aren’t enough hobbyist-friendly tutorials out there. All tutorials i can find are either about basic topics like blinking LEDs or they are way too advanced for beginners.

Look at the @Obijuan tutorial open-fpga-verilog-tutorial. This is the best tutorial out there, I have managed to learn Verilog in few weeks all thanks to Obijuan.

In FPGAs, hardware is now "software"! We have turned hardware into software!

One of the aspects that I am most passionate about in FPGAs is the possibility of creating your own microprocessor and making it work in the FPGA. Good Luck !

Thanks for reading, check out my blog at https://shankar-shiv.github.io/2020/05/14/Introduction-to-FPGAs/#understanding-fpgas for more projects using FPGAs.