
Michael Schröder sent us a link to his Gitlab repository
He managed to build RISC-V computer running Unix xv6 using only FOSS.
For hardware he choose our OSHW FPGA board iCE40HX8K-EVB
all fun projects at Olimex Ltd
20 Feb 2023 9 Comments
in fpga, open source, OSHW, risc-v Tags: computer, fpga, riscv, unix
Michael Schröder sent us a link to his Gitlab repository
He managed to build RISC-V computer running Unix xv6 using only FOSS.
For hardware he choose our OSHW FPGA board iCE40HX8K-EVB
17 Apr 2020 Leave a comment
in fpga, open source, OSHW, project Tags: cpu, fpga, icestorm, Open, oshw, Source
Michael Schröder sent us link to his project nand2tetris-13 yesterday.
He managed to build his own CPU, to write Assembler, Jack high level language, OS and then write Fibonacci demo and Pong game. All this done on Open Source Hardware FPGA board iCE40HX8K-EVB with MOD-LCD2.8RTP as display and FOSS tools IceStorm project. Keyboard is done by UART but my guess is that it could be easily implemented by iCE40-IO .
This is perfect project to teach students about so many things: Combinatory Logic, Sequential Logic, Computer Achitecture, Machine code to Assembler, High level language Compilers, simple OS and so on.
And the best here is that you can see this working on hardware not just boring lectures. Student can experiment with their own CPU and write applications like small games etc.
21 May 2018 1 Comment
in arduino, event, fpga, KiCAD, open source, OSHW, Retro computers, verilog Tags: conference, foss, fpga, hackathon, kicad, oshw, Plovdiv, risc-v, soldering, tuxcon
TuxCon 2018 conference about Free Open Source Software and Open Source Hardware will be June 9 and 10th in Plovdiv.
This is community driven event done by volunteers with the support of local IT companies and it’s totally free to participate.
In Saturday 9th of June in the Technical University building there will be two rooms, for lectures and workshops.
Hacking Risc-V core, implemented on iCE40 FPGA and playing with it’s instruction set and making small “monitor” program which allow you to enter programs written on machine code and executed on iCE40HX8K-EVB + iCE40-IO will be demonstrated, so if you want to touch and make your first program on RISC-V “computer” with VGA monitor and keyboard you may find Rangel Ivanov lecture interesting.
Plamen Vaisolov is retro computer maniac and keep working on them, implementing modern floppy disk emulators and other peripherials. He will share his experience with all these who miss Karateka and Load Runner 🙂
Neven Boyanov will talk about his experience with LoRA network and how to start on low budged.
Dimitar Gamishev has two interesting talks, one is for the Open Source Hardware and Software GPS car navigation he made. In the second he will speak about the home assistants like Google Home and Alexa, how to implement them with small Linux computer like OLinuXino and even with ESP32-Lyra and how to impress your girlfriend by switching on and off appliances and lights with your voice.
There will be KiCAD workshop where everyone (even with no knowledge) will learn how to install, configure and make small robot PCB with KiCAD. Then some general knowledges about PCB materials and how to prepare all files and send to PCB board house for manufacturing. How to select the components, footprints, good routing practices, DRC/ERC checks, Gerber generation and tips how to design your PCBs in way to be produced at lower cost. At the workshop will be our design engineers and you can talk to them directly about issues you encountered when worked with KiCAD or other CAD product.
In Sunday 10th of June we will have traditional Soldering workshop at Olimex training building, where we will assembly and program with Arduino small robot which can follow line or escape from labyrinth. This will be the same robot which PCB we will design in the KiCAD workshop in the previous day. We will go detailed through the program and study how modifications affect it.
The workshop with end with barbeque in Olimex backyard 🙂
We are looking forward to see you in Plovdiv soon!
07 Nov 2017 Leave a comment
in fpga, raspberrypi Tags: fpga, ice40, icestorm, Open, Source, tools
Andreas Seltenreich sent us message how he is using Raspberry PI to program his iCE40HX1K-EVB and we add to the wiki section.
22 Jun 2017 7 Comments
in fpga, KiCAD, OSHW Tags: board, fpga, ice40, oshw, risc-v
iCE40HX8K-EVB is upgrade of our iCE40HX1K-EVB with more logic cells FPGA so bigger Verilog projects could be synthesized.
GPIO1 34 pin bus is same as iCE40HX1K-EVB and all DAC, ADC, IO, etc modules are compatible, on top of this there are plenty of additional GPIOs on 4 40 pin 0.05″ step connectors.
512KB fast SRAM is on board, with iCE40-IO board you can connect VGA monitor and PS2 keyboard, so you can make your own custom CPU (for instance RISC-V) computer with monitor and keyboard.
19 Jul 2016 4 Comments
in fpga Tags: fpga, memory, video
The previous blog about video generation is here.
For the weekend I posted Verilog Programming challenge to change the shape of the square to circle but unfortunately we didn’t got single solution 🙂
I guess you guys also learn yet and this was hard challenge to solve 🙂
In this post I will show you that this is not so hard.
Let's see again the source from the older post, here is where square is generated: if(c_col > l_sq_pos_x && c_col < r_sq_pos_x && c_row > u_sq_pos_y && c_row < d_sq_pos_y) begin //generate blue square vga_r_r <= 0; vga_g_r <= 0; vga_b_r <= 7; end
What this codes does? If the video coordinates are within square we assign some color to VGA output.
This is the part we should modify to draw circle but how?
We will use for this purpose video memory and in this memory we will draw circle then when the video beam is in this area it will draw the content of the video memory.
Let’s define the memory:
reg [19:0] sq_figure [0:19];
This defines 20 registers which are 20 bit wide, so we define 20×20 bit video memory which we will use to draw image on the VGA screen.
We also need two counters which to count XY beam position within the square area where we will draw:
wire [4:0] sq_fig_x; wire [4:0] sq_fig_y; assign sq_fig_x = c_col - l_sq_pos_x; // our figure's x axis when in square boundary assign sq_fig_y = c_row - u_sq_pos_y; // our figure's y axis when in square boundary
This video memory registers must be load with the ball image, we will do this during the reset state where we initialize other registers too:
if(reset == 1) begin //while RESET is high init counters sq_figure[0][19:0] <= 20'b00000000000000000000; sq_figure[1][19:0] <= 20'b00000001111100000000; sq_figure[2][19:0] <= 20'b00000111111111000000; sq_figure[3][19:0] <= 20'b00011111111111110000; sq_figure[4][19:0] <= 20'b00111111111111111000; sq_figure[5][19:0] <= 20'b00111111111111111000; sq_figure[6][19:0] <= 20'b01111111111111111100; sq_figure[7][19:0] <= 20'b01111111111111111100; sq_figure[8][19:0] <= 20'b11111111111111111110; sq_figure[9][19:0] <= 20'b11111111111111111110; sq_figure[10][19:0] <= 20'b11111111111111111110; sq_figure[11][19:0] <= 20'b11111111111111111110; sq_figure[12][19:0] <= 20'b11111111111111111110; sq_figure[13][19:0] <= 20'b01111111111111111100; sq_figure[14][19:0] <= 20'b01111111111111111100; sq_figure[15][19:0] <= 20'b00111111111111111000; sq_figure[16][19:0] <= 20'b00111111111111111000; sq_figure[17][19:0] <= 20'b00011111111111110000; sq_figure[18][19:0] <= 20'b00000111111111000000; sq_figure[19][19:0] <= 20'b00000001111100000000; c_hor <= 0; c_ver <= 0; vga_hs_r <= 1; vga_vs_r <= 0; c_row <= 0; c_col <= 0; end
Now we have loaded our video memory with the image of the ball and we should add code which draw it:
if(c_col > l_sq_pos_x && c_col < r_sq_pos_x && c_row > u_sq_pos_y && c_row < d_sq_pos_y) begin //generate picture from the video memory if(sq_figure[sq_fig_y][sq_fig_x] == 1) begin vga_r_r <= 7; vga_g_r <= 0; vga_b_r <= 7; end else begin vga_r_r <= 0; vga_g_r <= 0; vga_b_r <= 0; end
What we do here? If the video memory is 1 we draw pink dot otherwise black on the screen. Is it really so simple? Let’s compile and see what happens!
Wow it works! You see the picture above.
Let’s copy this code it to example_4.v for further reference.
Now when we have video memory we can change it content and make animations by change dynamically the image inside the video memory.
To do this we have to load the registers with different ‘picture’.
Before we do this first let’s fix something annoying with the keyboard handling which bothers me. When I press up key ball start to move up and I can’t change the direction until it hits the wall.
This is because we commented the code for key release, so the arrow flags are clear press key flag is to hit the wall.
Let’s make this modification:
if(c_row == 1 && c_col == 1) begin //once per video frame if(u_arr) begin if (sq_pos_y > square_size) begin sq_pos_y <= sq_pos_y - 1; end else begin u_arr <= 0; d_arr <= 1; end end; if(d_arr) begin if (sq_pos_y < (v_pixels - 1 - square_size)) begin sq_pos_y <= sq_pos_y + 1; end else begin d_arr <= 0; u_arr <= 1; end end; if(l_arr) begin if (sq_pos_x > square_size) begin sq_pos_x <= sq_pos_x - 1; end else begin l_arr <= 0; r_arr <= 1; end end; if(r_arr) begin if (sq_pos_x < (h_pixels - 1 - square_size)) begin sq_pos_x <= sq_pos_x + 1; end else begin r_arr <= 0; l_arr <= 1; end end; end
Great! Now I can change up down left right direction on the fly while ball is moving, but the ball after a while start moving only diagonally because up down do not change left right direction 🙂
Let’s save current example to example_5.v for further reference and try to modify one more time the code.
Let’s make if ball is moving up but also in any of X direction pressing second time up to make it move stright up and same for other keys.
First we will need to add de-bounce time as once we press the key each frame this means 25 per second key will be scanned .
We will add de-bounce timer:
reg [19:0] arr_timer; // delay between key check
We define 20 bit counter, which will be clocked at 25Mhz and will overflow after 0.041(6) seconds, we will check keys only when this counter oveflow:
arr_timer <= arr_timer + 1; if(arr_timer == 0) begin if(ps2_data_reg_prev == 8'he0) begin //0xE0 means key pressed if(ps2_data_reg == 8'h75) begin if(u_arr == 1) begin u_arr <= 1; //0x75 up key d_arr <= 0; l_arr <= 0; r_arr <= 0; end else begin u_arr <= 1; //0x75 up key d_arr <= 0; end ps2_data_reg <= 0; end if(ps2_data_reg == 8'h6b) begin if(l_arr == 1) begin l_arr <= 1; //0x6B left key r_arr <= 0; u_arr <= 0; d_arr <= 0; end else begin l_arr <= 1; //0x6B left key r_arr <= 0; end ps2_data_reg <= 0; end if(ps2_data_reg == 8'h72) begin if(d_arr == 1) begin d_arr <= 1; //0x72 down key u_arr <= 0; l_arr <= 0; r_arr <= 0; end else begin d_arr <= 1; //0x72 down key u_arr <= 0; end ps2_data_reg <= 0; end if(ps2_data_reg == 8'h74) begin if(r_arr == 1) begin r_arr <= 1; //0x74 right key l_arr <= 0; u_arr <= 0; d_arr <= 0; end else begin r_arr <= 1; //0x74 right key l_arr <= 0; end ps2_data_reg <= 0; end end end
When u_arr is 0 and it is set 1 we just clear d_arr, but if u_arr already has been 1 and we set u_arr again we clear l_arr and r_arr too.
This way if ball moves diagonally up and we press the up key again it will go straight up, same for the other directions too.
Now everything is perfect! Let save it as example_6.v
One last mod: Let’s use arr_timer to dynamically change the video memory, we add this code
if(arr_timer == 0) begin sq_figure[8][19:0] <= sq_figure[8][19:0] ^ 20'b00000001111000000000; sq_figure[9][19:0] <= sq_figure[9][19:0] ^ 20'b00000001111000000000; sq_figure[10][19:0] <= sq_figure[10][19:0] ^ 20'b00000001111000000000; sq_figure[11][19:0] <= sq_figure[11][19:0] ^ 20'b00000001111000000000;
What we do here, each time arr_timer overflow (25 000 000 / 2^20) i.e. each 0.0416 seconds we xor few lines of the video memory and thus make square hole inside the ball.
We compile and see that the ball animation works but too fast. Let’s change the arr_timer to 21 bit and slow town the blinking.
The new code is saved as example_7.v
What we learn so far? How to define memory in Verilog and how to display this memory content at given coordinates.
All changes are now uploaded at GitHub.
What Next?
In the next Tutorial we will use iCE40HX1K-EVB SRAM memory as video memory to generate video with resolution 640×480 pixels 512 colors.
Then we will teach you how to use the 100Mhz iCE40-ADC and iCE40-DAC.
Using iCE40HX1K-EVB as 100Msps Logic Analizer for signals with voltage levels from 1.65V to 5.5V using iCE40-DIO and connected to Sigrok Pulseview and how you can sniff CAN, USB, RFID, I2C, I2S, JTAG, MIDI, MODBUS, SPDIF, SPI, and all other 66 decoding protocols which Sigrok supports.
18 Jul 2016 2 Comments
in fpga Tags: fpga, ice40, icecube2, windows
Windows is still most popular desktop platform and many people use it. So far all our blogs were how to use iCE40HX1K-EVB with Linux and IceStorm Free and Open Source tools.
What are the options for Windows users? I can think of three options at glance:
Perhaps there are other ways to use Linux software under Windows, but these three are probably most popular.
Of course Windows users have one more options and this is the original Lattice FPGA software called iCEcube2. Here we will go through installation process of these tools too and how to use them with iCE40HX1K-EVB.
Intalling the iCEcube2 IDE and generating bit file.
First you have to go to Lattice Semiconductor web site and to register. You will have to fill lot of personal and work info and apply for registration then waiting your account to be created and you will get e-mail which you have to use to activate it. The account registration may take from few hours up to 1 day.
Once your account is activated you can go to Products -> Software Tools -> iCEcube2 and to download it.
To install it you will need to apply for Free License including information for the MAC address on your computer where the software will run! You can see the difference between IceStom and iCEcube2 in the first you have no restrictions whatsoever, the only free thing in the latter is that it’s cost-free you have no other freedom. Lattice may shut you down from using it at any time. Why is this? Edmund explained it well in his lecture at TuxCon 2016. Lattice didn’t made these tools, they buy from 3rd party, this is why they should have way to count their users to may pay licensee fees to the software vendor (you will see restriction iCEcube2 to be licensed by Cadence employees probably to not abuse Lattice with many licensees which Lattice pays for). Lattice make money only from selling chips, and generates loss from users who use small amount of chips like startups, small developers, people who learn FPGAs, as they use the tools but do not buy many chips which to justify the development tools price they pay to the vendor. So the tools they provide are named to be “free” but they are only cost-free, sponsored by Lattice.
When download it you should select Windows version:
Click on the Download button and select save the .zip file.
While your zip file is downloading go back to the iCEcube2 page and request you cost-free license.
Enter your MAC address and click on Generate License. If you enter a wrong MAC address iCEcube2 will not be able to start. You can request a new license with the correct MAC address.
To learn what your MAC address is run ‘cmd’ and type ipconfig /all .
Go to your email and you should see a new email, with the license file attached (license.dat). Save it to a convenient location.
After the iCEcube2 .zip file has finished downloading simply unzip it and run the .exe inside. During the installation you must provide the path to the license file:
Then the installation is fairly a straight-forward process and you can run the iCEcube2 IDE.
Double click on New Project on the left panel and fill the form and click Next.
You can download some examples for the iCE40HX1K-EVB from GitHub. The example should have at least two files: one is the Verilog (or VHDL) code and the other is the .pcf pin constraint file.
Click on Next and Finish, we will add the design and constraint files later manually.
First add the Verilog file: right-click on Design Files and select the .v file and add it to the project using the >> button or just double-clicking on it and click OK.
Then click on Constraint Files and add the .sdc file:
And then right-click on Constraint Files under P&R flow for the .pcf file:
Finally click on Tool->Run All:
The synthesis process should start and at the end will look like this:
From all the 3MB files that are generated only the .bin file is what we will use to upload to the FPGA.
You can compare the process with IceStorm where you only have to have two files and simple issue one command ‘make’ compared to the claimed to be easier to use windows interface to create the simple blink LED project.
There are two types of programming: one is programming the iCE40HX1K-EVB on-board flash memory, which the FPGA reads every time it resets, and the other is directly programming (which often is described as configuring) the FPGA (the configuration is lost after power down / reset therefore).
Olimex provides a programming tool for Windows that uses the Olimexino-32U4 to program the SPI flash of the iCE40HX1K-EVB. First you need to program Olimexino-32U4 with the iceprogduino sketch as explained here. Arduino works under Windows too so the procedure is same as with Linux.
Then you need winiceprogduino.exe which programs the FPGA with the previously generated bin file. winiceprogduino is same as the linux iceprogduino but re-compiled for Windows with the DevC++ compiler.
You can download project, sources and pre-compiled version from GitHub. winiceprogduino takes COM port (the port that is assigned from Windows for Olimexino-32U4) and target file (the bin file we generated earlier with iCEcube2) as arguments and sends the file to the Olimexino-32U4 which programs it to the SPI flash or directly configures the FPGA, depending on the options you selected.
You can run winiceprogduino.exe and will see all options:
When you plug Olimexino-32U4 to your computer go to Device Manager and see which COM port is assigned to it. In our case it was COM10 and to program the blink led bin we should type:
FPGA will be programmed and you will see the LED1 and LED2 blinking.
Now you are set to create your own projects with iCEcube2. Have fun!
13 Jul 2016 8 Comments
in fpga, open source, verilog Tags: foss, fpga, generator, oshw, verilog, video
iCE40-IO is Open Source Hardware snap-to module for iCE40HX1K-EVB which adds VGA, PS2 and IrDA transciever.
In this tutorial you will learn how to generate VGA video signals, how to capture PS2 keys and how to move object on the video screen.
Here is my setup:
I have iCE40HX1K-EVB snap to iCE40-IO with PS2 keboard and VGA connected to it and OLIMEXINO-32U4 as programmer
The tutorial project is on GitHub. Let’s first see example_0.v
Yesterday after sharing my experience with Verilog to silently define signals which you could have type by mistake, there was comment by Andrew Zonenberg, who wrote that you can tell Verilog to consider this error by adding “`default_nettype none” as your first line code. I check and it works fine, so I will use it in all my further sources 🙂 Thanks for the tip Andrew!
The code starts with:
`default_nettype none //disable implicit definitions by Verilog module top( //top module and signals wired to FPGA pins CLK100MHz, vga_r, vga_g, vga_b, vga_hs, vga_vs, ps2_clk, ps2_data );
here we define top module and what physical signals we will use, these are the CLK100Mhz, VGA R,G,B, H-sync, V-sync, ps2 clock and data
then we must define each of them:
input CLK100MHz; // Oscillator input 100Mhz output [2:0] vga_r; // VGA Red 3 bit output [2:0] vga_g; // VGA Green 3 bit output [2:0] vga_b; // VGA Blue 3 bit output vga_hs; // H-sync pulse output vga_vs; // V-sync pulse input ps2_clk; // PS2 clock input ps2_data; // PS2 data
as you can see VGA R,G,B signals are 3 bit registers, this way we defini VGA to have 9bit color or 512 different colors
the next part use new keyword parameter, this is how the constants are defined in Verilog:
parameter h_pulse = 96; //H-SYNC pulse width 96 * 40 ns (25 Mhz) = 3.84 uS parameter h_bp = 48; //H-BP back porch pulse width parameter h_pixels = 640; //H-PIX Number of pixels horisontally parameter h_fp = 16; //H-FP front porch pulse width parameter h_pol = 1'b0; //H-SYNC polarity parameter h_frame = 800; //800 = 96 (H-SYNC) + 48 (H-BP) + 640 (H-PIX) + 16 (H-FP) parameter v_pulse = 2; //V-SYNC pulse width parameter v_bp = 33; //V-BP back porch pulse width parameter v_pixels = 480; //V-PIX Number of pixels vertically parameter v_fp = 10; //V-FP front porch pulse width parameter v_pol = 1'b1; //V-SYNC polarity parameter v_frame = 525; // 525 = 2 (V-SYNC) + 33 (V-BP) + 480 (V-PIX) + 10 (V-FP) parameter square_size = 10; //size of the square we will move parameter init_x = 320; //initial square position X parameter init_y = 240; //initial square position Y
for VGA timing we will use 25Mhz clock which is made by division by 4 of CLK100Mhz:
reg [1:0] clk_div; // 2 bit counter wire vga_clk; assign vga_clk = clk_div[1]; // 25Mhz clock = 100Mhz divided by 2-bit counter always @ (posedge CLK100MHz) begin // 2-bt counter ++ on each positive edge of 100Mhz clock clk_div <= clk_div + 2'b1; end
vga_clk is the bit2 of clk_div which is incrementing on each positive edge of 100Mhz clock
then we define the registers which will hold the VGA signals:
reg [2:0] vga_r_r; //VGA color registers R,G,B x 3 bit reg [2:0] vga_g_r; reg [2:0] vga_b_r; reg vga_hs_r; //H-SYNC register reg vga_vs_r; //V-SYNC register assign vga_r = vga_r_r; //assign the output signals for VGA to the VGA registers assign vga_g = vga_g_r; assign vga_b = vga_b_r; assign vga_hs = vga_hs_r; assign vga_vs = vga_vs_r;
we do want the video generation to start after some time not immediately, and for this we will use two signals:
reg [7:0] timer_t = 8'b0; // 8 bit timer with 0 initialization reg reset = 1;
8 bit timer will make the necessary delay, reset is internal signal and have nothing in common with the reset button on the board
these registers will hold info where the “video beam” is when the video is generated, we need two of them as one will hold the complete frame even with some of “invisible” video frame, the other just the visible part
reg [9:0] c_row; //complete frame register row reg [9:0] c_col; //complete frame register colum reg [9:0] c_hor; //visible frame register horisontally reg [9:0] c_ver; //visible frame register vertically
this signal flags if the display is enabled or disabled
reg disp_en; //display enable flag
these registers will hold the center coordinates of the visible square we draw on the screen:
reg [9:0] sq_pos_x; //position of square center X, Y reg [9:0] sq_pos_y;
these registers will hold the upper left and down right coordinates of the square we draw:
wire [9:0] l_sq_pos_x; //upper left and down right corners of the square wire [9:0] r_sq_pos_x; wire [9:0] u_sq_pos_y; wire [9:0] d_sq_pos_y; assign l_sq_pos_x = sq_pos_x - square_size; assign r_sq_pos_x = sq_pos_x + square_size; assign u_sq_pos_y = sq_pos_y - square_size; assign d_sq_pos_y = sq_pos_y + square_size;
the next registers are for reading the PS2 keyboard:
reg [3:0] ps2_cntr; // 4-bit PS2 clock counter reg [7:0] ps2_data_reg; // 8-bit PS2 data register reg [7:0] ps2_data_reg_prev; // previous 8-bit PS data register reg [7:0] ps2_data_reg_prev1; // previous previous 8-bit data register reg [10:0] ps2_dat_r; // 11-bit complete PS2 frame register reg [1:0] ps2_clk_buf; // PS2 clock buffer wire ps2_clk_pos; // PS2 positive edge detected signal reg u_arr = 0; //PS2 arrow keys detect flags reg l_arr = 0; reg d_arr = 0; reg r_arr = 0;
the 4-bit counter is for PS2 clock, the three data registers hold three sequential key codes as some keys are transmitted as two bytes when press and three when released
ps2_clk_buf is used to detect rising edge of the PS2 clock:
assign ps2_clk_pos = (ps2_clk_buf == 2'b01); // edge detector positive edge is when the buffer is '10'
25Mhz clock is used to detect PS2 clock and data:
always @ (posedge vga_clk) begin // on each positive edge at 25Mhz clock ps2_clk_buf[1:0] <= {ps2_clk_buf[0], ps2_clk}; // shift old value left and get current value of ps2_clk if(ps2_clk_pos == 1) begin // on positive edge ps2_cntr <= ps2_cntr + 1; if(ps2_cntr == 10) begin // when we got 10 clocks save the PS2 data to ps2_data_reg, // ps2_data_reg_prev and ps2_data_reg_prev1 ps2_cntr <= 0; // so we have last 3 data values captured from PS2 keyboard ps2_data_reg[7] <= ps2_dat_r[0]; ps2_data_reg[6] <= ps2_dat_r[1]; ps2_data_reg[5] <= ps2_dat_r[2]; ps2_data_reg[4] <= ps2_dat_r[3]; ps2_data_reg[3] <= ps2_dat_r[4]; ps2_data_reg[2] <= ps2_dat_r[5]; ps2_data_reg[1] <= ps2_dat_r[6]; ps2_data_reg[0] <= ps2_dat_r[7]; ps2_data_reg_prev <= ps2_data_reg; ps2_data_reg_prev1 <= ps2_data_reg_prev; end ps2_dat_r <= {ps2_dat_r[9:0], ps2_data}; // data shift left end
at this point we have detected when the PS2 keyboard start sending data and captured the transmitted data
here is where we detect is left, right, up and down keys are pressed:
if(ps2_data_reg_prev1 == 8'he0 && ps2_data_reg_prev == 8'hf0) begin // 0xE0 0xF0 sequence means key released if(ps2_data_reg == 8'h75) begin u_arr <= 0; //0x75 up key end else if(ps2_data_reg == 8'h6b) begin l_arr <= 0; //0x6B left key end else if(ps2_data_reg == 8'h72) begin d_arr <= 0; //0x72 down key end else if(ps2_data_reg == 8'h74) begin r_arr <= 0; //0x74 right key end end if(ps2_data_reg_prev == 8'he0) begin //0xE0 means key pressed if(ps2_data_reg == 8'h75) begin u_arr <= 1; //0x75 up key end else if(ps2_data_reg == 8'h6b) begin l_arr <= 1; //0x6B left key end else if(ps2_data_reg == 8'h72) begin d_arr <= 1; //0x72 down key end else if(ps2_data_reg == 8'h74) begin r_arr <= 1; //0x74 right key end end end
Now let’s generate the video signal:
always @ (posedge vga_clk) begin //25Mhz clock if(timer_t > 250) begin // generate 10 uS RESET signal reset <= 0; end else begin reset <= 1; //while in reset display is disabled, suare is set to initial position timer_t <= timer_t + 1; disp_en <= 0; sq_pos_x <= init_x; sq_pos_y <= init_y; end
with timer_t we generate initial 10 uS RESET signal where display is not active and we load initial XY coordinates in the middle of the visible area
this code updates current beam position:
if(reset == 1) begin //while RESET is high init counters c_hor <= 0; c_ver <= 0; vga_hs_r <= 1; vga_vs_r <= 0; c_row <= 0; c_col <= 0; end else begin // update current beam position if(c_hor < h_frame - 1) begin c_hor <= c_hor + 1; end else begin c_hor <= 0; if(c_ver < v_frame - 1) begin c_ver <= c_ver + 1; end else begin c_ver <= 0; end end end
H-sync and V-sync generation:
if(c_hor < h_pixels + h_fp + 1 || c_hor > h_pixels + h_fp + h_pulse) begin // H-SYNC generator vga_hs_r <= ~h_pol; end else begin vga_hs_r <= h_pol; end if(c_ver < v_pixels + v_fp || c_ver > v_pixels + v_fp + v_pulse) begin //V-SYNC generator vga_vs_r <= ~v_pol; end else begin vga_vs_r <= v_pol; end if(c_hor < h_pixels) begin //c_col and c_row counters are //updated only in the visible time-frame c_col <= c_hor; end if(c_ver < v_pixels) begin c_row <= c_ver; end if(c_hor < h_pixels && c_ver < v_pixels) begin //VGA color signals are //enabled only in the visible time frame disp_en <= 1; end else begin disp_en <= 0; end
now to draw the read frame, blue square:
if(disp_en == 1 && reset == 0) begin if(c_row == 0 || c_col == 0 || c_row == v_pixels-1 || c_col == h_pixels-1) begin //generate red frame with size 640x480 vga_r_r <= 7; vga_g_r <= 0; vga_b_r <= 0; end else if(c_col > l_sq_pos_x && c_col < r_sq_pos_x && c_row > u_sq_pos_y && c_row < d_sq_pos_y) begin //generate blue square vga_r_r <= 0; vga_g_r <= 0; vga_b_r <= 7; end else begin //everything else is black vga_r_r <= 0; vga_g_r <= 0; vga_b_r <= 0; end end else begin //when display is not enabled everything is black vga_r_r <= 0; vga_g_r <= 0; vga_b_r <= 0; end
you can change the colors by editing the RGB values above
once per frame update the square position depend on key pressed:
if(c_row == 1 && c_col == 1) begin //once per video frame if(u_arr) begin sq_pos_y <= sq_pos_y - 1; end; if(d_arr) begin sq_pos_y <= sq_pos_y + 1; end; if(l_arr) begin sq_pos_x <= sq_pos_x - 1; end; if(r_arr) begin sq_pos_x <= sq_pos_x + 1; end; end
now let’s save the code as example.v, synthesize and program.
Here is what we see:
when we press and hold arrow keys the square is moving across the screen yey!
but there is problem if we reach the end of frame the square go outside it 🙂
How we can fix this?
Let’s go again to the code which describe the position update, obviously we have to add another if with checking if the square is at the frame ends:
if(c_row == 1 && c_col == 1) begin //once per video frame if(u_arr) begin if (sq_pos_y > square_size) begin sq_pos_y <= sq_pos_y - 1; end end; if(d_arr) begin if (sq_pos_y < (v_pixels - 1 - square_size)) begin sq_pos_y <= sq_pos_y + 1; end end; if(l_arr) begin if (sq_pos_x > square_size) begin sq_pos_x <= sq_pos_x - 1; end end; if(r_arr) begin if (sq_pos_x < (h_pixels - 1 - square_size)) begin sq_pos_x <= sq_pos_x + 1; end end; end
now the square will never go outside! let’s save the code (it’s also saved on GitHub as example_1.v) and synthesize and program:
OK, what else we can change? To keep the button pressed all the time to move the square is boring, let’s make it to move once we just press and release the key without need to keep it all the time pressed.
we can do this by commenting this code which clears the key flags:
/* if(ps2_data_reg_prev1 == 8'he0 && ps2_data_reg_prev == 8'hf0) begin // 0xE0 0xF0 sequaence means key released if(ps2_data_reg == 8'h75) begin u_arr <= 0; //0x75 up key end else if(ps2_data_reg == 8'h6b) begin l_arr <= 0; //0x6B left key end else if(ps2_data_reg == 8'h72) begin d_arr <= 0; //0x72 down key end else if(ps2_data_reg == 8'h74) begin r_arr <= 0; //0x74 right key end end */
Now even when you press the key once the square keep moving in this direction until hit the ‘wall’ then stops! This code is saved on GitHub as example_2.v.
Can we make it bounce? Sure we can, we just have to update key status with reverse key when the square hit the wall:
if(c_row == 1 && c_col == 1) begin //once per video frame if(u_arr) begin if (sq_pos_y > square_size) begin sq_pos_y <= sq_pos_y - 1; end else begin // change direction when hit wall u_arr <= 0; d_arr <= 1; end end; if(d_arr) begin if (sq_pos_y < (v_pixels - 1 - square_size)) begin sq_pos_y <= sq_pos_y + 1; end else begin d_arr <= 0; u_arr <= 1; end end; if(l_arr) begin if (sq_pos_x > square_size) begin sq_pos_x <= sq_pos_x - 1; end else begin l_arr <= 0; r_arr <= 1; end end; if(r_arr) begin if (sq_pos_x < (h_pixels - 1 - square_size)) begin sq_pos_x <= sq_pos_x + 1; end else begin r_arr <= 0; l_arr <= 1; end end; end
Let’s save and compile! What? We got error!
example.blif:1750: fatal error: net `d_arr' has multiple drivers Makefile:11: recipe for target 'example.asc' failed make: *** [example.asc] Error 1
What does this means? d_arr register where we store the key direction has multiply drivers! Looking in the code we see that we assign d_arr in two different always blocks.
In FPGA all processes are performed in parallel, so if we assign one signal in two different blocks we will never know which assignment when is performed and this is considered error in the behavior description.
What we see is that both always blocks are executed on positive edge of vga_clk, so we can just merge them by copy:
ps2_clk_buf[1:0] <= {ps2_clk_buf[0], ps2_clk}; // shift old value left and get current value of ps2_clk if(ps2_clk_pos == 1) begin // on positive edge ps2_cntr <= ps2_cntr + 1; if(ps2_cntr == 10) begin // when we got 10 clocks save the PS2 data to ps2_data_reg, ps2_data_reg_prev and ps2_data_reg_prev1 ps2_cntr <= 0; // so we have last 3 data values captured from PS2 keyboard ps2_data_reg[7] <= ps2_dat_r[0]; ps2_data_reg[6] <= ps2_dat_r[1]; ps2_data_reg[5] <= ps2_dat_r[2]; ps2_data_reg[4] <= ps2_dat_r[3]; ps2_data_reg[3] <= ps2_dat_r[4]; ps2_data_reg[2] <= ps2_dat_r[5]; ps2_data_reg[1] <= ps2_dat_r[6]; ps2_data_reg[0] <= ps2_dat_r[7]; ps2_data_reg_prev <= ps2_data_reg; ps2_data_reg_prev1 <= ps2_data_reg_prev; end ps2_dat_r <= {ps2_dat_r[9:0], ps2_data}; // data shift left end if(ps2_data_reg_prev == 8'he0) begin //0xE0 means key pressed if(ps2_data_reg == 8'h75) begin u_arr <= 1; //0x75 up key end else if(ps2_data_reg == 8'h6b) begin l_arr <= 1; //0x6B left key end else if(ps2_data_reg == 8'h72) begin d_arr <= 1; //0x72 down key end else if(ps2_data_reg == 8'h74) begin r_arr <= 1; //0x74 right key end end
after the video generation and delete of first always block. In GitHub this code is saved as example_3.v
Now code is synthesized and we can program the FPGA. The square is bouncing to the frame every time it hit it!
We will leave up to you to hack further like to change square move speed etc!
12 Jul 2016 4 Comments
in fpga, verilog Tags: foss, fpga, hello, ice40, icestorm, oshw, verilog, world
One of the workshops at TuxCon 2016 included using Open Source Hardware FPGA board iCE40HX1K-EVB and there we went through the development process with FPGA and Verilog.
For those of you who were unable to attend, we will now show you what you’ve missed. First, see the previous post about how to setup FPGA FOSS IceStorm tools here.
Now that the tools are set, let’s learn some more about FPGA. This is a very brief introduction and it is far from comprehensive, but the Internet has tons of resources you can use to learn more.
We will go through most asked questions on the workshop only:
FPGA stands for Field Programmable Gate Array. They are digital integrated circuits (ICs) that contain configurable (programmable) blocks of logic along with configurable interconnections between these blocks. Design engineers can configure, or program, such devices to perform a variety of tasks.
Some FPGAs may only be programmed a single time (they are called OTP) while others may be reprogrammed over and over again. For development boards we need the latter because when we develop we often make mistakes and we need to be able to program FPGAs multiple times. The FPGAs which can be programmed many times usually have external non-volatile memory. It contains the configuration file which is read at power up to the local RAM inside FPGA, and is used to define the interconnections between the blocks inside FPGA. So when you apply power to these FPGA they need some small amount of time to read their program and then start working.
FPGAs allow many tasks to be performed in parallel at very high speed. They are also highly integrated (some FPGAs have millions of programmable blocks), so you can complete complex hardware designs in a very small space. The trade off is that FPGA are programmed differently than the micro controllers (as you will see later), so they require a little bit more studying in order to get used to them.
If you application requires high speed, and complex parallel tasks, you need FPGA. Typical applications are: digital signal processing as video and audio filtering, the FPGA outperform fastest DSPs in factor of 500. Another applications are developing new digital ICs like processors or microcontrollers with new architectures and instructions. FPGA are used also for physical layer communications, decoding and encoding high speed communication lines like HDMI, SATA, USB.
There is no sense to use FPGA in slow processes which can be done by microcontrollers, but they can be used to add fast peripherals to them. For example if you need very fast SPI to capture some fast serial signal, most of microcontrollers have SPIs which work up to 20-30Mhz clock, with FPGA you can make SPI which work on 100 Mhz or 200Mhz or 300Mhz and to buffer the data then to re-transmit slowly to the microcontroller who to do something with this data.
You can synthesize almost any digital circuit with FPGA, to make your own microprocessor with custom number of registers and instruction set, most of the companies which design microprocessors / microcontrollers first test their ideas on FPGAs.
Back in 1984 when the first FPGAs were made, design flows used for CPLD was taken and they were programmed by drawing schematics of digital circuits, then the CAD tool synthesized the schematic to FPGA configuration files which you can load to the FPGAs. This approach works well, but when the FPGAs become with thousands of logic cells and the schematics become more than several pages long the process become prone to errors exponentially with the size of the schematic. (Just imagine to draw internal schematic on modern processor with digital logic and then to test it).
At the end 1980s move toward HDL (hardware description languages) was made. Visualizing, capturing, debugging, understanding, and maintaining a design at the gate level of abstraction became increasingly difficult and inefficient when juggling thousands gates.
The lowest level of abstraction for a digital HDL is switch level, which describe the circuit as a netlist of transistor switches.
A higher level of abstraction is the gate level,which describe the circuit as a netlist of primitive logic gates and functions.
The next level of HDL abstraction is the ability to support functional representations using Boolean equations.
The highest level of abstraction sported by traditional HDLs is known as behavioral, which describe the behavior of a circuit using abstract constructs like loops and processes similar to programming language.
Verilog is one such HDL behavior language, another one very popular in Europe is VHDL, but as FOSS FPGA tool for iCE40 IceStorm has support for only Verilog we will make all next demos in Verilog :).
Let have look at the first Blink LED project we programmed on iCE40HX1K-EVB in the previous blog post. It’s available on GitHub.
This is configuration file for the project which tells how IceStorm to compile it:
PROJ = example
this is project name, it could be any other name, IceStorm will search for example.v source file and the result at the end will be example.bin which you can program to iCE40HX1K-EVB
PIN_DEF = ice40hx1k-evb.pcf
this is external file which assigns the signals we will use in the project to the physical chip pin numbers, if we open it will see:
set_io CLK 15 set_io BUT1 41 set_io BUT2 42 set_io LED1 40 set_io LED2 51
which means the 100 Mhz Oscillator clock is connected to pin15, button1 to pin41, LED1 to pin40 and so on.
DEVICE = hx1k
this tells IceStorm which device is used, in this case device from HX series with 1K logic blocks
yosys -p 'synth_ice40 -top top -blif $@' $<
invokes yosys to syntheses example.v Verilog sources ‘top’ is the name of the top module you could assume it as something like main() in C language.
arachne-pnr -d $(subst hx,,$(subst lp,,$(DEVICE))) -o $@ -p $^ -P vq100
after yosys has synthesized the sources ‘arachne-pnr’ try to place and route them physically inside the chip, you can imagine these logic cells are as matrix and this tool have to decide how to arrange them so to make smaller distances between the connected cells and physical pins, and design to work at maximal possible speed. Look at -P vq100 switch it tells arachne-pnr what package is used for the device in our case VQ100 chip package.
icepack $< $@
packs the text file output generated by arachne-pnr to .bin file read to be programmed in FPGA external Flash memory
icetime -d $(DEVICE) -mtr $@ $<
The icetime program is an iCE40 timing analysis tool. It reads designs in IceStorm ASCII format and writes times timing netlists that can be used in external timing analysers. It also includes a simple topological timing analyser that can be used to create timing reports.
sudo iceprogduino $<
small program which uses OLIMEXINO-32U4 (Arduino Leonardo) with custom firmware as programmer for the iCE40HX1K-EVB SPI Flash
module top( //top module CLK, BUT1, BUT2, LED1, LED2 );
this describes the ‘top’ module in the code which will be synthesised, it will use some physical signals defined in ice40hx1k-evb.pcf
then we define what are these signals inputs or outputs:
input CLK; //input 100Mhz clock input BUT1; //input signal from button 1 input BUT2; //input signal from button 2 output LED1; //output signal to LED1 output LED2; //output signal to LED2
with the keyword ‘reg’ we define registers i.e. analog of variables in programming language, but here these are with default width of 1 bit, in the registers we can store and read signals
reg BUT1_r; //register to keep button 1 state reg BUT2_r; //register to keep button 2 state reg LED1_m0_r; //LED1 value in mode = 0 reg LED2_m0_r; //LED2 value in mode = 0 reg LED1_m1_r; //LED1 value in mode = 1 reg LED2_m1_r; //LED2 value in mode = 1 reg [14:0] cntr; // 15 bit counter for LED blink timing reg [14:0] rst_cnt=0; // 15 bit counter for button debounce reg mode=1; //mode set to 1 initially reg [11:0] clk_div; // 12 bit counter
you can see that cntr and rst_cntr are with [14:0] in front of them, this means they are 15 bit long registers, clk_div is 12 bit
with the keyword wire you define internal signals which are additional to these defined in the top module
wire clk_24KHz; //signal with approx 24KHz clock wire reset; //used for button debounce
the keyword assign makes connection between signals, so every time right side signal changes the same change occur at the left side signal
assign reset = rst_cnt[14]; //reset signal is connected to bit15 of rst_cnt assign LED1 = mode ? LED1_m1_r : LED1_m0_r; //multiplexer controlled //by mode connects LED1_m1_r or LED1_m0_r to LED1 assign LED2 = mode ? LED2_m1_r : LED2_m0_r; //multiplexer controlled //by mode connects LED2_m1_r or LED2_m0_r to LED2 assign clk_24KHz = clk_div[11]; //100Mhz/4096= 24414 Hz
in this case 15th bit of rst_cnt register is connected to signal reset, signal clk_24KHz is connected to 12th bit of clk_div register
LED1 and LED2 are connected via multiplexers (made with ? keyword) with control signal mode to two registers with suffix ‘m1’ and ‘m0’
so when mode is 0 LED1 will be connected to LED1_m0_r register and when mode is 1 to LED1_m1_r
always block is executed every time when something in his sensitivity list changes:
always @ (posedge CLK) begin //on each positive edge of 100Mhz clock increment clk_div clk_div <= clk_div + 12'b1; end
in this case every time positive edge of CLK is happen i.e. CLK change from 0 to 1 it’s executed and adds 1 to clk_div
next always block is a bit more complex:
always @ (posedge clk_24KHz) begin //on each positive edge of 24414Hz clock BUT1_r <= BUT1; //capture button 1 state to BUT1_r BUT2_r <= BUT2; //capture button 2 state to BUT2_r cntr <= cntr + 15'd1; //increment cntr LED blink counter if(reset == 1'b0) begin //if bit15 of rst_cnt is not set yet rst_cnt <= rst_cnt + 15'd1; //increment the counter rst_cnt end if(BUT1_r == 1'b0 && BUT2_r == 1'b0 && reset == 1'b1) begin //if bit15 of rst_cnt is set and both buttons are pressed mode <= mode ^ 1'b1; //toggle the mode rst_cnt <= 15'd0; //clear debounce rst_cnt end LED1_m0_r <= ~BUT1_r; //copy inv state of button 1 to LED1_m0_r LED2_m0_r <= ~BUT2_r; //copy inv state of button 2 to LED2_m0_r if(cntr == 15'd12207) begin //when 0.5s pass LED1_m1_r <= 1'b0; //reset LED1_m1_r LED2_m1_r <= 1'b1; //set LED2_m1_r end if(cntr > 15'd24414) begin //when 1.0s pass cntr <= 15'd0; //clear cntr LED1_m1_r <= 1'b1; //set LED1_m1_r LED2_m1_r <= 1'b0; //reset LED2_m1_r end end
what happens here? every time at positive edge of clk_24KHz :
in BUT1_r and BUT2_r is loaded the current state of the buttons,
cntr is incremented with 1, this is our LED blink frequency counter
clk_24KHz is not actually exactly 24KHz but 100 000 000 Hz / 4096 = 24414 Hz or 24.414KHz 🙂
when this cntr reach value 12207 i.e. half second pass LED1_m1_r is loaded with 0 and LED2_m1_r is loaded with 1
when this cntr reach value 24414 i.e. one second pass LED1_m1_r is loaded with 1 and LED2_m1_r is loaded with 0
i.e. if mode is 1 the LED1 and LED2 will blink each half second.
when the mode is 0 LED1_m0_r and LED2_m0_r will follow button states i.e. in this mode when you press button 1, LED1 will be on and when you release button 1 LED1 will be off
same will be for LED2 too
Now let pay some more attention to what this code describes:
if(reset == 1'b0) begin //if bit15 of rst_cnt is not set yet rst_cnt <= rst_cnt + 15'd1; //increment the counter rst_cnt end if(BUT1_r == 1'b0 && BUT2_r == 1'b0 && reset == 1'b1) begin //if bit15 of rst_cnt is set and both buttons are pressed mode <= mode ^ 1'b1; //toggle the mode rst_cnt <= 15'd0; //clear debounce rst_cnt end
reset is signal connected to rst_cnt 15th bit, so until this bit is set rst_cnt will be incremented on every positive edge of clk_24KHz,
when reset is set to 1 if BUT1 and BUT2 are pressed together the mode is toggled and res_cnt is set to 0 to ensure some debounce time
you can download the project and make and program with these two lines:
make make prog
You will see first LED1 and LED2 to blink as default mode is 1. If you want to toggle the mode press and hold BUT1 and BUT2 and release them quickly.
LED1 and LED2 will switch off, in this mode if you press BUT1 will switch on LED1 and if you press BUT2 will switch on LED2, if you press the both buttons together mode will change again to 1 and LED1 and LED2 will start blinking.
Your first program is done!
Now let see what will happen if we change line 48 from
mode <= mode ^ 1'b1; //toggle the mode
to:
model <= mode ^ 1'b1; //toggle the mode
i.e. we made mistake and instead of mode wrote model what do you think will be there error message when synthesis is done?
You can try! Whaaaat? everything completes correctly and you get your example.bin ready for program. What happens when we run it? Right! The LED1 and LED2 blinks and you can’t change the mode by pressing BUT1 and BUT2 together anymore!
OMG how this happens? Welcome to the wonderful world of Verilog 🙂 If you do not define but use new signal Verilog silently creates it and just issue WARNING not error, in this case the warning is in the very beginning of the 1233 lines of messages you see printed while the source is synthesized:
Parsing Verilog input from `example.v' to AST representation. Generating RTLIL representation for module `\top'. Warning: Identifier `\model' is implicitly declared at example.v:48. Successfully finished Verilog frontend.
This feature may make you bang your head to the wall searching for errors and can’t happen in VHDL, where everything have to be strictly defined before to be used.
VHDL vs Verilog is like old C vs Pascal choice. In C you can do lot of things to shoot yourself in the leg and the compiler will not stop you.
In the next FPGA blog post we will go deeper and will show you how to generate VGA video signals with iCE40HX1-EVB + iCE40-IO boards and how to move object on the screen with the arrow keys of PS2 keyboard.
And we will not stop here, we are preparing more tutorials with iCE40HX1-EVB + iCE40-IO – video games Snake and Flappy bird. Then latter we will teach you how to build Digital Storage Oscilloscope with iCE40HX1-EVB + iCE40-IO+ iCE40-ADC , how to make Digital Logic Analyzer with iCE40HX1-EVB +iCE40-DIO for sniffing protocols from devices operating from 1.65 to 5.5V levels and how to make DDS generator of signals with any forms using iCE40HX1-EVB + iCE40-DAC.
EDIT: As I wrote we learn this stuff too! Regarding the implicit declarations they may be disabled by adding on top of your code:
`default_nettype none
I just try this and yosys stops with error when I mistype ‘mode’ with ‘model’:
Parsing Verilog input from `example.v' to AST representation. Generating RTLIL representation for module `\top'. ERROR: Identifier `\model' is implicitly declared at example.v:50 and `default_nettype is set to none. Makefile:8: recipe for target 'example.blif' failed make: *** [example.blif] Error 1
05 Jul 2016 1 Comment
in event Tags: computing, foss, fpga, freedom, Open, oshw, retro, robotics, Source, tuxcon
TuxCon 2016 will be this weekend 9th and 10th of July in Plovdiv. Be prepared it will be hot weather, prognosis are for 30-31C in the weekend!
BNR (Bulgarian National Radio) posted about TuxCon today.
The conference start at 11 o’clock with Reactive Java Robotics, Jitsi Meet, Security updates in GNU/Linux distributions and Open source in Education Welearners project.
After the lunch we will continue with Retro Computing, Open Source tools for FPGA, How to calculate 3D coordinates with 2D camera, Android customization, Open SCAD, How to use Open Source for corporate needs, MOVI voice recognition with Arduino.
Lighting talks at 18.00 will close the first day and we will move to the traditional beer party in popular Plovdiv pub with Free Beer from TuxCon sponsors (Free as Beer not as Speech 😀 ).
In Sunday three workshops will be held in Olimex training building, starting again at 11 o’clock.
Reactive Java robotics, Open Source Hardware Robotics and First steps in FPGA for absolute beginners.
Open Source Hardware Robotics – we will talk about our attempt to re-invent the popular mechanical robot hand from the past Robko, but with complete new mechanics and controlled via Internet with ESP8266 WiFi and JavaScript API. We will show the problems electronic engineers face when attempt to do mechanical projects.
FPGA for absolute beginners will introduce in popular way what FPGAs are and how they evolved with time, how they are programmed. We will emphasis on the first FOSS tool to work with FPGAs – IceStorm and we will make our first blink LED demo, then will take look at more sophisticated code of video generation and how to make small game like Flappy Bird on iCE40HX1K-EVB.
TuxCon is free to attend, everyone with interests in Open Source Software and Hardware is invited.
Recent Comments