FPGA Prototyping by Verilog Examples [美]Ping D.Chu [译]李艳志 孟伟 刘军 机械工业出版社
中文版 ISBN: 9787111536444
分类:可编程逻辑器件-系统设计
配套网站:https://academic.csuohio.edu/chu_p/rtl/fpga_vlog.html
一、门级组合电路
1.标识符,由字母、数字、下划线 (_) 和符号 ($) 组合而成
2.四值系统,0、1、Z、X(X仅用于建模与仿真)
3.数据类型,线网类(表示硬件元件间的物理连线,wire、wand、supply0)、变量类(表示行为模型中的抽象存储,reg、integer、real、time、realtime,后三种仅用于建模仿真)
4.数字表示
[符号][位宽]' [基数][数值] 其中位宽可选,若数值位数少于位宽,则填充 0 或z、x(当数值最高位为z、x时),若未指定,则取决于主机,且至少为32位。此外基数省略时,默认为十进制
b/B 二进制 o/O 八进制 h/H 十六进制 d/D 十进制
5.运算符,~、&、|、^
6.程序结构,I/O端口声明、信号声明、模块体
module eq1
(
[mode] [data-type][port-names],
...
[mode] [data-type][port-names]
)
程序体的描述方法,连续赋值、Always块、模块示例
assign [signal_name] = [expression];
信号声明指定了模块使用的内部信号和参数,如没有声明,则默认为线网类型
[数据类型] [端口名词]
结构描述
[模块名称] [例化名称]
(
.[端口名称] ([信号名称]),
...
.[端口名称] ([信号名称])
)
也可以通过规则列表,即按照原模块的顺序直接列出所有信号名称
用户数据报协议 UDPS,用表的形式描述电路
测试程序 testbench,uut 测试底层电路实例
附:eq1.v —> 1bit 比较器,eq2.v —> 2bit 比较器
二、FPGA 及 EDA 软件概述 (以 Spartan-3 为例)
1.FPGA 中最小单元是逻辑单元 LC,其包含 LUT 查找表和一个 D 触发器,8个逻辑单元通过内部布线结构组合起来,2个 LC 组合为一个 Slice,4个Slice组合成一个可配置逻辑块 CLB
宏单元,包含4种:组合乘法器、块 RAM、数字时钟管理器 DCM、I/O块 IOB
2.开发流程
附:eq1.v,eq2.v,.ucf 配置文件
三、寄存器传输级组合逻辑电路
1.运算符,共24个
算术运算符 + - * / %取模 **求幂;移位运算符 >> <<逻辑右/左移 >>> <<<算术右/左移;
关系运算符 > < >= <=;相等运算符 == !=逻辑相等/不等 === !==全等/全不等;
按位运算符 ~ & | ^;规约运算符 & | ^规约与/或/异或;逻辑运算符 ! && ||逻辑非/与/或;
连接运算符 {}连接 {{}}复制;条件运算符 ?:条件
注意:>>>移入的是最高符号位,其他移入的均为0
& | ^ 操作可以只有一个运算符,即缩减运算符,即对运算符各位按位操作
拼接运算符实现多个信号的拼接,复制运算符 {4{2'b01}} —> 8'b01010101
条件运算符
[signal] = [boolean-exp]? [true-exp] : [false-exp]
运算符优先级:! ~ + - ; ** * / + - ; >> << >>> <<< ; <<=>>= ; ==! ==== !== ; & ^ | ; && || ; ?:
2.组合逻辑电路 always 块
阻塞顺序赋值、if 语句、case 语句
always、initial
always @ ([sensitivity-list])
begin [optional name]
[optional local variable declaration]
[procedural statement]
...
end
顺序赋值语句:阻塞、非阻塞,阻塞赋值立即赋值,非阻塞赋值在 always 块最后赋值
一般对组合逻辑使用阻塞,对时序逻辑使用非阻塞
[variable-name] = [expression]; //阻塞
[variable-name] <= [expression]; //非阻塞
变量,reg、integer、real、time、realtime。integer数据的宽度固定(32位)
3.if 语句
if [bool-expr]
begin
[procedural statement];
[procedural statement];
end
else
begin
[procedural statement];
[procedural statement];
end
也可以使用 else if 构成"级联",并构成优先级布线
4.case 语句
case [case-expr]
[item]:
begin
[procedural statement];
[procedural statement];
...
end
[item]:
begin
[procedural statement];
[procedural statement];
...
end
...
default:
begin
[procedural statement];
[procedural statement];
...
end
endcase
case 语句即为多路选择语句,分支项过多会导致很大的传播延迟
casez、casex 语句,casez 语句中 z 值被 ? 替代,casex 语句中 x z 值被 ? 替代
full case、parallel case。每一种输入组合都被覆盖,即为 full,每种分支的值都互斥,即为 parallel
5.条件控制语句的布线结构:优先路由网络 if-else、多路选择网络 parallel case
也可看为是多个 2 选 1 多路选择器,还是一个 n 选 1 多路选择器
6.通用编码准则
在组合逻辑中,同一变量被多个 always 块赋值,可仿真,不可被综合;always 块中未被赋值的变量将增加一个锁存器,应使得 if case 语句包含所有分支,在每一个分支为每一个输出信号赋值
7.参数和常量
常量可用 localparam 关键字声明
参数可用 parameter 声明,用于将信息传递到一个模块
module [module-name]
#(
parmeter [parameter-name] = [default_value],
...
[parameter_name] = [default_value];
)
(
...// I/O port declaration
);
verilog-1995 中,参数只能在开头后声明,且只能用顺序清单方式或 defparam 语句重新定义,另常数也规定定义为参数
8.实例
LED数码管译码器、"符号-幅值"加法器、桶式移位器、简化的浮点加法器
整数 — "符号-幅值" 格式,最高位为符号,剩余位显示数值
桶式移位器,即逐级对移位数值按位识别,进行任意位数的移位
简化的浮点加法器,共 13 位:1 位符号位 s、4 位指数阈 e、8 位尾数字段 f,即
最大和最小的非零幅值为0.11111111*2^1111和0.10000000*2*0000
浮点数加法器计算步骤:排序、对齐、加减法、规范化,运算过程中的信号名称分别使用后缀 b、s、a、r、n 以分别表示大数、小数、对齐的数、加减法结果、规范化的数
附:eq1_always.v —> 1bit 比较器,prio_encoder.v —> 优先解码器,decoder_2_4.v —> 2:4解码器,
adder_carry.v —> 加法器,hex_to_sseg.v —> LED 数码管译码器,sign_mag_add.v —> "符号-数值" 数加法器,
barrel_shifter.v —> 桶式移位器,fp_adder.v —> 浮点加法器
四、常规时序电路
1.时序电路,输出与输入和内部状态有关
D 触发器和寄存器
D 触发器 DFF,主要参数:Tcq (clk 到 q 端的延迟)、Tsetup (建立时间)、Thold (保持时间)
同步系统包括:状态寄存器、次态逻辑、输出逻辑
最高运行频率、最小时钟周期,可设置期望的运行频率作为综合约束。时序电路时钟的最大频率必须超过所用晶振的频率
时序电路:常规时序电路、FSM、FSMD。使用非阻塞赋值 <=
2.触发器和寄存器的 HDL 代码
D触发器,有三种:不带异步复位、带异步复位 reset、带同步使能 en
寄存器,为由同一时钟和复位信号控制的 D 触发器的集合
寄存器文件,即带有一个输入端口和一个或多个输入端口的寄存器集合,用于快速、临时的存储
3.简单实例
移位寄存器:Free_running 寄存器,在每一时钟周期内右/左移一位;通用移位寄存器
二进制计数器:Free_running 二进制计数器,不断重复二进制序列实现;通用二进制计数器;模 m 计数器,实现从 0 到 m-1 不断循环计数
tick 信号,常用作与其他时序电路的接口的使能信号
4.时序电路的测试平台
时钟生成
always
begin
clk = 1'b1;
#(T/2);
clk = 1'b0;
#(T/2);
end
复位信号
initial
begin
reset = 1'b1;
#(T/2);
reset = 1'b0;
end
@(negedge clk); wait (q == 2);
5.案例
LED 分时复用电路,需保证 1kHz 左右的频率,其可用于十六进制数字的显示,即在输出 sseg 处添加一个译码器
码表,包含有 BCD 码计数器,并以 BCD 码计数,两种方案:case、if
FIFO,有两个控制信号 wr 写、rd 读,FIFO 头部始终可访问,任何时候都可对 FIFO 读。基于循环队列的实现,寄存器被排列为循环队列,并用两个指针访问,写指针指向队列头,读指针指向队列尾。两个状态信号 full empty,都在读写指针相同时发生置位。一种分别的机制为,使用两个触发器跟踪 empty 和 full 状态,初始化时其分别为 1、0,在每个时钟周期根据 wr rd 信号值修改此两个触发器。即刚读完即 empty,刚写完即 full。
附:d_ff.v —> D 触发器,reg.v —> 触发器,shift_reg.v —> 移位寄存器,bit_counter.v —> 二进制计数器,
mod_m_counter.v —> 模 m 计数器,disp_mux.v —> LED 分时复用电路,disp_hex_mux.v —> 十六进制数字 LED 分时复用,
stop_watch.v —> 码表,fifo.v —> FIFO缓冲器
五、有限状态机
1.有限状态机 FSM,用来对具有有限个内部转换状态的系统进行建模,这些内部状态的转换依赖于当前状态和外部输入
Mealy 输出、Moore 输出。如果一个 FSM 的输出只与其当前状态有关,则为 Moore 状态机;如一 FSM 输出和当前状态和外部输入都有关,则为 Mealy 状态机
状态机一般由一个抽象的状态图或 ASM 图说明,两者可随意转换
2.状态机编码设计:首先分类状态寄存器,然后使用组合逻辑对次态逻辑和输出逻辑进行编码。完整的状态机编码:状态寄存器、次态逻辑、Moore 输出逻辑、Mealy 输出逻辑
case,有两种设计方法,即次态逻辑与输出逻辑是否分开
ISE StateCAD,用户可图形化设计状态机
3.实例
上升沿检测器:基于 Moore、基于Mealy。基于 Mealy 设计需更少状态、更快运行速度,但易受毛刺信号影响
去抖电路,有多种去抖方案,这里是基于 FSM 的去抖方案。
注意:次态逻辑中要记得加 default 状态
附:fsm_eg.v —> FSM 举例,edge_detect_moore.v —> Moore 上升沿检测器,
edge_detect_mealy.v —> Mealy 上升沿检测器,edge_detect_gate.v —> 直接用门级实现上升沿检测器,
db_fsm.v —> FSM 实现去抖电路
六、带数据路径的有限状态机
1.带数据路径的有限状态机 FSMD,由一个 FSM 和常规时序电路组合而成。常用来检测外部命令和状态并生成控制信号,以指定常规时序电路的相应操作。FSMD 使用 RT (寄存器传输) 方法学的描述来实现系统
单个 RT 操作,即对于单个目标寄存器的数据处理和传输
ASMD 图,即将 RT 操作纳入到 ASM。图中的 RT 操作由模块内嵌的时钟信号控制,且目标寄存器的值得更像发生在 FSMD 退出当前 ASMD 块时,而非在 ASMD 块内
带寄存器的判决盒,FSMD 模块框图从形式上可分为数据路径和控制路径,数据路径的执行需进行 RT 操作,其包含数据寄存器、功能单元和布局网络
数据路径根据控制信号执行期望的 RT 操作并生成相应的内部状态信号,控制路径即一个有限状态机
2.FSMD 的代码开发
基于 RT 方法学的去抖电路
追溯 HDL 编码方式的方法:数据流组成的白盒描述、数据流组成的黑盒描述
首先分离状态机控制流和关键数据流,再使用独立的代码段分别描述
带有数据路径元件的编码,即数据路径独立出来编写
带有隐含数据路径元件的编码,即在状态机控制路径中嵌入 RT 操作,隐含描述的方法更加遵循 ASMD 图,且更简单。但显示描述可以通过适当修改减少逻辑元件
3.实例
斐波那契数电路
除法电路,通过逐步移位来计算二进制除法。此外由于是二进制,可以直接比大小看是否可以除,可除即相减
二进制向 BCD 码转换电路
周期计数器,测量周期性的输入波形的周期,系统时钟为f,在两上升沿间测得 N 个周期,则其周期为
低频率计数器,测量一个周期性的输入波形的频率,先求周期,再取倒数以获得较好的精确度
附:debounce_explicit.v —> 带明确数据路径元件的去抖电路,debounce.v —> 带隐含数据路径元件的去抖电路,
fib.v —> 斐波那契数电路,div.v —> 除法电路,bin2bcd —> 二进制向 BCD 码转换电路,
period_counter.v —> 周期计数器,low_freq_counter.v —> 低频率计数器
七、Verilog 相关的话题
1.阻塞和非阻塞
[variable-name] = [expression]; //阻塞
[variable-name] <= [expression]; //非阻塞
阻塞赋值立即发生,非阻塞赋值发生在所在 always 块结束时
组合逻辑电路中阻塞赋值语句的顺序很重要,一般情况下,阻塞赋值语句都可用非阻塞赋值语句替代
存储元件中须使用非阻塞赋值,以避免"竞争"
时序电路中使用阻塞和非阻塞赋值,其具体实现方式:触发器和组合逻辑是否在一个 always 块
2.另一种时序电路代码分格
先前代码都将 寄存器和次态逻辑 分开编写,另一种代码分格即将其组合起来
可以使用阻塞赋值获取次态逻辑的中间结果,并使用非阻塞赋值语句将中间结果赋值给寄存器,有时次态逻辑中的状态逻辑可以和次态逻辑结合起来
二进制计数器
FSM,在状态机也可以类似地,将状态寄存器和次态逻辑组合起来
FSMD,切要注意非寄存器输出要从 always 块中分离出来,单独成块
3.使用有符号数据类型
无符号整数和有符号整数,有符号整数通常由二进制补码格式表示
4 bit 二进制码盘,码盘中无符号整数和有符号整数的位置
在位宽扩展时,无符号数即在高位扩展 0,有符号数即扩展其符号位
Verilog-1995 中的有符号数:仅 integer 可以,reg 和 wire 默认为无符号数。同时 integer 位宽固定,使用很不便。可以按照符号数的扩展方式和计算方式手动编码,以实现任意位数的有符号数
Verilog-2001 中的有符号数:reg 和 wire 在声明时可通过 signed 关键字扩展为有符号类型。Verilog 默认仅在当表达式一边全部为有符号数,才会进行有符号数的扩展,否则进行 0 扩展
Verilog 中有两个系统函数:$signed()、$unsigned(),以将括号内的变量强制转换
4.在综合中使用函数
函数的基本语法:
module
...
//函数定义于 module 内
function [result_type] [func_id] ([input_arg]);
begin
[statements];
end
endfunction
...
endmodule
5.仅用于测试仿真的额外结构
always 和 initial 块
//时钟单元
always
begin
clk = 1'b1;
#20;
clk = 1'b0;
#20;
end
//设置初值
initial
begin
[procedural statements]
end
程序语句:阻塞赋值、非阻塞赋值、if 语句、case 语句、循环语句。支持的循环语句:for、while、repeat、forever
//for
for ([initial-assignment];[end-condition];[step-assignment])
begin
[procedural-statements;]
end
//while
while ([end-condition])
begin
[procedural-statements;]
end
//repeat
repeat ([number])
begin
[procedural-statements;]
end
//forever
forever
begin
[procedural-statements;]
end
forever 语句也可用于描述时钟单元
initial begin
clk = 1'b0;
forever
#10 clk = ~ clk;
end
时序控制
# [delay-time] //延时控制
@ ([eventl,[eventl,...); //事件控制
wait ([boolean_expression]) //等待控制
'timescale
事件控制语句中,@ 紧跟敏感列表,posedge、negedge
wait 语句,挂起后面的语句,直到满足条件
timescale 指令,用于控制 Verilog 代码的编译和处理
'timescale [time-unit]/[time-precision] //指定事件和延时单元,指定仿真的分辨率
其时间单位可为:s、ms、us、ns、ps、fs
系统函数和任务
$unsigned $signed //数据类型转换函数
$time $stime $realtime //仿真时间函数
$finish $stop //仿真控制任务:结束并退出、挂起
$display $write $strobe $monitor //显示任务
$display ([format_strinf],[argument],[argument],...);
常用换码:%d 十、%b 二、%o 八、%h 十六、%c 字符、%s 字符串、%g 实数
$write 类似 $display,但不会再字符的末尾添加换行符
$strobe 类似 $display,但在当前时间片的末尾执行,而非立即执行
$monitor 在每次参数变化时显示,而另外三个仅显示一次
$fopen $fclose 文件访问函数
[mcd-name] = $fopen("[file-name]"); //返回一个与文件相关的 32 位多通道描述符,最低位为输出保留位,其他各位各代表一个文件是否打开
文件打开后,即可写入数据: $fdisplay $fwrite $fstrobe $fmonitor
$fclose ([mcd-name],[format_string],...);
获取外部文件数据:$readmenb readmenh
$readmemb/h ("[file_name]",[,em_variable]);
自定义函数和任务,任务可以后输入、输出、双向参数、还可以包含时序控制结构。而函数中不能包含有时序结构
task [task_id] ([arg]); //其中 arg 默认数据类型是 reg,且 wire 不能被使用
begin
[statements];
end
endtask
复杂测试平台示例:二进制计数器的测试模块,bin_gen 模块生成测试向量、monitor 模块监视输入激励和输出响应
附:and.v —> 与门,eq1.v —> 1bit 比较器,ab_ff.v —> 与门和 D 触发器的实现,bin_counter.v —> 二进制计数器,
fsm_eg.v —> FSM 实例,div.v —> FSMD 除法实例,mod_m_counter_fc.v —> 使用函数的模 m 计数器,
eq2_file_tb.v —> 测试,eq2_task.v —> 任务实现 2bit 比较器,eq2_function.v —> 函数实现 2bit 比较器,
bin_gen.v —> 测试向量生成器,bin_monitor.v —> 监视器,bin_counter_tb3.v —> 顶层模块
八、UART
1.UART,即通用异步收发传输器,是一种用串行通信方式来传输并行数据的电路。其常与 RS-232 标准同时使用,RS-232 标准规定的电平和 FPGA 的 I/O 电压不相同,因此需要在串口和 I/O 口间使用电平转换芯片
UART 包括一个发送器和一个接收器。数据为:1 位起始位(为0)、6-8 位数据位、可选的奇偶校验位、1-2 位停止位(为1)
在串口线上没有时钟信息,在传输前,需将波特率、数据位、停止位的数量和奇偶校验位等参数提前设置好
2.UART 接收子系统:波特率发生器、UART 接收器、接口电路
过采样步骤,过采样可以确保没有时钟的 UART 传输数据的可靠性,但也限制串行传输的波特率只能是系统时钟的一个很小的分频,以至于其不能用于高数据传输速率
波特率产生器,用来产生一个采样信号,其频率是 UART 波特率的 16 倍,对 UART 接收端来说,采样信号仅作为一个使能信号来使用,而非作为一个时钟来使用,即仅通过其计算每经过几个系统时钟周期产生一个采样点
UART 接收端
接口电路,通常有三种方案:单指示触发器、单指示触发器与单字缓冲器、一个 FIFO 缓冲器
单指示触发器的方案,容易导致数据被覆盖的问题。由此增添一个单字缓冲器,即不会覆盖前一个数据。另一种方案即直接使用FIFO 缓冲器缓存所有未读数据
3.UART 发送子系统:UART 发送器、波特率发生器、接口电路。UART 发送器本质上是一个按照特定速率把数据位一位一位输出的移位寄存器。发送端和接受端公用一个波特率发生器
4.UART 总系统简述
附:uart_rx.v —> UART 接收端,flag_buf.v —> 指示触发器和缓冲器的接口,uart_tx —> UART 发送器,
uart.v —> UART 顶层描述,uart_test.v —> UART 验证电路
九、PS2 键盘 & 十、PS2 鼠标
跳过
十一、外部 SRAM
十二、Xilinx Spartan-3 特殊存储器
十三、VGA 控制器 I :图形 & 十四、VGA 控制器 II:示例
略