FPGA介绍
FPGA是现场可编程门阵列的简称,FPGA的应用领域最初为通信领域,但目前,随着信息产业和微电子技术的发展,可编程逻辑嵌入式系统设计技术已经成为信息产业最热门的技术之一,应用范围遍及航空航天、医疗、通讯、网络通讯、安防、广播、汽车电子、工业、消费类市场、测量测试等多个热门领域。并随着工艺的进步和技术的发展,向更多、更广泛的应用领域扩展。越来越多的设计也开始以ASIC转向FPGA, FPGA正以各种电子产品的形式进入了我们日常生活的各个角落。
FPGA人才需求
中国每年对于FPGA设计人才的需求缺口巨大,FPGA设计人才的薪水也是行业内最高的。目前,美国已有FPGA人才40多万,中国台湾地区也有7万多,而中国内地仅有1万左右,可见中国渴望有更多的FPGA人才涌现出来。
如何学习FPGA?
FPGA对我们如此重要,那么对于初学者来说,到底该如何学习FPGA呢?学习一门技术最好有合适的指导答燃枯老师,这样对掌握FPGA技术更容易,可惜的是大部分的学校还未开设相关的课程,也缺少具有实践经验的老师,那么如何才能找到一种捷径帮助初学者快速学会如此具有竞争力的技术呢?
(1)掌握FPGA的编程语言
在学习一门技术之前我们往往从它的编程语言开始,如同学习单片机一样,我们从C语言开始入门,当掌握了C语言之后,开发单片机应用程序也就不是什么难事了。学习FPGA也是如此,FPGA的编程语言有两种:VHDL和Verilog,这两种语言都适合用于FPGA的编程,VHDL是由美国军方组织开发的,在1987年就成为了IEEE的标准;而Verilog则是由一家民间企业的私有财产转移过来的,由于其优越性特别突出,于是在1995年也成为了IEEE标准。VHDL在欧洲的应用较为广泛,而Verilog在中国、美国、日本、台湾等地应用较为广泛,作者比较推崇是Verilog,因为它非常易于学习,很类似于C语言,如果具有C语言基础的人,只需要花很少的时间便能迅速掌握Verilog,而VHDL则较为抽象,学习的时间较长。
作为在校大学生,学习Verilog的最好时期是在大学二年级开设《电子技术基础(数字部分)》时同步学习,不仅能够理解数字电路实现的方式,更能通过FPGA将数字电路得以实现。大清洞三、大四的学生还可以进一步强化学习Verilog,建议以北京航天航空大学出版社出版的由夏宇闻教授编写的《Verilog数字系统设计教程(第二版)》作为蓝本,本书比较全面地、详细地介绍了Verilog的基本语法。如果是其他初学者,可以直接借助《Verilog数字系统设计教程(第二版)》和本书即能全面掌握Verilog的语法,这是学习FPGA的第一步,也是必不可少的一步。
(2)FPGA实验尤为重要
除了学习编程语言以外,更重要的是实践,将自己设计的程序能够在真正的FPGA里运行起来,这时我们需要选一块板子进行实验,一般的红色飓风的板子基本上可以满足大家的需求,大家感兴趣的不妨段困买一块做做实验。
(3)FPGA培训不可忽视
在有条件的情况下,参加FPGA的培训可以在短时间内大幅提升自己的水平,因为有老师带着可以省去了很多弯路。笔者在网上发现国内第一家大学EDA实验室创始人之一的夏宇闻教授和未名芯锐搞了一个FPGA培训班,感兴趣的朋友可以去看看,网上也有很多的视频资源,也可下下来看看.
我想只要大家想学FPGA,想从事FPGA工作,总会有办法找到适合自己的方法.
(1)先做一个消抖,存到圆贺文件debounce.vhdlibrary IEEE
use IEEE.std_logic_1164.all
use IEEE.std_logic_arith.all
use IEEE.std_logic_unsigned.all
entity debounce is
generic (
CLK_FREQ_MHz : integer := 20--in MHz
BUTTON_PRESS_STATUS : std_logic := '0'
)
port (
reset_n : in std_logic
clk : in std_logic
btnIn : in std_logic
btnPressed: out std_logic
)
end debounce
architecture debounce_arch of debounce is
constant MAX_MS_CNT : integer := CLK_FREQ_MHz * 1000 - 1
signal msCnt: integer range 0 to MAX_MS_CNT
signal msClk: std_logic --做一个毫秒脉冲,每1毫秒对按钮采样一次
signal btnIn_q: std_logic_vector(9 downto 0) --记住最后10次采样
signal btn : std_logic
signal btn_q: std_logic
begin
--产生毫秒脉冲
process(reset_n, clk)
begin
if reset_n = '0' then
msCnt <= 0
msClk <= '0'
elsif rising_edge(clk) then
if msCnt >= MAX_MS_CNT then
msCnt <= 0
msClk <= '1'
else
msCnt <= msCnt + 1
msClk <= '0'
end if
end if
end process
--记住最后10次采样
process(reset_n, clk)
begin
if reset_n = '0' then
btnIn_q <= (others =>not BUTTON_PRESS_STATUS)
elsif rising_edge(clk) then
if msClk = '1' then
btnIn_q <= btnIn_q(btnIn_q'left-1 downto 0) &btnIn
else
btnIn_q <= btnIn_q
end if
end if
end process
process(reset_n, clk)
variable all_samples_are_pressed : std_logic_vector(btnIn_q'left downto 0) := (others =>BUTTON_PRESS_STATUS)
begin
if reset_n = '0' then
btn <= '0'
btn_q <= '0'
elsif rising_edge(clk) then
if btnIn_q = all_samples_are_pressed then
btn <= '1' --最后10次采样都是按下状态,就确认按钮按下(10ms消抖)
elsif btnIn_q = not all_samples_are_pressed then
btn <橘唯派= '0' --最后10次采样都是抬起状态,就确认按钮抬起(10ms消抖)
else
btn <= btn --否则保持不变
end if
btn_q <山行= btn
end if
end process
btnPressed <= '1' when btn = '1' and btn_q = '0' else '0' --按钮按下上升沿检测
end debounce_arch
(2)做一个加法器,存到文件adder.vhd
library IEEE
use IEEE.std_logic_1164.all
use IEEE.std_logic_arith.all
use IEEE.std_logic_unsigned.all
entity adder is
port (
reset_n : in std_logic
clk : in std_logic
adderEn : in std_logic
data : out std_logic_vector(3 downto 0)
dataValid: out std_logic
)
end adder
architecture adder_arch of adder is
signal cnt : std_logic_vector(3 downto 0)
begin
process(reset_n, clk)
begin
if reset_n = '0' then
cnt <= x"0"
dataValid <= '0'
elsif rising_edge(clk) then
if adderEn = '1' then--将被替换成,按钮按下时,计数+1
if cnt >= x"9" then
cnt <= x"0"
else
cnt <= cnt + 1
end if
dataValid <= '1'
else
cnt <= cnt
dataValid <= '0'
end if
end if
end process
data <= cnt
end adder_arch
(3)做7段数码管显示,存到文件SevenSegment.vhd
library IEEE
use IEEE.std_logic_1164.all
use IEEE.std_logic_arith.all
use IEEE.std_logic_unsigned.all
entity SevenSegment is
generic (
LED_ON : std_logic := '0'
)
port (
reset_n : in std_logic
clk : in std_logic
data : in std_logic_vector(3 downto 0)
dataValid: in std_logic
ledOut : out std_logic_vector(6 downto 0)
)
end SevenSegment
architecture SevenSegment_arch of SevenSegment is
constant LED_OFF : std_logic := not LED_ON
signal led : std_logic_vector(6 downto 0)
begin
-- --a--
-- |f|b
-- --g--
-- |e|c
-- --d--
process(reset_n, clk)
begin
if reset_n = '0' then
led <= LED_ON &LED_ON &LED_ON &LED_ON &LED_ON &LED_ON &LED_OFF--display 0
elsif rising_edge(clk) then
if dataValid = '1' then
case data is --a b c d e f g
when x"0" =>
led <= LED_ON &LED_ON &LED_ON &LED_ON &LED_ON &LED_ON &LED_OFF--display 0
when x"1" =>
led <= LED_OFF &LED_ON &LED_ON &LED_OFF &LED_OFF &LED_OFF &LED_OFF--display 1
when x"2" =>
led <= LED_ON &LED_ON &LED_OFF &LED_ON &LED_ON &LED_OFF &LED_ON --display 2
when x"3" =>
led <= LED_ON &LED_ON &LED_ON &LED_ON &LED_OFF &LED_OFF &LED_ON --display 3
when x"4" =>
led <= LED_OFF &LED_ON &LED_ON &LED_OFF &LED_OFF &LED_ON &LED_ON --display 4
when x"5" =>
led <= LED_ON &LED_OFF &LED_ON &LED_ON &LED_OFF &LED_ON &LED_ON --display 5
when x"6" =>
led <= LED_ON &LED_OFF &LED_ON &LED_ON &LED_ON &LED_ON &LED_ON --display 6
when x"7" =>
led <= LED_ON &LED_ON &LED_ON &LED_OFF &LED_OFF &LED_OFF &LED_OFF--display 7
when x"8" =>
led <= LED_ON &LED_ON &LED_ON &LED_ON &LED_ON &LED_ON &LED_ON --display 8
when x"9" =>
led <= LED_ON &LED_ON &LED_ON &LED_ON &LED_OFF &LED_ON &LED_ON --display 9
when others =>
led <= (others =>LED_OFF)
end case
else
led <= led
end if
end if
end process
ledOut <= led
end SevenSegment_arch
(4)最后,综合到一起,存到文件top.vhd
library IEEE
use IEEE.std_logic_1164.all
use IEEE.std_logic_arith.all
use IEEE.std_logic_unsigned.all
entity top is
generic (
CLK_FREQ_MHz : integer := 20--可以修改成你的系统时钟频率,以MHz为单位
BUTTON_PRESS_STATUS : std_logic := '0' --指定按钮按下时,是逻辑0还是1
LED_ON : std_logic := '0'--指定数码管点亮需要输出0还是1
)
port (
reset_n : in std_logic
clk : in std_logic
btnIn : in std_logic
ledOut : out std_logic_vector(6 downto 0)
)
end top
architecture top_arch of top is
component debounce
generic (
CLK_FREQ_MHz : integer := 20--in MHz
BUTTON_PRESS_STATUS : std_logic := '0'
)
port (
reset_n : in std_logic
clk : in std_logic
btnIn : in std_logic
btnPressed: out std_logic
)
end component
component adder
port (
reset_n : in std_logic
clk : in std_logic
adderEn : in std_logic
data : out std_logic_vector(3 downto 0)
dataValid: out std_logic
)
end component
component SevenSegment
generic (
LED_ON : std_logic := '0'
)
port (
reset_n : in std_logic
clk : in std_logic
data : in std_logic_vector(3 downto 0)
dataValid: in std_logic
ledOut : out std_logic_vector(6 downto 0)
)
end component
signal btnPressed : std_logic
signal data : std_logic_vector(3 downto 0)
signal dataValid : std_logic
begin
debounce_inst : debounce
generic map (
CLK_FREQ_MHz =>CLK_FREQ_MHz, --in MHz
BUTTON_PRESS_STATUS =>BUTTON_PRESS_STATUS
)
port map(
reset_n =>reset_n,
clk =>clk,
btnIn =>btnIn,
btnPressed=>btnPressed
)
addr_inst : adder
port map (
reset_n =>reset_n,
clk =>clk,
adderEn =>btnPressed,
data =>data,
dataValid=>dataValid
)
SevenSegment_inst : SevenSegment
generic map (
LED_ON =>LED_ON
)
port map (
reset_n =>reset_n,
clk =>clk,
data =>data,
dataValid=>dataValid,
ledOut =>ledOut
)
end top_arch
(5)你只要修改top.vhd里generic的定义,设定时钟频率、按钮按下状态和数码管点亮状态即可
1.激励的设置相应于被测试模块的输入激励设置为reg型,输出相应设置为wire类型,双向端口inout在测试中需要进行处理。
方法1:为双向端口设置中间变量inout_reg作为该inout的输出寄存,inout口在testbench中要定义为wire型变量,然后用输出使能控制传输方向。
eg:
inout [0:0] bi_dir_port
wire [0:0] bi_dir_port
reg [0:0] bi_dir_port_reg
reg bi_dir_port_oe
assign bi_dir_port=bi_dir_port_oe?bi_dir_port_reg:1'bz
用bi_dir_port_oe控制端口数据方向,并利用中间变量寄存器改变其值。等于两个模块之间用inout双向口互连。往端口写(就是往模块里面输入)
方法2:使用force和release语句,这种方法不能准确反映双向端口的信号变化,但这种方法可以反映块内信号的变化。具体如示:
module test()
wire data_inout
reg data_reg
reg link
#xx//延时
force data_inout=1'bx//强制作为输入端口嫌指
...............
#xx
release data_inout//释放纤者枯输入端口
endmodule
从文本文件中读取和写入向量
1)读取文本文件:用 $readmemb系统任务从文本文件中读取二进制向量(可以包含输入激励和输出期望值)。$readmemh 用于读取十六进制文件。例如:
reg [7:0] mem[1:256] // a 8-bit, 256-word 定义存储器mem
initial $readmemh ( "mem.data", mem ) // 将.dat文件读入寄存器mem中
initial $readmemh ( "mem.data", mem, 128, 1 ) // 参数为寄存器加载数据的地址始终
2)输出文本文件:打开输出文件用?$fopen 例如:
integer out_file// out_file 是一个文件描述,需要定义为 integer类型
out_file = $fopen ( " cpu.data " )// cpu.data 是需要打开的文件,也就是最终的输出文本
设计中的信号值可以通过$fmonitor, $fdisplay,
2. Verilog和Ncverilog命令使用库文件或库目录
ex). ncverilog -f run.f -v lib/lib.v -y lib2 +libext+.v //一般编译文件在run.f中, 库文件在lib.v中,lib2目录中的.v文件系统自动搜索
使用库文件或库目录,只编译需要的模块而不必全部编译
3.Verilog Testbench信号记录的系统任务:
1). SHM数据库可以记录在设计仿真过程中信号的变化. 它只在probes有效的时间内记录你set probe on的信号的变化.
ex). $shm_open("waves.shm")//打开波形数据库
$shm_probe(top, "AS")/毁洞/ set probe on "top",
第二个参数: A -- signals of the specific scrope
S -- Ports of the specified scope and below, excluding library cells
C -- Ports of the specified scope and below, including library cells
AS -- Signals of the specified scope and below, excluding library cells
AC -- Signals of the specified scope and below, including library cells
还有一个 M ,表示当前scope的memories, 可以跟上面的结合使用, "AM" "AMS" "AMC"
什么都不加表示当前scope的ports
$shm_close //关闭数据库
2). VCD数据库也可以记录在设计仿真过程中信号的变化. 它只记录你选择的信号的变化.
ex). $dumpfile("filename")//打开数据库
$dumpvars(1, top.u1)//scope = top.u1, depth = 1
第一个参数表示深度, 为0时记录所有深度第二个参数表示scope,省略时表当前的scope.
$dumpvars//depth = all scope = all
$dumpvars(0)//depth = all scope = current
$dumpvars(1, top.u1)//depth = 1 scope = top.u1
$dumpoff //暂停记录数据改变,信号变化不写入库文件中
$dumpon //重新恢复记录
3). Debussy fsdb数据库也可以记录信号的变化,它的优势是可以跟debussy结合,方便调试.
如果要在ncverilog仿真时,记录信号, 首先要设置debussy:
a. setenv LD_LIBRARY_PATH :$LD_LIBRARY_PATH
(path for debpli.so file (/share/PLI/nc_xl//nc_loadpli1))
b. while invoking ncverilog use the +ncloadpli1 option.
ncverilog -f run.f +debug +ncloadpli1=debpli:deb_PLIPtr
fsdb数据库文件的记录方法,是使用$fsdbDumpfile和$fsdbDumpvars系统函数,使用方法参见VCD
注意: 在用ncverilog的时候,为了正确地记录波形,要使用参数: "+access+rw", 否则没有读写权限
在记录信号或者波形时需要指出被记录信号的路径,如:tb.module.u1.clk.
………………………………………………………………………………………………………
关于信号记录的系统任务的说明:
在testbench中使用信号记录的系统任务,就可以将自己需要的部分的结果以及波形文件记录下来(可采用sigalscan工具查看),适用于对较大的系统进行仿真,速度快,优于全局仿真。使用简单,在testbench中添加:initial begin
$shm_open("waves.shm")
$shm_probe("要记录信号的路径“,”AS“);
#10000
$shm_close即可。
4. ncverilog编译的顺序: ncverilog file1 file2 ....
有时候这些文件存在依存关系,如在file2中要用到在file1中定义的变量,这时候就要注意其编译的顺序是
从后到前,就先编译file2然后才是file2.
5. 信号的强制赋值force
首先, force语句只能在过程语句中出现,即要在initial 或者 always 中间. 去除force 用 release 语句.
initial begin force sig1 = 1'b1... release sig1end
force可以对wire赋值,这时整个net都被赋值也可以对reg赋值.
6.加载测试向量时,避免在时钟的上下沿变化
为了模拟真实器件的行为,加载测试向量时,避免在时钟的上下沿变化,而是在时钟的上升沿延时一个时间单位后,加载的测试向量发生变化。如:
assign #5 c=a^b
……
@(posedge clk) #(0.1*`cycle) A=1
******************************************************************************
//testbench的波形输出
module top
...
initial
begin
$dumpfile("./top.vcd")//存储波形的文件名和路径,一般是.vcd格式.
$dumpvars(1,top)//存储top这一层的所有信号数据
$dumpvars(2,top.u1)//存储top.u1之下两层的所有数据信号(包含top.u1这一层)
$dumpvars(3,top.u2)//存储top.u2之下三层的所有数据信号(包含top.u2这一层)
$dumpvars(0,top.u3)//存储top.u3之下所有层的所有数据信号
end
endmodule
//产生随机数,seed是种子
$random(seed)
ex: din <= $random(20)
//仿真时间,为unsigned型的64位数据
$time
ex:
...
time condition_happen_time
...
condition_happen_time = $time
...
$monitor($time,"data output = %d", dout)
...
//参数
parameter para1 = 10,
para2 = 20,
para3 = 30
//显示任务
$display()
//监视任务
$monitor()
//延迟模型
specify
...
//describ pin-to-pin delay
endspecify
ex:
module nand_or(Y,A,B,C)
input A,B,C
output Y
AND2 #0.2 (N,A,B)
OR2 #0.1 (Y,C,N)
specify
(A*->Y) = 0.2
(B*->Y) = 0.3
(C*->Y) = 0.1
endspecify
endmodule
//时间刻度
`timescale 单位时间/时间精确度
//文件I/O
1.打开文件
integer file_id
file_id = fopen("file_path/file_name")
2.写入文件
//$fmonitor只要有变化就一直记录
$fmonitor(file_id, "%format_char", parameter)
eg:$fmonitor(file_id, "%m: %t in1=%d o1=%h", $time, in1, o1)
//$fwrite需要触发条件才记录
$fwrite(file_id, "%format_char", parameter)
//$fdisplay需要触发条件才记录
$fdisplay(file_id, "%format_char", parameter)
$fstrobe()
3.读取文件
integer file_id
file_id = $fread("file_path/file_name", "r")
4.关闭文件
$fclose(fjile_id)
5.由文件设定存储器初值
$readmemh("file_name", memory_name")//初始化数据为十六进制
$readmemb("file_name", memory_name")//初始化数据为二进制
//仿真控制
$finish(parameter)//parameter = 0,1,2
$stop(parameter)
//读入SDF文件
$sdf_annotate("sdf_file_name", module_instance, "scale_factors")
//module_instance: sdf文件所对应的instance名.
//scale_factors:针对timming delay中的最小延时min,典型延迟typ,最大延时max调整延迟参数
//generate语句,在Verilog-2001中定义.用于表达重复性动作
//必须事先声明genvar类型变量作为generate循环的指标
eg:
genvar i
generate for(i = 0i <4i = i + 1)
begin
assign = din[i] = i % 2
end
endgenerate
//资源共享
always @(A or B or C or D)
sum = sel ? (A+B):(C+D)
//上面例子使用两个加法器和一个MUX,面积大
//下面例子使用一个加法器和两个MUX,面积小
always @(A or B or C or D)
begin
tmp1 = sel ? A:C
tmp2 = sel ? B:D
end
always @(tmp1 or tmp2)
sum = tmp1 + tmp2
******************************************************************************
模板:
module testbench//定义一个没有输入输出的module
reg …… //将DUT的输入定义为reg类型
……
wire…… //将DUT的输出定义为wire类型
……
//在这里例化DUT
initial
begin
…… //在这里添加激励(可以有多个这样的结构)
end
always…… //通常在这里定义时钟信号
initial
//在这里添加比较语句(可选)
end
initial
//在这里添加输出语句(在屏幕上显示仿真结果)
end
endmodule
一下介绍一些书写Testbench的技巧:
1.如果激励中有一些重复的项目,可以考虑将这些语句编写成一个task,这样会给书写和仿真带来很大方便。例如,一个存储器的testbench的激励可以包含write,read等task。
2.如果DUT中包含双向信号(inout),在编写testbench时要注意。需要一个reg变量来表示其输入,还需要一个wire变量表示其输出
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)