基本信息
规则
项目进度
视频课程
案例
内容介绍
软件 & 硬件
元器件
超低功耗FPGA、5280 LUTs, 1024kbit SPRAM,1.2V、48-pin QFN (7 x 7 mm)
Arm Cortex-M0,64kB flash, 12kB SRAM, HVQFN32 package
超高速(7ns)比较器, 可工作于+3V/+5V
可编程分辨率的单总线数字温度传感芯片
300mA的线性稳压器LDO,并有使能和自动放电的功能
WS2812B是一款智能控制LED光源,它将控制电路和RGB芯片集成到5050组件包中。它包括智能数字端口数据锁存器和用于放大驱动器的信号整形电路。它还包括一个内部精密振荡器和一个恒流5电压控制端口,以确保一致的像素点光颜色高度。这些可以添加到全彩面板、条状全彩软灯、led装饰照明和不规则视频室内外led屏幕。
OLED控制器 - 支持128*32、128*64 OLED显示屏
一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。
软件
Verilog HDL是一种硬件描述语言,用于设计和归档电子系统。
工具
Lattice的FPGA开发环境,支持Windows/Linux操作系统,支持ICE40系列器件的开发。
电路图
物料清单
附件
-
UP5K_Pico_V3.pdf
V1.0版本的原理图,PDF格式,开源资料:https://gitee.com/eetree-git/ICE40_Training
-
UP5K_Pico.step
UP5K核心模块的3D模型
-
PICO_Training_final.pdf
训练板的原理图 - PDF格式
2022年寒假在家一起练活动已于2022年2月27日结束
2022年寒假练活动汇总:https://www.eetree.cn/project/detail/649
点击【支持一下】即可参与活动,购买及参加。
注:项目中必须用到此平台规定的板卡,其他板卡完成任务,不符合退款要求,收到板卡7天内如有质量问题可联系退换,使用之后不支持退换。
【基于iCE40UP5K的FPGA学习平台】需完成的项目,任选其一完成(于1月14日前完成发货,时间截止到2月27日):
项目1 - 利用ADC制作一个数字电压表
-
旋转电位计可以产生0-3.3V的电压
-
利用板上的串行ADC对电压进行转换
-
将电压值在板上的OLED屏幕上显示出来
项目2 - 利用PWM制作一个音乐播放器
-
通过PWM产生不同的音调,并驱动板上蜂鸣器将音调输出
-
能够播放三首不同的曲子,每个曲子的时间长度为1分钟,可以切换播放
-
曲子的切换使用扩展板的按键,需要有按键消抖的功能
-
播放的曲子的名字在OLED屏幕上显示出来(汉字显示)
项目3 - 利用DAC制作一个可调波形、频率、幅度、直流偏移的DDS信号发生器
-
能够产生正弦波、三角波、方波,可以通过扩展板上的按键控制波形的切换
-
产生信号的幅度0-3Vpp之间可调,调节分辨率精确到10mV,可以通过电位计进行调节
-
产生信号的频率100Hz - 2MHz之间可调,频率调节分辨率可达10Hz
-
将1KHz - 10KHz频率范围的信号同时送到蜂鸣器,驱动蜂鸣器发出声音,调节频率和信号的幅度会改变蜂鸣器的声音输出
-
产生的波形、波形的幅度、波形的频率都实时显示在OLED屏幕上
项目4 - 声音控制的节日彩灯
-
利用扩展板上的麦克风输入电路,将放大以后的音频信号通过核心板上的比较器构成的ADC进行量化(8bit分辨率即可)
-
量化后的音频信号的音量和频率来控制扩展板上12个WS2812B,来产生不同的效果
-
手机播放音乐,来控制彩灯
-
OLED上通过波形显示被量化的音频信号的波形和频谱,波形和频谱可以切换
项目5 - 可定时的音乐时钟
-
使用扩展板上的12颗彩灯对应于12个小时
-
核心板上的FPGA产生时钟,在OLED显示评上通过模拟或者数字的方式显示当前的时间 - 小时、分、秒
-
将“小时”的信息通过12颗彩灯来显示,效果自行设计
-
具有定时的功能,通过扩展板上的按键设置时间,到该时间点即(彩灯闪烁 + 音频播放),持续5秒钟时间
- 音频播放通过扩展板上的蜂鸣器来实现
项目6 - 用彩灯显示的水平仪/节日焰火
-
利用扩展板上的MMA7660姿态传感器来感知板子的角度
-
用扩展板上的12颗彩灯以灯光流动的方式显示板子的角度信息
- 用扩展板上的12颗彩灯制作焰火的效果,并通过板子的角度变换来改变焰火的显示效果
- 同时通过蜂鸣器播放节日音乐
项目7 - 音频信号采集/FFT频谱分析
-
扩展板上的麦克风将声音信号转变为模拟电信号
-
利用核心板上的比较器构成Sigma Delta ADC对音频模拟信号进行采集
-
量化后的信号在OLED显示屏上进行波形显示
-
同时利用FPGA内部的乘法器对采集到的信号进行FFT变换,将变换后的频谱显示在OLED上
-
能够计算并显示出信号的频率、幅度、谐波分量
项目8 - 计数器/频率计
-
利用核心板上的高速比较器 + PWM构成的直流偏置电压生成,对外部信号进行频率测量和占空比测量
-
将测量的频率和占空比的结果显示在OLED屏幕上
- 测试信号可以来自核心板自己产生的PWM信号 - 可调频率、可调占空比,通过杜邦线将PWM输出的信号连接到比较器的输入端
项目9 - RISC-V软核的设计或移植
-
设计或移植一款RISC-V软核(网上有很多)
-
基于该软核做一些简单的扩展:
- 能够点量核心板和扩展板上的LED
- 能够识别扩展板上的按键
- 扩展SPI接口功能,能够在扩展板的OLED上显示信息
- 扩展I2C接口功能,能够读取MMA7660姿态传感器的信息,并显示在OLED上
- 能够读取核心板上18B20的温度传感器的信息,显示在OLED上
项目10 - SDR通信数据收发系统(调制信号的观察需要测量仪器)
-
FPGA+R-2R DAC产生FM信号,载波频率为10.7MHz,频带宽度为50KHz,可以用频谱仪查看产生信号的频谱
- 通过核心板的高速比较器+FPGA逻辑构成的ADC对输入的10.7MHz的载波调制信号进行采样
- FPGA逻辑完成FM解调
- FPGA将姿态传感器的状态信息通过FM调制发射出去,再通过FM解调将数据恢复出来,通过核心板上的UART总线发到上位机PC上,在PC的串口助手中显示当前姿态传感器的信息
————————————————————————————————————————
活动流程及时间安排
硬禾学堂为鼓励大家真心玩起来,我们活动一直在采用“玩转就免费”的方针,鼓励大家用心去学,本次的活动依旧采用这种方式:
- 购买:活动参与者需先从硬禾学堂购买板卡,于2022.2.27前均可购买,下单即参加活动(12月3日前下单的板卡不能用于退款)。希望大家尽快完成下单,以便工作人员有货时尽快发货。
- 发货:当天下午三点前下单的可当天发货,周末休息。因一些学校放假较早,保证稳妥寄送,下单前请填写一定可以收到货的地址。待同学们都收到板卡后,硬禾官方将采用每个板卡2-3场直播的方式教大家上手,并有技术交流群一起讨论学习,由于板卡数量有限,早参加早占取名额,也能尽早收到板卡。
- 活动时间:从收到板卡日起,活动参与者需于2022年2月27日晚12点前实现规定的功能,并按照规则将项目上传到电子森林项目网站,所需提交材料和电子森林使用说明请点此查阅,逾期提交者无效。
- 审核时间:硬禾学堂将于2022年3月18日前审核完毕并邮件告知审核通过者。
- 返款时间:审核结束之后,将于2022.2.21-2022.4.15期间完成退款。
- 毕业生福利:如果你是大四毕业生,在此活动中购买板卡,并用本板卡来完成你的毕设(不限制所做项目),可截止到2022年6月30日前上传你的毕设项目于电子森林,硬禾将给予退款(若中间申请开票,则不能再退款)。
注:本次寒假练所发板卡均配有数据线且包邮。
————————————————————————————————————————
以下说明包含寒假在家一起练材料提交说明以及电子森林项目网站使用说明(请认真查阅),活动期间所有活动板卡不开票,请备注开票的于活动结束之后联系工作人员统一开票,符合退款的不开票。
2022年“寒假在家一起练”材料提交及方式
一、完成项目之后在电子森林项目网站需提交以下材料
上传之前请先使用手机号注册电子森林账号:https://www.eetree.cn/,项目标题请命名为:用/基于xxx实现/完成/设计/制作xxx
1. 3-5分钟短视频(要求横屏且1080p)
- 简短的自我介绍
- 硬件介绍
- 项目设计思路
- 项目实现功能(重要的代码介绍和实物功能展示)
注:视频太短(仅几十秒),视频无板卡演示效果,拍摄不清晰,镜头太晃均不合格。
2. 项目总结报告(即说明文档)
- 项目描述(项目介绍、设计思路和框图/软件流程图、简单的硬件介绍、实现的功能及图片展示、主要代码片段及说明、遇到的主要难题及解决方法、未来的计划或建议等,需达到除代码之外的约1500字左右)
项目案例参考:https://www.eetree.cn/project/detail/167,https://www.eetree.cn/project/detail/498
- 可编译下载的代码(放在项目的附件,用于验证)
注:项目报告中没有源代码和实现结果的展示图者均被退回
二、项目提交后需提交以下材料到training@eetree.cn邮箱,以作项目关联和顺利返款
邮件请命名:2022寒假在家练+iCE40 FPGA平台+真实姓名
1. 电子森林注册昵称(不是用户名)
2. 下单时所留姓名和电话(如果你是大四毕业生,用此板卡来完成你的毕设,还需发送你的真实姓名+学校+专业+你的学生证+毕业设计课题)
订单查看方式:请关注“硬禾学堂”公众号,移动端点击底部“硬禾学堂”,进入后点击“我的”,即可查看全部订单信息。
3. 订单号和付款截图
在全部订单信息中点击对应订单即可复制订单号:

在“微信支付”中可截图付款凭证
注:如不参与退款,则无需发送邮件,仅提交项目即可,并在团队成员处添加个人姓名和学校
————————————————————————————————————————
电子森林项目网站使用说明
2. 请注意每编辑完成一个页面一定要点击底部“保存”
3. “基本信息”页
- 标题请按照规则填写
- 封面图一定要更换成适合本人项目的图,不要用默认的
- 视频请先上传到B站/优酷/腾讯,然后在“视频代码”处粘贴iframe格式代码
- 类型选择“分享类”
- 标签请填写3-5个描述你所做项目的关键词,其中一个要包括“2022寒假在家练”标签
- 在“高校”处选择个人学校
- 在“团队介绍”和“团队成员”处添加个人姓名和学校/公司
注:寒/暑假练项目均为个人项目,团队合作完成者仅退一人款
- 在“描述”中编辑项目总结报告
- 图片的上传请点击右上角的“上传”
- 代码的插入请使用插入工具
- 注意格式整洁,正文字号建议用12pt,标题建议用14pt,并加粗显示,正文首行不要缩进两字符
- 描述下面的“规则”、“参与推广”、“支付协议”则无需填写’
4. “进度”页和“软硬件”
- 在“进度”页中可添加个人的每一个阶段项目进程

- 在“软硬件”页中可选择使用的软件和工具,若没有则无需填写
- 在附件处一定要添加可下载的代码文件,若是文件太大, 可上传到百度网盘上,并在“描述”中粘贴链接
- 编辑完成后一定要点击“保存”
5. 其他页
- “视频课程”、“应用案例”和“商品”页若没有则无需填写
6. 点击“预览”,并“发布”项目
- 本人先点击“预览”查看自己的项目,若没有问题之后再点击“发布”
- 发布之后若查出存在问题,可点击“取消审核”再次修改。若在项目通过之后发现存在问题,依旧可再次修改项目,直到项目完善

调试成功 更新发布于 2021年02月26日
今天焊接了第一块板子,焊接上器件以后工作正常。5V-3.3V的开关稳压电源芯片改用了SGM6012-3.3,因此在后面的版本中可以去掉分压电阻R23/R22。
生产版本:
- 将所有0201封装的电阻和电容改为0402封装的器件
- 调整一下邮票孔外延,以适配PICO的尺寸
- RGB三色灯是否需要调整封装?
未来的测试:
- RISC-V软核测试
- PICO扩展板外设的测试
V2打样到手 更新发布于 2021年03月15日
基于第一个版本做了两个调整:
1 间距调整到跟树莓派PICO保持一致
2 修改了RGB三色灯,封装变大一些,方便供货
添加了2个高速比较器以支持对模拟信号的采集 更新发布于 2021年11月15日
为尽可能和树莓派Pico兼容,能够充分共用树莓派Pico的外设扩展模块等,将本核心板做了简单升级:
- 核心板上添加了2个高速比较器,通过Sigma Delta的方式实现双路ADC的功能,可以实现10Ksps/8bit的采样
- 核心板上添加了1个温度传感器DS18B20U,通过单线协议对环境温度进行感知
- 板上有一个R、G、B三色灯,通过ice40 FPGA内部的IP驱动
- FPGA的配置是通过USB Type-C端口访问存储外设的方式来实现,只需要将生成的FPGA二进制代码(rbt格式)拖进盘符为StepFPGA16的外设存储盘即可
- 核心板可以通过UART同上位主机通信
本核心板可以支持为Pico设计的外设扩展模块:
FPGA入门1 - 什么是FPGA? 更新发布于 2022年01月11日
View Introduction to FPGA Part 1 - What is an FPGA Blog
Required Hardware
For this series, you will need the following hardware:
- Lattice iCE40 development board. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
If other hardware is needed for a particular episode, it will be listed in the video as well as in the written tutorial.
Reconfigurable Hardware
An FPGA provides a way to implement custom digital logic circuits in a short amount of time using an HDL. Prior to reconfigurable logic ICs, engineers would have to wire up breadboards to test their ideas.
Image credits: “breadboard - 1” by Windell Oskay is licensed under CC BY 2.0.
As you can imagine, complex digital designs would create wiring nightmares, and tracking down bugs became nearly impossible. Reconfigurable logic ICs (e.g. CPLDs and FPGAs) offer a way to reduce space, wiring clutter, and debugging effort.
An FPGA is made of many Programmable Logic Blocks (PLBs), each of which contains several Logic Cells. In the FPGA used in the series (the Lattice iCE40HX1K on the iCEstick), each PLB contains 8 cells. This number may change depending on your particular FPGA.
The following diagram (taken from the iCE40LP/HX datasheet) shows the different parts of the iCE40LP/HX FPGA.
Each cell contains a D flip-flop and a Lookup Table (LUT). We will explore these components in future episodes.
In addition to these blocks, most FPGAs will contain some random access memory (RAM) for short-term storage of data, a phase-locked loop (PLL) to act as a clock multiplier, and some input/output (I/O) banks that allow you to use the various pins as digital inputs or outputs. Some offer additional peripherals, such as dedicated communication blocks (e.g. SPI), analog-to-digital converters (ADCs), or even on-chip central processing units (CPUs) for running sequential code.
When you “program” an FPGA, you tell it how to connect these various blocks and peripherals together to implement your digital circuit!
What Is It Good For?
Making designs for an FPGA is an intermediate or advanced engineering topic, as you need to have a fundamental understanding of digital circuits, which includes logic gates, binary, and boolean algebra.
Important! This series assumes you have some basic knowledge of digital circuits. We will focus on implementing these designs in an HDL, testing them through simulation, and uploading them to an FPGA.
In many cases, you will often find that a microcontroller or microprocessor (e.g. single board computer) can get the job done for less effort. However, there are a number of applications where you need to have reconfigurable digital logic. Let’s look at a few examples:
- Parallel I/O operations - You need to control a lot of hardware very quickly, such as displays made up of thousands or millions of LEDs. These LED cubes offer silky smooth frame rates because they’re being controlled by FPGAs. A CPU would struggle to feed the required data to all of the LEDs (or driver chips) that fast.
- Data acquisition (DAQ) - You can create custom digital logic that samples and buffers sensor data very quickly, which can then be read by a CPU later for analysis. For example, the Digilent Analog Discovery 2 uses a Xilinx FPGA to sample and buffer data from its oscilloscope and logic analyzer front end circuitry.
- Specialized computations - Depending on the application, digital circuits can often be configured to perform specific math operations much faster than a CPU, which is configured to execute generic operations (e.g. math, load, store). As a result, you’ll often see FPGAs used in digital signal processing (DSP), which sometimes requires calculations to be performed at rates much faster than what current CPUs can do. You’ll also see them used in areas like cryptocurrency mining and machine learning.
- Custom processor - Because an FPGA allows you to build an almost limitless number of digital circuits, you can actually implement one or more CPUs in an FPGA. This offers the ability to create customized CPUs if you can’t find one on the market that meets your needs. For example, you might take a RISC-V CPU implementation and add a special instruction to execute a divide-and-accumulate operation in a single clock cycle.
Because you can reconfigure it in the field, the FPGA makes for a fantastic prototyping tool. You can tweak and refine your digital circuit until it meets your requirements. If you plan to produce more than a few thousand units of your final product, you might consider turning your custom digital circuit into an application-specific integrated circuit (ASIC). With an ASIC, your digital circuit design is permanently etched into an integrated circuit (“chip”) that you can include in your product. However, the tooling costs for an ASIC might be prohibitive.
If you plan to produce less than a few thousand units of your product, you might consider just adding the FPGA to your design, as Mercedes-Benz did in one of their infotainment consoles.
Design Flow
The following diagram shows a typical process for creating a design for an FPGA. If you are used to programming for computers or microcontrollers, you will notice that the naming of the steps are different (e.g. there is no “compile” step) than what you might be used to.
Note that each step lists the tool we plan to use throughout this series.
- HDL Coding - Rather than wire up physical components on a breadboard or spin a custom PCB for discreet digital logic parts, you will describe your design in an HDL. The three most popular hardware description languages are VHDL, Verilog, and SystemVerilog. We will use Verilog in this series (as it is the only language fully supported by the rest of the tool suite at this time).
- Simulation - Prior to building and uploading your design to a real FPGA, you will often want to simulate the design to ensure that everything works. This step involves writing more HDL to toggle the inputs and clock signals so that you can observe the output behavior in a simulation program. The test HDL code is known as a "testbench." We will write testbenches in Verilog and simulate the design in gtkwave.
- Synthesis - As HDL is not a procedural language (i.e. your code does not execute sequentially in a processor), it cannot be “compiled.” Rather, the process of “synthesis” takes your HDL code and converts it into gate-level representations that can be understood by the FPGA. In essence, it converts your code into a digital circuit. We will use the yosys tool for synthesis.
- Place and Route (PNR) - The output of synthesis is only the structure of the circuit needed to realize your design (as detailed in HDL). The PNR tool figures out exactly how to connect the various parts inside your particular FPGA to make this happen. The process is similar to using an autorouter in a PCB design program. We will use nextpnr as our PNR tool.
- Package - The output of the PNR tool is a human-readable ASCII file that details what connections need to be made among the components inside the FPGA. The package tool converts the ASCII file into a binary file that can be read by the FPGA’s on-chip configuration process. For this process, we will use icepack, which is part of the Project IceStorm toolset.
- Upload - Finally, the binary file must be uploaded to the FPGA. Note that most modern FPGAs do not contain onboard, non-volatile flash memory. As a result, the binary file is usually uploaded to aflash memory chip on the same PCB as the FPGA. On boot, the FPGA reads the stored binary data in order to configure its logic cells. We will use iceprog (also part of Project IceStorm) to upload our synthesized and routed design to the FPGA.
Don’t worry! You won’t need to install and use these tools individually. We will rely on the open-source and cross-platform tool apio to help manage these tools. As an analogy, if yosys is like avr-gcc, then apio is like the Arduino IDE. Apio is specifically made to work with popular iCE40 development boards, and it makes the building/uploading process much easier so that we can focus on learning the building blocks of FPGA design.
Recommended Reading
All demonstrations and solutions for this series can be found in this GitHub repository.
You can learn more about the apio tool on their documentation site.
FPGA入门2- 如何使用Yosys、IceStorm和Apio? 更新发布于 2022年01月11日
View Introduction to FPGA Part 1 - What is an FPGA Blog
Required Hardware
I recommend an iCEstick for this tutorial. However, any of the development boards listed as “supported” by the apio project should work. I also recommend a USB extension cable, but it is optional.
Install apio
Apio is a Python-based tool that controls other low-level tools, such as yosys, nextpnr, icepack, and iceprog. It’s designed to work with development boards (rather than individual FPGA chips). As such, it’s helpful to think of apio as the “Arduino IDE” of FPGAs (where yosys would be analogous to the avr-gcc compiler).
You will need to have Python 3.5+ installed on your system (if you do not have it already). Navigate to the Python website and follow the instructions for installing it.
Here is the apio guide for installing the tool on different operating systems.
Note: as of this writing, the current version of apio does not work. You will want to install version 0.6.7. Future updates to apio will hopefully fix any bugs.
Install apio with the following command:
python -m pip install apio==0.6.7
Linux Only:
You might need to update your path so that `apio` can be found as a command-line tool. Edit your profile:
nano ~/.profile
Add the following line to the end:
PATH=$PATH:~/.local/bin
Save and exit (‘ctrl+x’ and ‘y’ if you are using nano). Re-run the profile configuration:
source ~/.profile
If you are using a Debian/Ubuntu flavor, you will also likely need to add your current username to the “dialout” group:
sudo usermod -a -G dialout $USER
Install Tool Suite
Run the following commands to install all of the required tools and drivers:
apio install --all
apio install drivers
Many of the FPGA dev boards contain an FTDI chip to translate USB communication to UART communication (e.g. so you can upload your synthesized/packaged FPGA design to the onboard flash memory). If your board has an FTDI chip (the iCEstick has one), enter the following command:
apio drivers --ftdi-enable
Windows Only:
On windows, you will need to call the above command in an administrator command prompt. In the Windows search bar, enter “cmd.” Right-click on the Command Prompt entry and select Run as administrator.
In the new window, enter the `apio drivers --ftdi-enable` command. This will launch Zadig. Plug in your dev board. Select Options > List All Devices. Select your dev board from the drop-down menu (e.g. the iCEstick will show up as “Lattice FTUSB Interface Cable (Interface 0)”). Choose the libusbK driver and click Replace Driver.
Important! On Windows, make sure you have no other FTDI parts plugged into your USB ports. This will cause a conflict with the apio uploading process. If you run `apio upload` or `apio system --lsftdi` and get the following error, it means you have an extra FTDI device plugged into your computer:
ftdi_usb_get_strings failed: -4 (libusb_open() failed)
Remove the extra FTDI device before trying to upload or communicate with the dev board using apio again.
Upload Example
Apio comes with a number of pre-made examples. Create a folder on your system to hold your FPGA designs. For example, I will place my designs in C:\Users\sgmustadio\Documents\apio.
In a terminal window, navigate to your working directory (e.g. Documents\apio for me) and enter the following to list all of the available examples:
apio examples -l
Create the icestick/leds example (if you have the iCEstick):
apio examples -d icestick\leds
Navigate into the leds directory. Verify and simulate the design:
cd icestick/leds
apio verify
apio sim
This should open gtkwave. All five LED pins (D1-D5) should be logic HIGH.
When you are satisfied with the simulation, build and upload the design to your FPGA:
apio build
apio upload
You should see the LEDs on your FPGA board turn on!
Challenge
Your challenge for this episode is to turn off one of the LEDs by modifying leds.v. Use whichever text editing program you like.
Solution
Here is one possible solution to turn off one of the LEDs. Note that I turned off the 5th LED (the green LED on the iCEstick).
//------------------------------------------------------------------
//-- Hello world example for the iCEstick board
//-- Turn on all the leds
//------------------------------------------------------------------
module leds(output wire D1,
output wire D2,
output wire D3,
output wire D4,
output wire D5);
assign D1 = 1'b1;
assign D2 = 1'b1;
assign D3 = 1'b1;
assign D4 = 1'b1;
assign D5 = 1'b0;
endmodule
There are a number of ways to represent numbers in Verilog. `1’b0` means that the constant value is a 1-bit number (given by the first ‘1’) and expressed in binary (given by the ‘b’). The final ‘0’ is the value of the number.
We set the value of the D5 pin to binary ‘0’, which tells the FPGA to set that pin to logic LOW.
I recommend reading this documentation page on data types in Verilog to learn about the different ways to express numbers.
Recommended Reading
All demonstrations and solutions for this series can be found in this GitHub repository.
You can learn more about the apio tool on their documentation site.
Matt Venn demonstrates how to use yosys, nextpnr, and icepack/iceprog in a 3-part video series (if you would like to see how to use the tools without apio).
FPGA入门3 - 如何使用Verilog? 更新发布于 2022年01月11日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick (including the pins on either side of the PMOD connector) can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
Create a full adder as shown in the diagram below using Verilog.
A full adder adds two binary numbers together. The full adder shown is a 1-bit adder and contains carry-in (Cin) and carry-out (Cout) bits. There are several possible ways to implement a full adder, but your solution should output a sum (S) bit along with a Cout bit that adds the 1-bit value A to 1-bit value B and also adds the Cin bit.
Note that there are multiple circuits that can create a full adder. I have provided one possible way to accomplish the goal of adding two 1-bit numbers. See this Wikipedia article for more information on the adder.
Upload your design to the FPGA and test it. Try pressing different combinations of buttons and compare it to the truth table to ensure that your design works.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
full-adder.pcf
# LEDs
set_io led[0] 99
set_io led[1] 98
# PMOD I/O
set_io -pullup yes pmod[0] 78
set_io -pullup yes pmod[1] 79
set_io -pullup yes pmod[2] 80
full-adder.v
// Solution to full adder challenge
//
// Inputs:
// pmod[2:0] - pushbuttons (x3)
//
// Outputs:
// led[1:0] - LEDs (x2)
//
// LED 0 turns on if 1 or 3 buttons are pressed. LED 1 turns on if 2 or 3
// buttons are pressed.
//
// Date: October 25, 2021
// Author: Shawn Hymel
// License: 0BSD
// Full adder with button inputs
module full_adder (
// Inputs
input [2:0] pmod,
// Output
output [1:0] led
);
// Wire (net) declarations (internal to module)
wire a;
wire b;
wire c_in;
wire a_xor_b;
// A, B, and C_IN are inverted button logic
assign a = ~pmod[0];
assign b = ~pmod[1];
assign c_in = ~pmod[2];
// Create intermediate wire (net)
assign a_xor_b = a ^ b;
// Create output logic
assign led[0] = a_xor_b ^ c_in;
assign led[1] = (a_xor_b & c_in) | (a & b);
endmodule
Save these files in a single directory on your computer. Open a command prompt and navigate to your project directory. Build and upload the design with apio. If you are not using the iCEstick, change the board name to the board tag found in the apio supported boards file (for example, the tag for the “iCEstick Evaluation Kit” is “icestick”).
apio init -b icestick
apio build
apio upload
When the process is done uploading the design, you should be able to press the buttons on your breadboard to verify that the full adder works!
Explanation
Let’s first look at the physical constraints file (.pcf). This should look very much like the examples shown in the video. We define 2 input/output pins connected to physical pins 98 and 99 on the FPGA
set_io led[0] 99
set_io led[1] 98
Next, we define 3 input/output pins with pull-up resistors connected to physical pins 78, 79, and 80 on the FPGA.
set_io -pullup yes pmod[0] 78
set_io -pullup yes pmod[1] 79
set_io -pullup yes pmod[2] 80
In the full-adder.v Verilog file, we start by declaring the name of our module with the required inputs and outputs (these names should match those in the .pcf file):
module full_adder (
// Inputs
input [2:0] pmod,
// Output
output [1:0] led
);
While not necessary, I demonstrate using wires as intermediary names for the input signals, as the buttons are active-low. For example, ~pmod[0] is renamed (using a wire) to ‘a’.
// Wire (net) declarations (internal to module)
wire a;
wire b;
wire c_in;
wire a_xor_b;
// A, B, and C_IN are inverted button logic
assign a = ~pmod[0];
assign b = ~pmod[1];
assign c_in = ~pmod[2];
Next, I assign a wire name to the combination of the A XOR B operation. Once again, this is not strictly necessary, but it does demonstrate how to use wires.
assign a_xor_b = a ^ b;
Finally, the output logic is created using the named wires. Here, the first LED (S) is given by (A XOR B) XOR Cin. The second LED (Cout) is given by ((A XOR B) AND Cin) OR (A AND B).
assign led[0] = a_xor_b ^ c_in;
assign led[1] = (a_xor_b & c_in) | (a & b);
Finally, we close the module with “endmodule.” When synthesized, this should produce a truth table similar to the one given in the challenge section. Note that the only difference is that the buttons must be inverted, as they are active low.
Recommended Reading
The following content might be helpful if you would like to dig deeper:
- Nuts and Volts magazine has a fantastic intro to digital logic article if you need a refresher
- Here is another set of articles on digital logic from i-programmer.info
- Wikipedia article on the adder
- Stanford has a great Introduction to Verilog guide
- Matt Venn demonstrates how to use yosys, nextpnr, and icepack/iceprog in a 3-part video series (if you would like to see how to use the tools without apio)
FPGA入门4 - 时钟和过程赋值 更新发布于 2022年01月11日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
Create a clock divider using a counter so that the LEDs count up (in binary) once per second.
The iCEstick has a 12 MHz oscillator connected to physical pin 21 on the FPGA. As a result, you can use the following line in your .pcf file to have a 12 MHz clock signal on the “clk” input in Verilog:
set_io clk 21
Hint: you will probably need two counters in your design--one to operate as a clock divider and another to count up on the LEDs.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
[Edit 12/28/2021] IMPORTANT: the solution below is a useful clock divider design for beginners, but it can lead to errors down the road when we talk about metastability. For a better clock divider that does not rely on an asynchronous clock signal, please see here.
clock-counter.pcf
# Oscillator
set_io clk 21
# LEDs
set_io led[0] 99
set_io led[1] 98
set_io led[2] 97
set_io led[3] 96
# PMOD I/O
set_io -pullup yes rst_btn 78
clock-counter.v
// Clock-divided counter
//
// Inputs:
// clk - 12 MHz clock
// rst_btn - pushbutton (RESET)
//
// Outputs:
// led[3:0] - LEDs (count from 0x0 to 0xf)
//
// LEDs will display a binary number that increments by one each second.
//
// Date: October 26, 2021
// Author: Shawn Hymel
// License: 0BSD
// Count up each second
module clock_counter (
// Inputs
input clk,
input rst_btn,
// Outputs
output reg [3:0] led
);
wire rst;
reg div_clk;
reg [31:0] count;
localparam [31:0] max_count = 6000000;
// Reset is the inverse of the reset button
assign rst = ~rst_btn;
// Count up on (divided) clock rising edge or reset on button push
always @ (posedge div_clk or posedge rst) begin
if (rst == 1'b1) begin
led <= 4'b0;
end else begin
led <= led + 1'b1;
end
end
// Clock divider
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
count <= 32'b0;
end else if (count == max_count) begin
count <= 32'b0;
div_clk <= ~div_clk;
end else begin
count <= count + 1;
end
end
endmodule
Save these files in a single directory on your computer. Open a terminal and enter the following apio commands to initialize the board (assuming you are using an iCEstick), build the project, and upload it to your FPGA development board:
apio init -b icestick
apio build
apio upload
When the process is done, the LEDs should count up (in binary) once per second. Press the first pushbutton to reset the counter.
Explanation
The .pcf file should be very similar to what you used in the previous challenge. The big difference is the addition of the clock signal. The 12 MHz is permanently attached to physical pin 21 on the FPGA, and we assign the name “clk” to that signal.
After defining our input and output signals, we declare our local wires, registers, and parameters:
wire rst;
reg div_clk;
reg [31:0] count;
localparam [31:0] max_count = 6000000;
Note that we introduce a new declaration here: the parameter. Parameters are used to hold constant values during runtime (e.g. numbers used for calculations). You can change a parameter’s value at synthesis time if you are instantiating a module in another module (see this article for more detail: https://www.chipverify.com/verilog/verilog-parameters).
In our case, we are using a “localparam,” which sets a constant numerical value in our module and prevents it from being overridden by a “defparam” or argument call from a top-level module. See this discussion for more information: https://stackoverflow.com/questions/30288783/difference-between-parameter-and-localparam.
Note that we must set the number of bits required to hold this value. In this example, we need 32 bits (given by [31:0]) to hold the value 6000000.
Next, we use a continuous assignment to invert the reset button signal (so that it can trigger a reset on a rising edge). Alternatively, you could could use “negegde” in the sensitivity list for the reset button if you did not want to invert the signal. The counter is implemented in the always block, which defines a set of procedural assignments.
// Count up on (divided) clock rising edge or reset on button push
always @ (posedge div_clk or posedge rst) begin
if (rst == 1'b1) begin
led <= 4'b0;
end else begin
led <= led + 1'b1;
end
end
Next, we need to create the actual clock divider, which converts the 12 MHz clk signal to a 1 Hz signal (named “clk_div”).
The clock divider is just another counter block. Just like the first counter block, the counter signal resets to 0 if the reset button is pressed. Next, if the counter reaches the maximum value (6,000,000) as set by the max_count localparam, it resets back to 0 and the div_clk signal toggles. Finally, if neither of those conditions are met, the count value increments by one.
// Clock divider
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
count <= 32'b0;
end else if (count == max_count) begin
count <= 32'b0;
div_clk <= ~div_clk;
end else begin
count <= count + 1;
end
end
We count to 6,000,000 twice to make one complete cycle (low and high) for the div_clk signal. That’s how we convert 12 MHz to 1 Hz!
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门5 - 有限状态机 更新发布于 2022年01月11日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
Button bounce occurs when a switch or pushbutton’s contacts rapidly connect and disconnect in a short amount of time. This generates noise on the button’s line. If you are simply looking for a rising or falling edge to detect a button press, your design (whether hardware or software) might read the button bounce as multiple button presses due to the multiple edges.
In the previous episode, I demonstrated a simple counter using a button as the “clock” input. Unfortunately, due to button bounce, the counter would often skip values, which creates a frustrating user experience.
Your challenge is to design a state machine in your FPGA using Verilog that corrects this button bounce (e.g. a button debouncing circuit). The output should be the same as in the previous episode: 4 LEDs that count up in binary on each button press.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
[Edit 12/28/2021] IMPORTANT: the solution below is a useful button debouncing design for beginners, but it can lead to errors down the road when we talk about metastability. For a better button debounce circuit that does not rely on an asynchronous clock signal, please see here.
Here is how I designed my state machine for the button debouncing circuit. Note that I used a simple Moore state machine; your solution might be different!
The idea is to wait for some amount of time (say, about 40 ms) after a rising edge on the signal line (inverted button line, so high = button press) and then sample the line again. If it is still high, then we know the button has been pressed. If the input signal line has gone back to low, then we start the state machine over again (and do not increment our LED counter). I set the MAX value to 479,999, which should provide a wait period of 40 ms (assuming a 12 MHz clock) by delaying for 480,000 clock cycles.
Notice that I use 4 states in my FSM. I start with a “HIGH” state to give the state machine a place to reset. If I had just started with the “LOW” state, the FSM would immediately transition to “WAIT” so long as the button was held down. As a result, the LED counter would increment every 40 ms. Having a “repeat while pressed” feature might be desirable in some cases, but it’s not something I wanted here.
The state machine spends 1 clock cycle in the "PRESSED" state where the LED counter (stored in the "led" register) increments by 1.
I used a Moore state machine to make it a little easier to comprehend. You could try recreating this functionality using a Mealy state machine. I encourage you to try it!
Here is my physical constraint file and Verilog code:
solution-button-debouncing.pcf
# Oscillator
set_io clk 21
# LEDs
set_io led[0] 99
set_io led[1] 98
set_io led[2] 97
set_io led[3] 96
# PMOD I/O
set_io -pullup yes rst_btn 78
set_io -pullup yes inc_btn 79
solution-button-debouncing.v
// One possible way to debounce a button press (using a Moore state machine)
//
// Inputs:
// clk - 12 MHz clock
// rst_btn - pushbutton (RESET)
// inc_btn - pushbutton (INCREMENT)
//
// Outputs:
// led[3:0] - LEDs (count from 0x0 to 0xf)
//
// One press of the increment button should correspond to one and only one
// increment of the counter. Use a state machine to identify the edge on the
// button line, wait 40 ms, and sample again to see if the button is still
// pressed.
//
// Date: November 5, 2021
// Author: Shawn Hymel
// License: 0BSD
// Use a state machine to debounce the button, which increments a counter
module debounced_counter (
// Inputs
input clk,
input rst_btn,
input inc_btn,
// Outputs
output reg [3:0] led
);
// States
localparam STATE_HIGH = 2'd0;
localparam STATE_LOW = 2'd1;
localparam STATE_WAIT = 2'd2;
localparam STATE_PRESSED = 2'd3;
// Max counts for wait state (40 ms with 12 MHz clock)
localparam MAX_CLK_COUNT = 20'd480000 - 1;
// Internal signals
wire rst;
wire inc;
// Internal storage elements
reg [1:0] state;
reg [19:0] clk_count;
// Invert active-low buttons
assign rst = ~rst_btn;
assign inc = ~inc_btn;
// State transition logic
always @ (posedge clk or posedge rst) begin
// On reset, return to idle state and restart counters
if (rst == 1'b1) begin
state <= STATE_HIGH;
led <= 4'd0;
// Define the state transitions
end else begin
case (state)
// Wait for increment signal to go from high to low
STATE_HIGH: begin
if (inc == 1'b0) begin
state <= STATE_LOW;
end
end
// Wait for increment signal to go from low to high
STATE_LOW: begin
if (inc == 1'b1) begin
state <= STATE_WAIT;
end
end
// Wait for count to be done and sample button again
STATE_WAIT: begin
if (clk_count == MAX_CLK_COUNT) begin
if (inc == 1'b1) begin
state <= STATE_PRESSED;
end else begin
state <= STATE_HIGH;
end
end
end
// If button is still pressed, increment LED counter
STATE_PRESSED: begin
led <= led + 1;
state <= STATE_HIGH;
end
// Default case: return to idle state
default: state <= STATE_HIGH;
endcase
end
end
// Run counter if in wait state
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
clk_count <= 20'd0;
end else begin
if (state == STATE_WAIT) begin
clk_count <= clk_count + 1;
end else begin
clk_count <= 20'd0;
end
end
end
endmodule
Synthesize and upload this design to your FPGA. You might need to press the RESET button to put the state machine back in the first state (“HIGH”). Then, try pressing the INCREMENT button to increment the LED counter. You should (hopefully) not see any more skips!
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门6 - Verilog模块和参数 更新发布于 2022年01月11日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
Your challenge is to create a design that counts up on the LEDs from 0x0 to 0xF and then back down again from 0xF to 0x0. The design should repeat endlessly, counting up and down forever. Additionally, the counter should be slow enough that you can see the individual counts with your eyes, meaning a single count should occur every 0.5 to 1.0 seconds.
You should create this design using modules. I recommend starting with the clock divider module we created in the video. The code for that module can be found here.
Additionally, you will likely want to use a finite state machine (FSM) as the basis for your counter module. You’re welcome to use either Moore-type or Mealy-type state machines as a starting point.
Finally, you will want a top-level design to tie all of these modules together to run your up-and-down counter.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
Here is how I designed my particular up-and-down counter:
Things are getting a little more complicated! We have to add a good amount of glue logic in our top-level design to make everything work.
To start, we instantiate a clock-divider module so that the clock signal that drives our counters (and other glue logic) is 1 or 2 Hz (slow enough that we can see the counters changing values on the LEDs) rather than 12 MHz.
We then instantiate two different counters. We’ll add parameters to our module code so that we can set the instantiated counter to count up or count down. Notice that the done line from one counter feeds into the go input on the other counter. This is how one counter triggers the next counter to start.
We also need to register a simple initial go pulse to one of the counters. The input to this always block will be the reset line so that when a reset signal is seen, a simple pulse will start the whole counting sequence. We use an OR gate to allow either the done signal from the down counter or the initial go pulse to start the up counter.
Finally, we use a multiplexer (mux) to switch which counter has control of the output LEDs. We need to remember if we’re counting up or counting down, so we create some simple glue logic that toggles the mux control line depending on which done signal is seen from the counter modules.
Rather than paste all of the code in this tutorial, you can find the Verilog for the different modules here:
Paste these files into a new project directory. Navigate to the directory in a terminal. Initialize your project, build it, and upload to the iCEstick:
apio init -b icestick
apio verify
apio build
apio upload
Once the uploading process is complete, you should be able to press the RESET button to start the counting process.
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门7 - Verilog Testbenches及仿真 更新发布于 2022年01月11日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
Your challenge is to write a testbench and simulate your button debouncing design from the episode 5 challenge. Your testbench should toggle the increment button line (from high to low to simulate an actual button press). Each time there is a full button press (e.g. the simulated user holds the button down for longer than some predetermined amount of time), the counter increments by one.
Note that you should simulate some form of button bounce. If I zoom in on one of the transitions in my solution, you can see how the inc_btn line toggles rapidly a few times before settling on a value.
Here are a few hints:
- You might need to use the Verilog system functions $random or $urandom to generate random numbers for your bounce simulation
- You can use for loops in testbench Verilog code (and sometimes synthesizable code, too)
- You might need to adjust your simulated clock speed or timer delay. Trying to simulate 40 ms with a 12 MHz will take a long time and produce a very large .vcd file!
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
I made a slight adjustment to the original button debouncing code: I turned the MAX_CLK_COUNT value into a parameter instead of a local parameter. This allows me to instantiate the debounce module with a different wait period so I can more easily simulate the design.
button-debouncing.v
// Use a state machine to debounce the button, which increments a counter
module debounced_counter #(
// Parameters
parameter MAX_CLK_COUNT = 20'd480000 - 1
) (
// Inputs
input clk,
input rst_btn,
input inc_btn,
// Outputs
output reg [3:0] led
);
// States
localparam STATE_HIGH = 2'd0;
localparam STATE_LOW = 2'd1;
localparam STATE_WAIT = 2'd2;
localparam STATE_PRESSED = 2'd3;
// Internal signals
wire rst;
wire inc;
// Internal storage elements
reg [1:0] state;
reg [19:0] clk_count;
// Invert active-low buttons
assign rst = ~rst_btn;
assign inc = ~inc_btn;
// State transition logic
always @ (posedge clk or posedge rst) begin
// On reset, return to idle state and restart counters
if (rst == 1'b1) begin
state <= STATE_HIGH;
led <= 4'd0;
// Define the state transitions
end else begin
case (state)
// Wait for increment signal to go from high to low
STATE_HIGH: begin
if (inc == 1'b0) begin
state <= STATE_LOW;
end
end
// Wait for increment signal to go from low to high
STATE_LOW: begin
if (inc == 1'b1) begin
state <= STATE_WAIT;
end
end
// Wait for count to be done and sample button again
STATE_WAIT: begin
if (clk_count == MAX_CLK_COUNT) begin
if (inc == 1'b1) begin
state <= STATE_PRESSED;
end else begin
state <= STATE_HIGH;
end
end
end
// If button is still pressed, increment LED counter
STATE_PRESSED: begin
led <= led + 1;
state <= STATE_HIGH;
end
// Default case: return to idle state
default: state <= STATE_HIGH;
endcase
end
end
// Run counter if in wait state
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
clk_count <= 0;
end else begin
if (state == STATE_WAIT) begin
clk_count <= clk_count + 1;
end else begin
clk_count <= 0;
end
end
end
endmodule
Here is the testbench Verilog code (note the “_tb.v” suffix on the filename!):
button-debouncing_tb.v
// Define timescale
`timescale 1 us / 10 ps
// Define our testbench
module button_debouncing_tb();
// Internal signals
wire [3:0] out;
// Storage elements (buttons are active low!)
reg clk = 0;
reg rst_btn = 1;
reg inc_btn = 1;
integer i; // Used in for loop
integer j; // Used in for loop
integer prev_inc; // Previous increment button state
integer nbounces; // Holds random number
integer rdelay; // Holds random number
// Simulation time: 10000 * 1 us = 10 ms
localparam DURATION = 10000;
// Generate clock signal (about 12 MHz)
always begin
#0.04167
clk = ~clk;
end
// Instantiate debounce/counter module (use about 400 us wait time)
debounced_counter #(.MAX_CLK_COUNT(4800 - 1)) uut (
.clk(clk),
.rst_btn(rst_btn),
.inc_btn(inc_btn),
.led(out)
);
// Test control: pulse reset and create some (bouncing) button presses
initial begin
// Pulse reset low to reset state machine
#10
rst_btn = 0;
#1
rst_btn = 1;
// We can use for loops in simulation!
for (i = 0; i < 32; i = i + 1) begin
// Wait some time before pressing button
#1000
// Simulate a bouncy/noisy button press
// $urandom generates a 32-bit unsigned (pseudo) random number
// "% 10" is "modulo 10"
prev_inc = inc_btn;
nbounces = $urandom % 20;
for (j = 0; j < nbounces; j = j + 1) begin
#($urandom % 10)
inc_btn = ~inc_btn;
end
// Make sure button ends up in the opposite state
inc_btn = ~prev_inc;
end
end
// Run simulation (output to .vcd file)
initial begin
// Create simulation output file
$dumpfile("button-debouncing_tb.vcd");
$dumpvars(0, button_debouncing_tb);
// Wait for given amount of time for simulation to complete
#(DURATION)
// Notify and end simulation
$display("Finished!");
$finish;
end
endmodule
Let’s review some of the important parts of this testbench. First, we generate a constant clock signal at 12 MHz:
always begin
#0.04167
clk = ~clk;
end
Next, we instantiate our debouncing code:
debounced_counter #(.MAX_CLK_COUNT(4800 - 1)) uut (
.clk(clk),
.rst_btn(rst_btn),
.inc_btn(inc_btn),
.led(out)
);
Here, you can see that we set the MAX_CLK_COUNT parameter to 4800. This should give us a delay period of about 400 us (instead of the usual 40 ms) between a detected falling edge and sampling the line again. Without it, you’ll likely find that the simulation will run for a very long time (possibly hours) and your .vcd file will be huge (likely gigabytes).
In our initial begin control block, we briefly pulse the reset line to reset the debouncer’s state machine:
#10
rst_btn = 0;
#1
rst_btn = 1;
Next, we use an outer for loop to toggle the inc_btn line with 1000 cycle delay in between each toggle. We also use an inner for loop to generate up to 20 random button bounce toggles rapidly on the line (with an up to 10 cycle random delay between each simulated bounce). Finally, we ensure that the button ends up in the opposite state after a random number of toggles.
for (i = 0; i < 32; i = i + 1) begin
// Wait some time before pressing button
#1000
// Simulate a bouncy/noisy button press
// $urandom generates a 32-bit unsigned (pseudo) random number
// "% 10" is "modulo 10"
prev_inc = inc_btn;
nbounces = $urandom % 20;
for (j = 0; j < nbounces; j = j + 1) begin
#($urandom % 10)
inc_btn = ~inc_btn;
end
// Make sure button ends up in the opposite state
inc_btn = ~prev_inc;
end
Note that we are using the blocking assignment operator (‘=’) in this block! This means each assignment happens sequentially before the next one executes (similar to how a sequential programming language would operate).
When you are just starting out, a good rule to follow is that you should use non-blocking assignments (‘<=’) in always blocks with a clocked sensitivity list (e.g. always @ (posedge clk)). You should use blocking assignments (‘=’) in always blocks with combinational logic and non-clocked sources (e.g. always @ ( * )). Try not to mix them (at least until you get more comfortable using them).
In testbenches, you can create non-synthesizable code with for loops and initial blocks that only run once. As a result, Verilog starts to look more like a programming or scripting language (for testbenches).
We finally end our testbench with the usual initial block that tells the simulation to run and store the value changes in a particular .vcd file.
initial begin
// Create simulation output file
$dumpfile("button-debouncing_tb.vcd");
$dumpvars(0, button_debouncing_tb);
// Wait for given amount of time for simulation to complete
#(DURATION)
// Notify and end simulation
$display("Finished!");
$finish;
end
Save these files on your computer. Apio will force you to initialize a board (even though you won’t upload your design to a board). From there, verify your code and run the simulation.
apio init -b icestick
apio verify
apio sim
This should cause GTKWave to automatically open, where you can view your waveforms!
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门8 - 存储器和块RAM 更新发布于 2022年01月11日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
Your challenge is to create a simple LED sequencer. You should configure your 4 pushbuttons as follows:
- RST: reset button (globally reset all counters, state machines, etc.)
- SET: set button that records the value on VAL[1:0] into a memory location and then increments the memory pointer
- PTN[0]: first bit of the value/pattern
- PTN[1]: second bit of the value/pattern
The idea is that you can record a simple sequence (up to 8 values) in block RAM. You do this by holding down VAL[1:0] to create a number (e.g. binary 00, 01, 10, or 11) and then pressing the SET button. When the SET button is pressed, the 2-bit binary value is stored in memory and the memory address pointer is increased by one. You repeat the process, holding down a new 2-bit binary number.
You continue doing this until you’ve filled up the sequencer memory with 8 values. At the same time, the sequencer is continuously cycling through the memory, reading each value and displaying it on 2 of the LEDs.
So, if you enter the sequence 0, 1, 0, 1, 2, 3, 2, 1, on the next full loop, the sequencer would display the following binary values on the LEDs: 00, 01, 00, 01, 10, 11, 10, 01.
Hint: you will likely need to use many of the concepts and modules you wrote from previous videos/challenges! This includes a clock divider and a button debounce module. You will also likely want to make these designs modular and instantiate them in a top-level design. Finally, I highly recommend simulating your design to make sure everything works.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
The code for this project is too large to paste onto this page in its entirety. As a result, you can find all of the Verilog modules, physical constraint file, memory initialization text file, and testbench/simulation files in this repository.
In this project, I use the clock-divider, debouncer, and memory Verilog modules written in this and previous episodes/challenges. I also create a top-level design (named sequencer-top.v) that brings together these modules and provides some glue logic:
sequencer-top.v
// Top-level design for sequencer
//
// Inputs:
// clk - 12 MHz clock
// rst_btn - pushbutton (RESET)
// set_btn - pushbutton (SET value at next memory address)
// ptn_0_btn - pushbutton (PATTERN[0] for sequence)
// ptn_1_btn - pushbutton (PATTERN[1] for sequence)
//
// Outputs:
// led[1:0] - LEDs
//
// Push RESET to set everything to 0. Hold PATTERN[0] and/or PATTERN[1] and
// press SET to record value in memory. Continue doing this for up to 8 memory
// locations. Sequence will play on LEDs and loop forever.
//
// Date: November 15, 2021
// Author: Shawn Hymel
// License: 0BSD
// Top-level design for the sequencer
module sequencer_top #(
// Parameters
parameter DEBOUNCE_COUNTS = 480000 - 1, // Counts for debounce wait
parameter STEP_COUNTS = 6000000 - 1, // Clock cycles between steps
parameter NUM_STEPS = 8 // Number of steps
) (
// Inputs
input clk,
input rst_btn,
input set_btn,
input ptn_0_btn,
input ptn_1_btn,
// Outputs
output reg [1:0] led,
output [2:0] unused_led
);
// Internal signals
wire rst;
wire set;
wire set_d;
wire [1:0] ptn;
wire div_clk;
wire [1:0] r_data;
// Storage elements (initialize some values)
reg w_en = 0;
reg r_en = 1'b1; // Always high!
reg [2:0] w_addr = 0;
reg [2:0] r_addr;
reg [1:0] w_data;
reg [2:0] step_counter;
reg [2:0] mem_ptr = 0;
// Turn off unused LEDs
assign unused_led = 3'b000;
// Invert active-low buttons
assign rst = ~rst_btn;
assign set = ~set_btn;
assign ptn[0] = ~ptn_0_btn;
assign ptn[1] = ~ptn_1_btn;
// Clock divider
clock_divider #(
.COUNT_WIDTH(24),
.MAX_COUNT(STEP_COUNTS)
) div (
.clk(clk),
.rst(rst),
.out(div_clk)
);
// Button debouncer for set buttons
debouncer #(
.COUNT_WIDTH(24),
.MAX_CLK_COUNT(DEBOUNCE_COUNTS)
) set_debouncer (
.clk(clk),
.rst(rst),
.in(set),
.out(set_d)
);
// Memory unit
memory #(
.MEM_WIDTH(2),
.MEM_DEPTH(NUM_STEPS),
.INIT_FILE("mem_init.txt")
) mem (
.clk(clk),
.w_en(w_en),
.r_en(r_en),
.w_addr(w_addr),
.r_addr(r_addr),
.w_data(w_data),
.r_data(r_data)
);
// Read from memory each divided clock cycle
always @ (posedge div_clk or posedge rst) begin
if (rst == 1'b1) begin
led <= 0;
r_addr <= 0;
step_counter <= 0;
end else begin
r_addr <= step_counter;
step_counter <= step_counter + 1;
led <= r_data;
end
end
// Register write data as soon as debounced set signal goes high
always @ (posedge set_d) begin
w_data <= ptn;
end
// Handle writing pattern to memory
always @ (posedge clk or posedge rst) begin
// Reset memory address pointer and write enable signal
if (rst == 1'b1) begin
mem_ptr <= 0;
w_en <= 1'b0;
// Set write enable high and increment memory pointer
end else if (set_d == 1'b1) begin
w_addr <= mem_ptr;
w_en <= 1'b1;
mem_ptr <= mem_ptr + 1;
// Reset write enable signal
end else begin
w_en <= 1'b0;
end
end
endmodule
In this design, we invert the button signals and instantiate the clock divider, debouncer, and memory modules. Note that we instantiate the memory module to use block RAM that is 2 bits wide and 8 elements deep.
Next, we read from memory on each divided clock cycle:
// Read from memory each divided clock cycle
always @ (posedge div_clk or posedge rst) begin
if (rst == 1'b1) begin
led <= 0;
r_addr <= 0;
step_counter <= 0;
end else begin
r_addr <= step_counter;
step_counter <= step_counter + 1;
led <= r_data;
end
end
The value in memory is clocked/registered to the output LEDs. The step_counter (i.e. the read memory address pointer) is also increased by one. Because step_counter is only 3 bits wide, it will automatically wrap from 0x7 to 0x0, so we don’t need additional logic to prevent the pointer from exceeding the memory address space.
In parallel, the value on the pattern buttons (ptn[1:0]) is registered so that it can be written to memory on the next (non-divided) clock cycle (even if you let go of the ptn buttons after pressing the set button).
// Register write data as soon as debounced set signal goes high
always @ (posedge set_d) begin
w_data <= ptn;
end
Finally, we handle writing the pattern to memory:
// Handle writing pattern to memory
always @ (posedge clk or posedge rst) begin
// Reset memory address pointer and write enable signal
if (rst == 1'b1) begin
mem_ptr <= 0;
w_en <= 1'b0;
// Set write enable high and increment memory pointer
end else if (set_d == 1'b1) begin
w_addr <= mem_ptr;
w_en <= 1'b1;
mem_ptr <= mem_ptr + 1;
// Reset write enable signal
end else begin
w_en <= 1'b0;
end
end
Whenever the debounced set signal (a single pulse) is seen, the write enable (w_en) line is pulsed for a clock cycle while the pattern is present on the write data (w_data) bus. Note that the pointer for the write address (w_addr) is set from a separate mem_ptr value, which is also incremented.
Save all of these files in a single folder. Initialize the project with apio, verify, and run simulation:
apio init -b icestick
apio verify
apio sim
You should see GTKWave open with the simulated design. Zoom out, and make sure that the pattern is stored in memory. Note that the initialization memory file has the pattern stored as: 0, 0, 0, 0, 1, 1, 1, 1. So, you should see: 3, 3, 0, 0, 1, 1, 1, 1 after the pattern has been set from the simulated buttons.
Now, enter the following to upload the design to your FPGA:
apio build
apio upload
Try entering new patterns on the buttons and see if the sequencer replays them!
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门9 - 锁相环和毛刺 更新发布于 2022年01月11日
Required Hardware
You will need only the Icarus Verilog simulator and GTKWave waveform viewer for this challenge, as you will be required to simulate gate delays to prove that your design reduces glitches.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Challenge
As shown in the video, most basic counter designs will emit glitches while the binary numbers transition from one to another. For example, if you want to add 1 to ‘b0111, you’d need to change 4 bits. The ripple carry counter we showed in the video changes one bit at a time. If there is any gate delay (or more accurately, LUT delay), these transitions will be noticeable on the output of the counter.
However, so long as you register the output value after it has settled, you will not notice any glitches. As you start working with faster clock speeds, such glitches may become apparent.
Your challenge is to build a counter that reduces or eliminates glitches. You must also demonstrate your design by simulating it. Note that any time you use a gate or look-up table (LUT), you should add a 1 ns delay so that you can visualize any transitions or glitches in the waveform viewer.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
One option is to use a carry-lookahead adder (CLA) design for your counter. This adder computes the carry bits before the sum, which reduces the total amount of delay for the adder to perform its computation.
Another option is to use a reflected binary code (RBC), which is also called a “Gray code” (named after its inventor, Frank Gray). A Gray code is a reordering of bits in a counter so that only one bit changes at a time. It prevents glitches from appearing during the counting process, and it’s used frequently in digital communications.
Note that for a Gray code to work, you must use exactly 2^n counts (where n is a positive integer). For example, you must use all possible values in 0 through 15 for a 4-bit Gray counter to ensure that only one bit is changing at a time.
Finally, you can use a lookup table or finite state machine (FSM) to guarantee that the output latency of the changing value remains constant. In other words, there will not be any glitches because all of the bits in the counter value update at the same time. The FSM is quite simple for a counter. A 2-bit counter might consist of 4 states that transitions from one to the next each clock cycle: ‘b00 to ‘b01 to ‘b10 to ‘b11.
Here is my implementation that combines a 4-bit Gray code counter with an FSM to ensure that the output update latency remains constant. Note that all code is stored in a testbench, as we induce simulated lookup delays in the FSM.
// Defines timescale for simulation: <time_unit> / <time_precision>
`timescale 1 ns / 1 ps
// 4-bit gray code counter FSM
module gray_counter (
// Inputs
input clk,
input rst,
// Outputs
output reg [3:0] count
);
// State machine transitions (with simulated delays)
always @ (posedge clk or posedge rst) begin
if (rst == 1'b1) begin
count <= 0;
end else begin
case (count) // Gray code state
4'b0000: #1 count <= 4'b0001; // 0
4'b0001: #1 count <= 4'b0011; // 1
4'b0011: #1 count <= 4'b0010; // 2
4'b0010: #1 count <= 4'b0110; // 3
4'b0110: #1 count <= 4'b0111; // 4
4'b0111: #1 count <= 4'b0101; // 5
4'b0101: #1 count <= 4'b0100; // 6
4'b0100: #1 count <= 4'b1100; // 7
4'b1100: #1 count <= 4'b1101; // 8
4'b1101: #1 count <= 4'b1111; // 9
4'b1111: #1 count <= 4'b1110; // 10
4'b1110: #1 count <= 4'b1010; // 11
4'b1010: #1 count <= 4'b1011; // 12
4'b1011: #1 count <= 4'b1001; // 13
4'b1001: #1 count <= 4'b1000; // 14
4'b1000: #1 count <= 4'b0000; // 15
default: #1 count <= 4'b0000;
endcase
end
end
endmodule
// Define our testbench
module gray_counter_tb();
// Simulation time: 10000 * 1 ns = 10000 ns
localparam DURATION = 10000;
// Internal signals
wire [3:0] out;
// Internal storage elements
reg clk = 0;
reg rst = 0;
// Generate clock signal: 1 / ((2 * 4.167) * 1 ns) = 119,990,400.77 Hz
always begin
#4.167
clk = ~clk;
end
// Instantiate gray code counter
gray_counter gray (.clk(clk), .rst(rst), .count(out));
// Pulse reset line high at the beginning
initial begin
#10
rst = 1;
#1
rst = 0;
end
// Run simulation (output to .vcd file)
initial begin
// Create simulation output file
$dumpfile("gray-code-counter_tb.vcd");
$dumpvars(0, gray_counter_tb);
// Wait for given amount of time for simulation to complete
#(DURATION)
// Notify and end simulation
$display("Finished!");
$finish;
end
endmodule
The gray_counter module should be straightforward. At each clock cycle, the counter value increments to the next Gray code value as given by the 16 states. While there is some lookup latency (simulated), it should be constant across all values. By using a Gray code, only one bit changes at a time, which makes this design resistant to glitches.
Here is an example of the Gray code in action as a waveform:
Recommended Reading
The following content might be helpful if you would like to dig deeper:
Lattice Semiconductor的FPGA使用入门 更新发布于 2022年01月11日
本文来自Digikey技术博客:
FPGA 基础知识 – 第 2 部分:Lattice Semiconductor 的 FPGA 使用入门
作者:Clive "Max" Maxfield
投稿人:Digi-Key 北美编辑
2019-12-18
编者按:最佳处理解决方案常常是由 RISC、CISC、图形处理器与 FPGA 的组合提供,或由 FPGA 单独提供,或以硬处理器内核作为部分结构的 FPGA 提供。然而,许多设计人员不熟悉 FPGA 的功能、其发展脉络以及如何使用 FPGA。本系列文章由多个部分组成,第 1 部分详细介绍了 FPGA 并说明了为何需要这类器件。作为第 2 部分,本文着重介绍了 FPGA 供应商 Lattice Semiconductor 推出的 FPGA 器件系列及相关设计工具。第 3、4 和 5 部分则相应介绍 Altera、Microchip 和 Xilinx 的 FPGA 产品。
如第 1 部分所述,现场可编程门阵列 (FPGA) 具有诸多特性,无论是单独使用,抑或采用多样化架构,皆可作为宝贵的计算资产;但是许多设计人员并不熟悉 FPGA,亦不清楚如何将这类器件整合到设计中。
解决办法之一是深入研究主要供应商提供的 FPGA 架构及相关工具。本文则从 Lattice Semiconductor 产品系列开始着手。
FPGA 选件高级概述市场上有许多不同类型的 FPGA,每种类型都有不同的功能和特性组合。可编程结构是所有 FPGA 的核心,并以可编程逻辑块阵列的形式呈现(图 1a)。FPGA 结构进一步扩展可包括 SRAM 块(称为块 RAM (BRAM))、锁相环 (PLL) 和时钟管理器等组件(图 1b)。此外,还可以添加数字信号处理 (DSP) 块(DSP 切片)和高速串行器/解串器 (SERDES)(图 1c)。
图 1:最简单的 FPGA 仅包含可编程结构和可配置通用 IO (GPIO) (a);不同架构是在此基本结构上增加其他元件而形成:SRAM 块、PLL 和时钟管理器 (b),DSP 块和 SERDES 接口 (c),以及硬处理器内核和外设 (d)。(图片来源:Max Maxfield)
外设接口功能(如 CAN、I2C、SPI、UART 和 USB)可以实现为可编程结构中的软内核,但许多 FPGA 将其作为硬内核在硅片中实现。同样,微处理器也可以实现为可编程结构中的软内核,或作为硬内核在硅片中实现(图 1d)。不同 FPGA 针对不同的市场和应用提供不同的功能、特性和容量集合。
FPGA 供应商有很多,包括 Altera(被 Intel 收购)、Atmel(被 Microchip Technology 收购)、Lattice Semiconductor、Microsemi(也被 Microchip Technology 收购)和 Xilinx。
所有这些供应商都提供多个 FPGA 系列;有的提供片上系统 (SoC) FPGA(包含硬处理器内核),有的则针对航天等高辐射环境提供耐辐射器件。由于产品系列众多,每个系列提供不同的资源,因此为眼前的任务选择最佳器件可能很棘手。本文着重介绍了 Lattice Semiconductor 推出的器件系列及相关设计工具。
Lattice Semiconductor 的 FPGA 介绍Lattice Semiconductor 的 FPGA 产品范围覆盖低中端,专注于低功耗器件,应用遍及通信、计算、工业、汽车和消费类等迅速增长的市场,以此帮助客户解决从边缘到云的网络问题。
Lattice 推出了四个主要的 FPGA 系列:
- iCE(堪称世界上最小的超低功耗 FPGA)
- CrossLink 和 CrossLinkPlus(针对高速视频和传感器应用进行了优化)
- MachXO(针对桥接、扩展、平台管理和安全应用进行了优化)
- ECP(针对连接和加速应用的通用器件)
此外,Lattice 还推出了诸多设计和验证工具套件,包括 Lattice Diamond 软件(用于 CrossLink/CrossLinkPlus、MachXO 和 ECP 器件)、Lattice Radiant 软件(用于 iCE FPGA 和未来架构)、LatticeMico(用于创建基于软微处理器设计的图形工具),以及 Lattice sensAI 堆栈和神经网络编译器(用于人工智能 (AI) 和机器学习 (ML) 设计)。
传统:ECP FPGA许多设计人员都认为 Lattice 的 ECP 器件是“传统”FPGA 器件。这类器件采用 10 mm x 10 mm 封装,引脚间距 0.5 mm,最多可包含 85,000 (k) 个四输入查找表 (LUT)。静态和动态功耗较低,协议无关单通道 SERDES 功能器件的功耗低于 0.25 W,四通道 SERDES 功能器件则低于 0.5 W。
除了 SRAM 块、数字信号处理 (DSP) 块、锁相环 (PLL) 和时钟管理器外,ECP FPGA 还具有可编程 I/O,支持 LVCMOS 33/25/18/15/12、XGMII、LVTTL、LVDS、Bus-LVDS、7:1 LVDS、LVPECL 和 MIPI D-PHY 输入/输出接口。
ECP FPGA 的配置单元基于 SRAM,因此与所有其他基于 SRAM 的 FPGA 一样,仅当系统通电时,才能由外部(例如闪存器件、微处理器、微控制器)加载配置。
ECP 器件的一个典型示例是 LFE5UM5G-25F-8BG381C,支持 ECP5 5G SERDES 的 FPGA,采用 10 mm x 10 mm 封装。为了使设计人员能更好地研究和试验 ECP5 FPGA 系列的特性,Lattice 还推出了相应的 ECP5-5G 开发板 LFE5UM5G-45F-VERSA-EVN(图 2)。
图 2:ECP5 评估板可作为原型开发板,具有丰富的逻辑块、I/O、5G SERDES 和扩展排针。(图片来源:Lattice Semiconductor)
iCE 器件是市面上现有体积最小的 FPGA,该系列中最小的器件采用 1.4 mm x 1.4 mm 封装,提供 18 个 I/O。iCE FPGA 采用灵活的逻辑架构,最多可包含 5k 个四输入 LUT,具有高达 128 Kb 的 Lattice 嵌入式 sysMEM BRAM、1 Mb 的单端口 RAM (SPRAM)、高性能 DSP 块,以及可定制 I/O。
iCE FPGA 器件体积小、功耗低(对于多数应用,休眠电流低至 75 µA,有功电流范围从 1 至 10 mA),但功能却很强大。例如,这类器件可实现人工神经网络 (ANN),可用于模式匹配以实现边缘应用中“始终开启”的人工智能。
iCE FPGA 的配置数据存储在非易失性存储器 (NVM) 中,因而属于一次性可编程 (OTP) 器件。尽管如此,器件仍包含基于 SRAM 的配置单元。开发过程中,可将设计直接由外部加载至基于 SRAM 的配置单元来进行测试。设计提交后,即可将其加载至 NVM 中。器件上电后,存储于 NVM 的配置将以并行传输方式自动复制到基于 SRAM 的配置单元中。
iCE 器件的一个示例是 ICE40UL1K-SWG16ITR1K iCE40 UltraLite,它是世界上外形尺寸最小的 FPGA(截至本文发布时),采用 1.4 mm x 1.4 mm 封装,静态功率为 42 µW。代表性开发板包括 HM01B0-UPD-EVN Himax HM01B0 UPduino 扩展板和 sensAI 模块化演示板(图 3)。
图 3:Himax HM01B0 UPduino 扩展板是一款完整的开发套件,适用于通过以视觉和声音作为传感器输入来实现人工智能 (AI)。(图片来源:Lattice Semiconductor)
该套件基于 UPduino 2.0 开发板,这款 Arduino 外形尺寸的快速原型开发板,具有 iCE40 UltraPlus FPGA 的性能和 I/O 功能。此外,套件还包括 Himax HM01B0 低功耗图像传感器模块和两个 I2S 麦克风。
专用:CrossLink 和 CrossLinkPlus FPGACrossLink 和 CrossLinkPlus 系列都是专用 FPGA,除了可编程逻辑和强大的 I/O 功能外,器件规格经过强化,因而可广泛适用于工业和汽车应用。其中包括移动行业处理器接口 (MIPI) D-PHY 高速数据通信物理层标准、相机串行接口 2 (CSI2) 和显示串行接口 2 (DSI2) 内核;封装分别采用 6 mm x 6 mm (CrossLink) 和 3.5 mm x 3.5 mm (CrossLinkPlus)。
与 iCE FPGA 一样,CrossLink 器件的配置数据存储在 OTP NVM 中,也包含基于 SRAM 的配置单元,开发过程中可直接加载进行测试。设计提交后,则将其加载至 NVM 中。器件上电后,数据以并行传输方式自动复制到基于 SRAM 的配置单元中。相比之下,CrossLinkPlus 器件的配置单元基于闪存,因此可根据需要对这些器件进行重新编程;此外,器件还具有不足 10 ms 的即时启动功能。
CrossLink 器件的一个示例是 LIF-MD6000-6JMG80I,具有 5,936 个逻辑单元和 37 个 I/O,总 RAM 位数为 184,320。在着手开始嵌入式视觉设计时,设计人员可借助 LF-EVDK1-EVN 嵌入式视觉开发套件,将基于 CrossLink 的 MIPI 输入与 ECP5 FPGA 处理整合,用以进行嵌入式视觉设计的原型开发(图 4)。
图 4:Lattice 的 LF-EVDK1-EVN 嵌入式视觉开发套件为嵌入式系统设计人员提供了软件和硬件原型开发环境,套件功能包括输入和输出板之间的组合和匹配,以连接各类图像传感器和显示屏。(图片来源:Lattice Semiconductor)
MachXO FPGA 具有数百个 I/O,对需要 GPIO 扩展、接口桥接和上电管理功能的各种应用而言,可谓绝佳选择。系列中的最新产品旨在符合 NIST 标准,另增安全功能以确保系统硬件和固件的安全性。
MachXO FPGA 具有一组功能强大的 GPIO,包括“热插拔”等功能,因而无论电源轨状态如何,都可向 I/O 施加电压。此外,虽然多数 FPGA 输入默认为上拉状态,但 MachXO 输入默认为下拉状态,因此非常适合控制功能应用。MachXO FPGA 具有不足 10 ms 的即时启动功能,可作为“先启后停”控制器件的理想解决方案,在系统上电和断电期间用于对其他元器件进行管理和排序。
MachXO 器件的配置数据存储在闪存中。这类器件也包含基于 SRAM 的配置单元。器件上电后,存储于闪存的配置数据将以并行传输方式自动复制到基于 SRAM 的配置单元中。此外,器件运行时,可将新配置加载至闪存中,之后可选择在适当的时候将新配置复制到 SRAM 单元中。
MachXO 器件的一个典型示例是 LCMXO3LF-9400C-6BG256C,具有 9,400 个逻辑单元和 206 个 I/O,总 RAM 位数为 442,368。代表性开发板是 LCMXO3LF-6900C-S-EVN MachXO3 入门套件(MachX03L 版本)。
图 5:MachXO3L 入门套件是一款基础分线板,在基于 MachXO3L 的设计中可用于简单的评估和开发。(图片来源:Lattice Semiconductor)
套件板具有 SPI 闪存,可用于评估外部启动或双重启动功能。评估 MIPI DSI 和 CSI2 I/O 时,建议使用 LCMXO3L-DSI-EVN MachXO3L DSI 分线板;评估高速差分 I/O 时,建议使用 LCMXO3L-SMA-EVN MachXO3L SMA 分线板。
Lattice Semiconductor 的 FPGA 设计和开发伴随 FPGA 发展的最常见技术之一是语言驱动设计 (LDD)。这涉及使用 Verilog 或 VHDL 等硬件描述语言 (HDL),在抽象级别(即寄存器传送级 (RTL))上捕获设计意图。通过逻辑仿真进行验证之后,该表达式将连同目标 FPGA 类型、引脚分配和时序约束(例如最大输入到输出延迟)等其他信息一并传输至合成引擎。合成引擎输出的配置文件可以直接加载至 FPGA 中,或者对基于 SRAM 的 FPGA 而言,可加载至外部存储器件中(图 6)。
图 6:通过逻辑仿真进行验证之后,RTL 设计描述将与 FPGA 类型、引脚分配和时序约束等其他设计细节一并传输至合成引擎。合成引擎输出的配置文件,可以直接加载至 FPGA 中(对基于 NVM 或闪存的器件),或加载至外部存储器件中(对基于 SRAM 的器件)。(图片来源:Max Maxfield)
Lattice Diamond 属于这类工具,可提供完整的基于 GUI 的 FPGA 设计和验证环境,适用于 CrossLink、MachXO 和 ECP 器件。
与 Lattice Diamond 一样,Lattice Radiant 也可提供完整的基于 GUI 的 FPGA 设计和验证环境,但后者主要针对 iCE FPGA 和未来架构。
Lattice Radiant 功能众多:
- 行业标准 IEEE 1735 知识产权 (IP) 加密和 Synopsys 设计约束 (SDC),实现最佳互操作性
- 集成的工具组环境,简化设计导航和调试功能
- 全新的 Process Toolbar 支持简单的“一键式”设计执行功能
- 完整的物理到逻辑闭环设计流程,实现交叉探索
- IP 数据打包功能,使开发人员和第三方 IP 提供商能够以适合分发的形式打包经加密的 IP 数据包
Lattice 推出了两款软处理器内核——LatticeMico8 和 LatticeMico32,两者均可应用于 FPGA 的可编程结构。
LatticeMico8 是一款 8 位微控制器,针对 MachXO2 可编程逻辑器件 (PLD) 系列进行了优化和全面测试。此外,该器件也可用于其他 FPGA 系列的参考设计。微控制器内核结合了完整的 18 位宽指令集与 32 个通用寄存器,在保留丰富功能的同时,最大限度地减少消耗的器件资源——最小配置中 LUT 少于 200 个。
LatticeMico32 是一款 32 位哈佛架构 RISC 微处理器。LatticeMico32 结合了 32 位宽指令集与 32 个通用寄存器,因而性能和灵活性适合各种市场应用。采用 RISC 架构,在确保性能满足各种应用所需的同时,使内核消耗的器件资源最少。为了加速微处理器系统的开发,可将 LatticeMico32 与几种兼容 WISHBONE 控制器的可选外设元器件集成。
LatticeMico 系统开发工具具有图形用户界面,使用户可对 LatticeMico 处理器内核和外设进行拖放式操作,将其连接至总线,并为各元器件定义各种参数,例如在处理器地址空间中的位置。系统定义完成后,该工具即可自动生成相应的 RTL 以进行仿真和合成。此外,该系统提供的工具使用户能够生成软件,用以在处理器内核上运行。
机器学习工具:Lattice sensAI 堆栈和神经网络编译器目前,机器学习 (ML) 和人工智能 (AI) 应用广泛部署于各种嵌入式系统和整个物联网 (IoT),包括工业物联网 (IIoT)。
Lattice sensAI 堆栈包含评估、开发和部署基于 FPGA 的 ML/AI 解决方案所需的一切功能,包括模块化硬件平台、示例演示、参考设计、神经网络 IP 内核,软件开发工具和定制设计服务。在消费类和工业物联网应用中,开发人员可借助该堆栈实现灵活的机器学习推断引擎,加快上市时间。
Lattice 卷积神经网络 (CNN) 加速器 IP 内核是用于深度神经网络 (DNN) 的计算引擎。该引擎针对卷积神经网络进行了优化,因此可用于分类、对象检测和跟踪等基于视觉的应用。CNN IP 内核本身可执行所需的计算,因此无需额外添加处理器。
同时,借助 Lattice 神经网络编译器,设计人员可使用 TensorFlow、Caffe 和 Keras 等通用开发框架下创建的神经网络,并将其编译以在 Lattice CNN 和紧凑型 CNN 加速器 IP 内核中实施。
总结最佳设计解决方案常常是由处理器与 FPGA 的组合提供,或由 FPGA 单独提供,或以硬处理器内核作为部分结构的 FPGA 提供。作为一项技术,FPGA 多年来发展迅速,如今已经能够满足灵活性、处理速度、功耗等多方面的设计需求,非常适合智能接口、机器视觉和 AI 等众多应用。
如上所述,Lattice Semiconductor 的 FPGA 产品范围覆盖低中端,专注于低功耗器件,应用遍及通信、计算、工业、汽车和消费类等迅速增长的市场,以此解决从边缘到云的网络问题。此外,Lattice 还推出了若干设计和验证工具套件,适用于基于语言的设计、基于图形处理器的设计以及专注于机器学习和人工智能应用的设计等各种设计流程。
FPGA入门10 - 亚稳态和跨时钟域 更新发布于 2022年02月02日
Required Hardware
You will need only the Icarus Verilog simulator and GTKWave waveform viewer for this challenge, as you will be required to simulate gate delays to prove that your design reduces glitches.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCE40 Memory Usage Guide
- iCEstick Evaluation Kit User’s Guide
Challenge
As shown in the video, metastability can occur if a setup or hold time violation occurs. This type of anomaly is prevalent when working with asynchronous signals (e.g. signals that are not clocked) or with signals from another clock domain (e.g. signals that are not clocked on the same system clock). You can see metastability in action in this blog post by Colin O’Flynn.
To keep things simple, it’s often advised that FPGA designers should only work with synchronous signals and use one clock for the entire design (or a divided version of the same clock). However, sometimes you must work across clock domains. For example, if you have another device talking to your FPGA over SPI that drives the clock (SCK) line. If the SCK line toggles at 5 MHz and the FPGA clock toggles at 12 MHz, then we must cross clock domains.
One of the most common techniques for working across clock domains is to use a “synchronizer” circuit. This includes a chain of 2 or more flip-flops that operate on the receiving clock signal. You can read more about the synchronizer here.
Additionally, some FPGA block RAM allows you to read and write using different clock signals. This is known as “dual-port RAM.” The block RAM in our iCE40-HX1K offers dual-port capabilities. We can use that to construct a first-in, first out (FIFO) system that allows us to pass data from one clock domain to another. This might include queuing up samples taken from a sensor or acting as a buffer for a communication system (e.g. SPI) that operates on a different clock domain.
Avoiding metastability in a FIFO can be quite difficult. As a result, it often helps to turn to the experts who have spent years perfecting such designs. One such FIFO design can be found here. Clifford Cummings has graciously provided us with a detailed design of his FIFO as well as Verilog code that we can implement.
Your challenge is to implement the FIFO outlined in the above paper, build a testbench for it, and test it with Icarus Verilog.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
Here is my implementation. Note that I combined some of the modules from Clifford Cummings’s paper, but the design should work the same.
async-fifo.v
// Asynchronous FIFO module
module async_fifo #(
// Parameters
parameter DATA_SIZE = 8, // Number of data bits
parameter ADDR_SIZE = 4 // Number of bits for address
) (
// Inputs
input [DATA_SIZE-1:0] w_data, // Data to be written to FIFO
input w_en, // Write data and increment addr.
input w_clk, // Write domain clock
input w_rst, // Write domain reset
input r_en, // Read data and increment addr.
input r_clk, // Read domain clock
input r_rst, // Read domain reset
// Outputs
output w_full, // Flag: 1 if FIFO is full
output reg [DATA_SIZE-1:0] r_data, // Data to be read from FIFO
output r_empty // Flag: 1 if FIFO is empty
);
// Constants
localparam FIFO_DEPTH = (1 << ADDR_SIZE);
// Internal signals
wire [ADDR_SIZE-1:0] w_addr;
wire [ADDR_SIZE:0] w_gray;
wire [ADDR_SIZE-1:0] r_addr;
wire [ADDR_SIZE:0] r_gray;
// Internal storage elements
reg [ADDR_SIZE:0] w_syn_r_gray;
reg [ADDR_SIZE:0] w_syn_r_gray_pipe;
reg [ADDR_SIZE:0] r_syn_w_gray;
reg [ADDR_SIZE:0] r_syn_w_gray_pipe;
// Declare memory
reg [DATA_SIZE-1:0] mem [0:FIFO_DEPTH-1];
//--------------------------------------------------------------------------
// Dual-port memory (should be inferred as block RAM)
// Write data logic for dual-port memory (separate write clock)
// Do not write if FIFO is full!
always @ (posedge w_clk) begin
if (w_en & ~w_full) begin
mem[w_addr] <= w_data;
end
end
// Read data logic for dual-port memory (separate read clock)
// Do not read if FIFO is empty!
always @ (posedge r_clk) begin
if (r_en & ~r_empty) begin
r_data <= mem[r_addr];
end
end
//--------------------------------------------------------------------------
// Synchronizer logic
// Pass read-domain Gray code pointer to write domain
always @ (posedge w_clk or posedge w_rst) begin
if (w_rst == 1'b1) begin
w_syn_r_gray_pipe <= 0;
w_syn_r_gray <= 0;
end else begin
w_syn_r_gray_pipe <= r_gray;
w_syn_r_gray <= w_syn_r_gray_pipe;
end
end
// Pass write-domain Gray code pointer to read domain
always @ (posedge r_clk or posedge r_rst) begin
if (r_rst == 1'b1) begin
r_syn_w_gray_pipe <= 0;
r_syn_w_gray <= 0;
end else begin
r_syn_w_gray_pipe <= w_gray;
r_syn_w_gray <= r_syn_w_gray_pipe;
end
end
//--------------------------------------------------------------------------
// Instantiate incrementer and full/empty checker modules
// Write address increment and full check module
w_ptr_full #(.ADDR_SIZE(ADDR_SIZE)) w_ptr_full (
.w_syn_r_gray(w_syn_r_gray),
.w_inc(w_en),
.w_clk(w_clk),
.w_rst(w_rst),
.w_addr(w_addr),
.w_gray(w_gray),
.w_full(w_full)
);
// Read address increment and empty check module
r_ptr_empty #(.ADDR_SIZE(ADDR_SIZE)) r_ptr_empty (
.r_syn_w_gray(r_syn_w_gray),
.r_inc(r_en),
.r_clk(r_clk),
.r_rst(r_rst),
.r_addr(r_addr),
.r_gray(r_gray),
.r_empty(r_empty)
);
endmodule
r-ptr-empty.v
// Increment read address and check if FIFO is empty
module r_ptr_empty #(
// Parameters
parameter ADDR_SIZE = 4 // Number of bits for address
) (
// Inputs
input [ADDR_SIZE:0] r_syn_w_gray, // Synced write Gray pointer
input r_inc, // 1 to increment address
input r_clk, // Read domain clock
input r_rst, // Read domain reset
// Outputs
output [ADDR_SIZE-1:0] r_addr, // Mem address to read from
output reg [ADDR_SIZE:0] r_gray, // Gray address with +1 MSb
output reg r_empty // 1 if FIFO is empty
);
// Internal signals
wire [ADDR_SIZE:0] r_gray_next; // Gray code version of address
wire [ADDR_SIZE:0] r_bin_next; // Binary version of address
wire r_empty_val; // FIFO is empty
// Internal storage elements
reg [ADDR_SIZE:0] r_bin; // Registered binary address
// Drop extra most significant bit (MSb) for addressing into memory
assign r_addr = r_bin[ADDR_SIZE-1:0];
// Be ready with next (incremented) address (if inc set and not empty)
assign r_bin_next = r_bin + (r_inc & ~r_empty);
// Convert next binary address to Gray code value
assign r_gray_next = (r_bin_next >> 1) ^ r_bin_next;
// If the synced write Gray code is equal to the current read Gray code,
// then the pointers have caught up to each other and the FIFO is empty
assign r_empty_val = (r_gray_next == r_syn_w_gray);
// Register the binary and Gray code pointers in the read clock domain
always @ (posedge r_clk or posedge r_rst) begin
if (r_rst == 1'b1) begin
r_bin <= 0;
r_gray <= 0;
end else begin
r_bin <= r_bin_next;
r_gray <= r_gray_next;
end
end
// Register the empty flag
always @ (posedge r_clk or posedge r_rst) begin
if (r_rst == 1'b1) begin
r_empty <= 1'b1;
end else begin
r_empty <= r_empty_val;
end
end
endmodule
w-ptr-full.v
// Increment write address and check if FIFO is full
module w_ptr_full #(
// Parameters
parameter ADDR_SIZE = 4 // Number of bits for address
) (
// Inputs
input [ADDR_SIZE:0] w_syn_r_gray, // Synced read Gray pointer
input w_inc, // 1 to increment address
input w_clk, // Write domain clock
input w_rst, // Write domain reset
// Outputs
output [ADDR_SIZE-1:0] w_addr, // Mem address to write to
output reg [ADDR_SIZE:0] w_gray, // Gray adress with +1 MSb
output reg w_full // 1 if FIFO is full
);
// Internal signals
wire [ADDR_SIZE:0] w_gray_next; // Gray code version of address
wire [ADDR_SIZE:0] w_bin_next; // Binary version of address
wire w_full_val; // FIFO is full
// Internal storage elements
reg [ADDR_SIZE:0] w_bin; // Registered binary address
// Drop extra most significant bit (MSb) for addressing into memory
assign w_addr = w_bin[ADDR_SIZE-1:0];
// Be ready with next (incremented) address (if inc set and not full)
assign w_bin_next = w_bin + (w_inc & ~w_full);
// Convert next binary address to Gray code value
assign w_gray_next = (w_bin_next >> 1) ^ w_bin_next;
// Compare write Gray code to synced read Gray code to see if FIFO is full
// If: extra MSb of read and write Gray codes are not equal AND
// 2nd MSb of read and write Gray codes are not equal AND
// the rest of the bits are equal
// Then: address pointers are same with write pointer ahead by 2^ADDR_SIZE
// elements (i.e. wrapped around), so FIFO is full.
assign w_full_val = ((w_gray_next[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
(w_gray_next[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
(w_gray_next[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0]));
// Register the binary and Gray code pointers in the write clock domain
always @ (posedge w_clk or posedge w_rst) begin
if (w_rst == 1'b1) begin
w_bin <= 0;
w_gray <= 0;
end else begin
w_bin <= w_bin_next;
w_gray <= w_gray_next;
end
end
// Register the full flag
always @ (posedge w_clk or posedge w_rst) begin
if (w_rst == 1'b1) begin
w_full <= 1'b0;
end else begin
w_full <= w_full_val;
end
end
endmodule
Here is my testbench:
async-fifo_tb.v
// Define timescale
`timescale 1 us / 10 ps
// Define our testbench
module async_fifo_tb();
// Settings
localparam DATA_SIZE = 8;
localparam ADDR_SIZE = 4;
// Internal signals
wire [DATA_SIZE-1:0] r_data;
wire r_empty;
wire r_full;
// Internal storage elements
reg r_en = 0;
reg r_clk = 0;
reg r_rst = 0;
reg [DATA_SIZE-1:0] w_data;
reg w_en = 0;
reg w_clk = 0;
reg w_rst = 0;
// Variables
integer i;
// Simulation time: 10000 * 1 us = 10 ms
localparam DURATION = 10000;
// Generate read clock signal (about 12 MHz)
always begin
#0.04167
r_clk = ~r_clk;
end
// Generate write clock signal (5 MHz)
always begin
#0.1
w_clk = ~w_clk;
end
// Instantiate FIFO
async_fifo #(
.DATA_SIZE(DATA_SIZE),
.ADDR_SIZE(ADDR_SIZE)
) uut (
.w_data(w_data),
.w_en(w_en),
.w_clk(w_clk),
.w_rst(w_rst),
.r_en(r_en),
.r_clk(r_clk),
.r_rst(r_rst),
.w_full(w_full),
.r_data(r_data),
.r_empty(r_empty)
);
// Test control: write and read data to/from FIFO
initial begin
// Pulse resets high to initialize memory and counters
#0.1
w_rst = 1;
r_rst = 1;
#0.01
w_rst = 0;
r_rst = 0;
// Write some data to the FIFO
for (i = 0; i < 4; i = i + 1) begin
#0.2
w_data = i;
w_en = 1'b1;
end
#0.2
w_en = 1'b0;
// Try to read more than what's in the FIFO
for (i = 0; i < 6; i = i + 1) begin
#0.08334
r_en = 1'b1;
end
#0.08334
r_en = 1'b0;
// Fill up FIFO (and then some)
for (i = 0; i < 18; i = i + 1) begin
#0.2
w_en = 1'b1;
w_data = i;
end
#0.2
w_en = 1'b0;
// Read everything in the FIFO (and then some)
for (i = 0; i < 18; i = i + 1) begin
#0.08334
r_en = 1'b1;
end
#0.08334
r_en = 1'b0;
end
// Run simulation
initial begin
// Create simulation output file
$dumpfile("async-fifo_tb.vcd");
$dumpvars(0, async_fifo_tb);
// Wait for given amount of time for simulation to complete
#(DURATION)
// Notify and end simulation
$display("Finished!");
$finish;
end
endmodule
When I simulate the design, you can see how data is read in the same order in which it was placed into the FIFO.
Additionally, the w_full line goes high when the FIFO is full (all the memory elements are filled with data) and the r_empty line goes high when the FIFO is empty (no data left to read). The internal circuitry prevents reading when empty and writing when full, so the lines mostly act as indicators to your other modules (if you want to know when to stop reading or writing).
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门11 - RISC-V软核处理器 更新发布于 2022年02月02日
Required Hardware
For this challenge, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” by the apio project should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- learn-fpga GitHub repository - contains Bruno Levy’s RISC-V implementation (FemtoRV)
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
Build FemtoRV
Find your board on this README page (note that the FemtoRV has limited support at this time, and I’ll be showing how to use FemtoRV with the iCEstick). Follow the directions to install the required libraries and drivers. Note that you will need Linux (I recommend Raspberry Pi OS or Ubuntu) to build this project.
When you are done installing all of the dependencies and libraries, you’ll want to make some slight configuration changes, as we are not using IRDA, SSD13151 (OLED driver), or MAX7219 (LED matrix driver). Start in the FemtoRV directory:
cd ~/Projects/fpga/learn-fpga/FemtoRV
nano RTL/CONFIGS/icestick_config.v
Comment out the following lines (leave everything else alone):
`define NRV_IO_LEDS
//`define NRV_IO_IRDA
`define NRV_IO_UARD
//`define NRV_IO_SSD13151
//`define NRV_IO_MAX7219
`define NRV_MAPPED_SPI_FLASH
Save and exit. From the FemtoRV directory, call the following:
make ICESTICK
This will take some time. The first time you run it, it will download and install the RISC-V build system (so you can compile C/C++ code).
Challenge
Your challenge is to make buttons work on the FemtoRV. By default, buttons aren’t defined for the iCEstick, so you will need to make some changes to the FemtoRV Verilog code. Hint: look at the port list in femtosoc.v to get an idea of what you need to change. Also, the `-pullup yes` parameter does not work with the build tool used, so you’ll have to use an external pullup or define the internal pullup with the SB_IO directive (you can read about it in this Lattice doc).
Write a simple C program to test your button functionality. For example, whenever a button is held, make the LEDs blink or count up.
Solution
Spoilers below! I highly encourage you to try the challenge on your own before comparing your answer to mine. Note that my solution may not be the only way to solve the challenge.
Navigate to the FemtoRV directory and modify the PCF file:
cd ~/Projects/fpga/learn-fpga/FemtoRV
nano BOARDS/icestick.pcf
There, comment out the OLED and LED matrix pins. Add new button pin definitions.
set_io pclk 21
#set_io oled_DIN 91
#set_io oled_CLK 90
#set_io oled_CS 88
#set_io oled_DC 87
#set_io oled_RST 78
set_io D1 99
set_io D2 98
set_io D3 97
set_io D4 96
set_io D5 95
set_io TXD 8
set_io RXD 9
#set_io ledmtx_DIN 81
#set_io ledmtx_CS 80
#set_io ledmtx_CLK 79
set_io spi_cs_n 71
set_io spi_miso 68
set_io spi_mosi 67
set_io spi_clk 70
set_io RESET 47
set_io irda_TXD 105
set_io irda_RXD 106
set_io irda_SD 107
set_io pmod[0] 78
set_io pmod[1] 79
set_io pmod[2] 80
set_io pmod[3] 81
Save and exit. Open the iCEstick configuration file:
nano RTL/CONFIGS/icestick_config.v
Define NRV_IO_BUTTONS (this tells the SOC module to instantiate the buttons module).
/************************* Devices **********************************************************************************/
`define NRV_IO_BUTTONS // Mapped IO to PMOD connector (78, 79, 80, 81)
`define NRV_IO_LEDS // Mapped IO, LEDs D1,D2,D3,D4 (D5 is used to display errors)
//`define NRV_IO_IRDA // In IO_LEDS, support for the IRDA on the IceStick (WIP)
`define NRV_IO_UART // Mapped IO, virtual UART (USB)
//`define NRV_IO_SSD1351 // Mapped IO, 128x128x64K OLED screen
//`define NRV_IO_MAX7219 // Mapped IO, 8x8 led matrix
`define NRV_MAPPED_SPI_FLASH // SPI flash mapped in address space. Use with MINIRV32 to run code from SPI flash.
Save and exit. Open the femtosoc.v file:
nano RTL/femtosoc.v
Define the pmod pins as inputs in the port list for the iCEstick:
…
`ifdef NRV_IO_BUTTONS
`ifdef ICE_FEATHER
input [3:0] buttons,
`elsif ICE_STICK
input [3:0] pmod,
`else
input [5:0] buttons,
`endif
`endif
…
In the “Buttons” section, use the SB_IO module to enable the pull-up resistors on the pmod pins. Because we defined NRV_IO_BUTTONS in the config file, the Buttons module will be instantiated, which will give us access to the buttons bus.
/********************* Buttons *************************************/
/*
* Directly wired to the buttons.
*/
`ifdef NRV_IO_BUTTONS
`ifdef ICE_STICK
wire [3:0] buttons;
SB_IO #(
.PIN_TYPE(6’b000001), // 0000: no output, 01: simple input
.PULLUP(1’b1) // Enable pull-up resistor
) sb_buttons [3:0] ( // Multiple modules
.PACKAGE_PIN({pmod[3], pmod[2], pmod[1], pmod[0]}),
.D_IN_0({buttons[3], buttons[2], buttons[1], buttons[0]})
);
`endif
wire [31:0] buttons_rdata;
Buttons buttons_driver(
.sel(io_word_address[IO_BUTTONS_bit]),
.rdata(buttons_rdata),
.BUTTONS(buttons)
);
`endif
Save and exit. Build and upload the SOC to board. Note that we use “ICESTICK” as our make target:
make ICESTICK
When that’s done, write a quick C program to test the buttons. Create a separate directory to keep your software and create main.c:
cd ~/Projects/fpga
mkdir -p femtorv32/button_test
cd femtorv32/button_test
nano main.c
Here’s one possible program. Note that we are using the IO_IN and IO_BUTTONS macros as defined in femtorv32.h. These read from the memory address associated with the buttons. This is not real memory (e.g. RAM)–it simply tells the SOC to read from the register in the Buttons module.
#include <femtorv32.h>
int main() {
int count = 0;
while (1) {
if ((IO_IN(IO_BUTTONS) & 1) == 0) {
count = (count + 1) % 16;
IO_OUT(IO_LEDS, count);
delay(250);
}
}
return 0;
}
Levy created a Makefile template for us to use that handles importing header files, linking to the required libraries, and calling the RISC-V compiler. All we need to do is include it in a local Makefile:
nano Makefile
Add just the following line to that file:
include ../../learn-fpga/FemtoRV/FIRMWARE/makefile.inc
Save and exit. Use make to build the software. Note that we use “main.prog” as the target: “main” is the name of our main C file and “.prog” tells make to upload the compiled program to our board.
make main.prog
When that’s done, you should be able to hold the first button to see your LEDs counting!
Recommended Reading
The following content might be helpful if you would like to dig deeper:
FPGA入门12 - RISC-V中定制外设 更新发布于 2022年02月02日
Required Hardware
For this tutorial, you will need the following hardware:
- FPGA development board based on the Lattice iCE40. I recommend the iCEstick for this series. However, any of the development boards listed as “supported” for the FemtoRV SOC should work.
- Breadboard
- Pushbuttons
- Jumper wires
- (Optional) USB extension cable
Hardware Connections
The PMOD connector at the end of the iCEstick has the following pinout:
A full pinout of the iCEstick can be found here.
Connect 4 pushbuttons to the iCEstick as follows. Note that you do not need pull-up resistors on the buttons. We will use the internal pull-up resistors available in the FPGA.
Resources
The following datasheets and guides might be helpful as you tackle the challenges:
- GitHub repository - contains all examples and solutions for this series
- learn-fpga GitHub repository - contains Bruno Levy’s RISC-V implementation (FemtoRV)
- Verilog documentation
- Apio tool usage
- iCE40 LP/HX Datasheet
- iCEstick Evaluation Kit User’s Guide
PWM Peripheral
We will create a simple pulse-width modulation (PWM) peripheral. The SOC can write a value to the peripheral’s register. The PWM module will maintain a counter that continuously counts (e.g. up to 4095, resets to 0, and then back to 4095 over and over). Whenever the counter is less than the PWM register value, the output line (e.g. pin) will be high. Whenever it is greater than or equal to the register value, the output line will be low. This creates the following pattern:
So long as our clock speed is fast enough, the PWM (if driving an LED) should appear steady to our eyes.
On your host computer, head to the learn-fpga repository and create the PWM driver:
cd ~/Projects/fpga/learn-fpga/FemtoRV
nano RTL/DEVICES/pwm.v
In that file, enter the following Verilog code. If you’ve been following along with the series, this code should be straightforward to decipher.
// Control brightness of one of the LEDs
module pwm #(
// Parameters
parameter WIDTH = 12 // Default PWM values 0..4095
) (
// Inputs
input clk,
input wstrb, // Write strobe
input sel, // Select (read/write ignored if low)
input [31:0] wdata, // Data to be written (to driver)
// Outputs
output [3:0] led
);
// Internal storage elements
reg pwm_led = 1'b0;
reg [WIDTH-1:0] pwm_count = 0;
reg [WIDTH-1:0] count = 0;
// Only control the first LED
assign led[0] = pwm_led;
// Update PWM duty cycle
always @ (posedge clk) begin
// If sel is high, record duty cycle count on strobe
if (sel && wstrb) begin
pwm_count <= wdata[WIDTH-1:0];
count <= 0;
// Otherwise, continuously count and flash LED as necessary
end else begin
count <= count + 1;
if (count < pwm_count) begin
pwm_led <= 1'b1;
end else begin
pwm_led <= 1'b0;
end
end
end
endmodule
Save and exit. You are welcome to simulate the PWM design with a separate testbench. You can find my PWM code and testbench here.
Memory Addressing
To communicate with peripherals, FemtoRV relies on a unique memory addressing scheme. Memory addresses are 32 bits. However, bits 24..31 and bits 0..1 are not used. Bits 22..23 are used to access different “pages.”
A page is where we want to read or write data. For example, if bits 22...23 are ‘b00, then we can access physical RAM (implemented as block RAM in the iCEstick). We have 20 bits of addresses we can use to communicate with block RAM (bits 2..21). That means addresses 0x00000000..0x003FFFFC (in CPU instructions) are used to read/write to RAM.
However, only the first 6 kB of that space is actually available to us as physical RAM. The iCE40-HX1K has 8 kB of block RAM, and 2 kB are set aside for general purpose registers.
If bits 22..23 are ‘b01, then it means we want to access a special purpose register (e.g. a register in one of our hardware peripherals).
If bits 22..23 are ‘b10, then it means we want to access program memory, which is stored in the SPI flash chip on the iCEstick.
For the special purpose registers (in “I/O memory address space”), peripheral addresses are given by a one-hot encoding scheme. A one-hot scheme means that only one bit is high at a time for each address. There are 20 bits of address space available (bits 2..21), so there are 20 total addresses for us to use for hardware peripherals.
If you look in the HardwareConfig_bits.v file, you can see that bits 0..11 are taken up by existing hardware peripherals, and bits 17..19 are reserved for constant hardware registers (e.g. CPU information). We will need to modify this file (along with some others) to integrate our PWM driver.
Modify FemtoRV
To integrate this peripheral into the FemtoRV design, we need to make a few changes to the original Verilog code. Start by adding a PWM device bit number to the HardwareConfig_bits.v file:
nano RTL/Devices/HardwareConfig_bits.v
Add the following line just after “localparam IO_FGA_DAT_bit = 11;”
localparam IO_PWM_bit = 12; // W write duty cycle (12 bits)
Save and exit. Modify the top-level femtosoc.v file:
nano RTL/femtosoc.v
Make the following changes to the Verilog code:
...
`include "DEVICES/FGA.v" // Femto Graphic Adapter
`include "DEVICES/HardwareConfig.v" // Constant registers to query hardware con$
`include "DEVICES/pwm.v" // PWM driver for one LED
...
module femtosoc(
`ifdef NRV_IO_LEDS
`ifdef FOMU
output rgb0,rgb1,rgb2,
`else
output D1,D2,D3,D4,D5,
`endif
`endif
`ifdef NRV_IO_PWM
output D1,D2,D3,D4,D5,
`endif
...
/****************************************************************/
/* PWM Peripheral */
`ifdef NRV_IO_PWM
pwm #(
.WIDTH(12)
) pwm (
.clk(clk),
.wstrb(io_wstrb),
.sel(io_word_address[IO_PWM_bit]),
.wdata(io_wdata),
.led({D4, D3, D2, D1})
);
`endif
/****************************************************************/
/* And last but not least, the processor */
...
Save and exit. In the iCEstick hardware configuration file, we need to disable the LEDs so we can have the PWM module control one of the LEDs.
nano RTL/CONFIGS/icestick_config.v
Change the Devices section to look like the following:
`define NRV_IO_BUTTONS // Mapped IO to PMOD connector (78, 79, 80, 81)
//`define NRV_IO_LEDS // Mapped IO, LEDs D1,D2,D3,D4 (D5 is used to di$
//`define NRV_IO_IRDA // In IO_LEDS, support for the IRDA on the IceSt$
`define NRV_IO_UART // Mapped IO, virtual UART (USB)
//`define NRV_IO_SSD1351 // Mapped IO, 128x128x64K OLED screen
//`define NRV_IO_MAX7219 // Mapped IO, 8x8 led matrix
`define NRV_MAPPED_SPI_FLASH // SPI flash mapped in address space. Use with MIN$
`define NRV_IO_PWM // Use PWM peripheral to control LED
Save and exit. Build and upload the new SOC design:
make ICESTICK
Example Software
Note that we did not update FIRMWARE/LIBFEMTORV32/femtorv32.h to include the memory addressing macros. You’re welcome to add those macros, but I’m going to manually address the I/O memory space to talk directly to the PWM hardware.
cd ~/Projects/fpga
mkdir -p femtorv32/pwm_test
cd femtorv32/pwm_test
nano main.c
Here is a simple C program that ramps up the brightness of the LED connected to the PWM peripheral, resets it to 0 (off), and repeats the process.
#include <femtorv32.h>
int main() {
while (1) {
for (int i = 0; i < 4096; i++) {
*(volatile uint32_t*)(0x404000) = i;
delay(1);
}
}
}
Save and exit. Create a Makefile that includes the FemtoRV Makefile template.
nano Makefile
Add the following line:
include ../../learn-fpga/FemtoRV/FIRMWARE/makefile.inc
Save and exit. Build and upload the software:
make main.prog
The first LED on the iCEstick should slowly increase in brightness over time, reset to off, and continue the process. It will take a little over 4 seconds to complete one cycle.
There is no real challenge for this final episode. If you made it this far, feel free to celebrate! You should have many of the basic building blocks to start creating your own FPGA designs and working within larger Verilog projects.
If you’d like an open-ended challenge, try designing your own, custom hardware peripheral for the FemtoRV. Share your project on Twitter and tag us if you make something cool (@DigiKey, @MakerIO, @ShawnHymel, @BrunoLevy01, #FemtoRV).
Recommended Reading
The following content might be helpful if you would like to dig deeper:
基于iCE40UP5K的FPGA学习平台利用PWM制作的音乐播放器
在超低功耗的ICE FPGA开发板上利用PWM波实现了音乐播放的效果,同时WS2812彩灯在不停的闪烁
爆肝儿
中山大学
用iCE40UP5K的FPGA学习平台制作一个数字电压表
用iCE40UP5K的FPGA学习平台板载的FPGA和高速比较器完成ADC的电路。扩展板上的旋转电位计提供0-3.3V的电压,通过ADC进行数字转换并在OLED上进行显示。
MMA
基于ICE40+比较器实现电压表
利用FPGA内部逻辑加上外部高速比较器实现sigma-delta-ADC,将采集的的电压在oled显示器上面显示
zhaolin0912
安徽师范大学
基于iCE40UP5K的FPGA学习平台——利用ADC制作一个数字电压表
基于iCE40UP5K的FPGA学习平台——利用ADC制作一个数字电压表
cjmf
中国计量大学
基于硬禾ICE40UP5K开发板设计的简易DDS信号发生器
本人是一名来自河南的电子信息大二学生,本项目是基于硬禾ICE40UP5K开发板和扩展板设计的波形、幅度、频率可调的DDS信号发生器
这个邓某超逊
河南工业大学
2022寒假在家练 基于iCE40UP5K的FPGA学习平台,利用PWM制作一个音乐播放器(项目2)
基于iCE40UP5k的FPGA学习平台,利用PWM制作一个音乐播放器。总共包含三首曲子,通过含有代码消抖的按键进行切换,同时正在播放的曲目名称会在SPI OLED屏上显示。
Undefined_User
基于iCE40UP5K的数字电压表
在本文中,我们将使用硬禾学堂的“基于iCE40UP5K的FPGA学习平台”开发板来实现一个Σ-Δ ADC采集,并制作一个简易的数字电压表。
raincorn
河南工业大学
基于ICE40UP5K FPGA实现的数字电压表
本次实验基于ICE40UP5K FPGA学习平台,结合板卡上的电压比较器编写的数字电压表,使用了Lattice官方提供的SSD ADC代码和硬禾学堂的OLED驱动程序,来完成本次实验
amirror
南京农业大学
基于iCE40UP5K制作数字电压表
该项目是基于iCE40UP5K的FPGA学习平台制作一个数字电压表。以Σ-ΔADC,可在转换速率要求不高的情况下,以极低的成本实现ADC。完成数字电压表制作。
胡超杰
河南工业大学
基于ICE40UP5K的数字电压表设计
基于2022冬寒假在家练项目,利用所提供的ICE40UP5K FPGA 学习平台,实现了测量旋转电位计电压并显示到OLED屏幕上的功能。
smallcracker
哈尔滨工业大学
基于iCE40UP5K的FPGA学习平台实现数字电压表
使用iCE40UP5K的FPGA学习平台实现一个数字电压表的功能
Andy
西安电子科技大学
基于ICE40UP5K利用PWM制作一个音乐播放器
2022寒假在家练,基于ICE40UP5K的FPGA学习平台,项目二-基于PWM实现一个音乐播放器
梦比优斯
北京理工大学
基于pwm实现简易音乐播放器
通过PWM产生不同的音调,产生3首曲子,并曲子的切换使用扩展板的按键,有按键消抖的功能,曲子的名字再屏幕上显示出来
wyxeth
北京航空航天大学
2022寒假在家一起练-用iCE40 FPGA平台完成数字电压表
使用高速比较器构造ADC,制作一个数字电压表并显示在128*64OLED屏幕上
Huang
华南理工大学
基于iCE40UP5K-FPGA学习平台实现的数字电压表
在iCE40UP5K的FPGA学习平台上利用比较器和PWM波基于sigma delta原理实现了ADC功能,测量板载电位器的电压值,制作的数字电压表,通过OLED实时显示,精度0.1V以内。
karn
中山大学
基于ICE40UP5K的FPGA学习平台设计音乐播放器
使用按键控制切换歌曲,ICE40UP5K核心板产生不同频率PWM信号驱动蜂鸣器播放歌曲,同时OLED显示屏模块显示歌曲名称
ONE_PIECE
西华大学
在iCE40UP5K的FPGA学习平台下利用蜂鸣器制作音乐播放器
我选择的项目为利用基于iCE40UP5K的FPGA学习平台制作一款PWM制作一个音乐播放器,要求有音乐播放、按键防抖切换、OLED屏幕显示音乐汉字名称。
神经娃
兰州大学
2022寒假在家练 - 基于iCE40UP5K的FPGA学习平台制作数字电压表
2022寒假在家练项目 - 基于iCE40UP5K的FPGA学习平台制作数字电压表
Happy An
中国科学院大学
基于ICE40UP5K的FPGA学习平台 制作数字电压表
利用ADC制作一个数字电压表,旋转电位计可以产生0-3.3V的电压,利用板上的串行ADC对电压进行转换,将电压值在板上的OLED屏幕上显示出来。
大风吹
南开大学
基于iCE40UP5K的DDS信号发生器
基于iCE40UP5K制作的DDS信号发生器,可以产生指定频率的方波,锯齿波,三角波,正弦波
Sherlock
山东大学
制作基于ICE40_FPGA_PICO利用PWM驱动蜂鸣器可以播放、切换曲子并显示中文曲名的音乐播放器
使用ICE40_FPGA_PICO开发板,用PWM驱动无源蜂鸣器发出不同的音调,然后对音调的节拍时间进行统计,然后用SPI的方式驱动0.96英寸的oled,把中文曲名和时间显示在上面,可以使用消抖后的按键K1复位,K2进行切歌。
mosu
安徽理工大学
基于iCE40UP5K的FPGA学习平台实现音乐播放器
通过PWM产生不同的音调,并驱动板上蜂鸣器将音调输出;能够播放三首不同的曲子,每个曲子的时间长度为1分钟,可以切换播放;曲子的切换使用小脚丫核心板上的按键,需要有按键消抖的功能;播放的曲子的名字在OLED屏幕上显示出来(汉字显示)
chuirich
兰州大学
基于ICE40UP5K的电压显示器
主要实现项目1 - 利用ADC制作一个数字电压表 旋转电位计可以产生0-3.3V的电压 , 利用板上的串行ADC对电压进行转换 将电压值在板上的OLED屏幕上显示出来
xinshuwei
南昌大学
基于iCE40UP5K的FPGA学习平台-利用PWM制作一个音乐播放器
完成了利用PWM制作一个音乐播放器,实现了通过PWM产生不同的音调,蜂鸣器将音调输出;能播放三首不同的曲子,时间长度为1分钟,可切换播放;使用扩展板的按键,有按键消抖的功能;曲子的名字在OLED屏幕上显示出来功能
千千3
北京理工大学
基于iCE40UP5K实现数字电压表
基于Lattice iCE40UP5K实现一个Σ-Δ ADC采集,采集后的电压将会在OLED屏幕上显示,实现一个简易的电压表
niuniu
河北师范大学
基于ICE40UP5K实现ADC采集及数字电压表
基于ICE40UP5K芯片,通过实现Σ-Δ ADC的方式,进行电压信号的采集并转换为数字信号,最终在OLED上显示实时数据。
Vanlong
重庆邮电大学
基于树莓派Pico的嵌入式系统学习扩展板(无Pico核心模块)
本板卡专为嵌入式系统学习而设计。基于树莓派PICO的嵌入式系统学习平台,可以通过C/C++以及MicroPython编程来学习嵌入式系统的工作原理和应用。
基于iCE40UP5K的FPGA核心板
核心板基于Lattice的ICE40UP5K FPGA,板载LPC11U35下载器,可以通过USB-C接口进行FPGA的配置,支持在ICE40UP5K上对RISC-V软核的移植以及开源的FPGA开发工具链,板上RGB三色LED灯用于简单的调试,总计28个IO用于扩展使用。
团队介绍
-
苏州硬禾信息科技有限公司