硬件描述语言¶
Vivado 相关¶
为 IP 核创建自定义参数¶
可以通过模块名后的 #(parameter )
实现:
module myHeartbeat #(parameter nbits = 25)(
input clk,
output reg heartbeat = 0
);
reg [nbits-1:0]divider = 0;
always @(posedge clk) begin
if (divider == 0) begin
heartbeat <= !heartbeat;
end
divider <= divider+1;
end
endmodule
Verilog¶
推荐资源
- 书籍:
- Verilog by Example: A Concise Introduction for FPGA Design
- Verilog 数字系统设计
- 练习:
- HDL Bits
运算符¶
- 按位运算符 6 个:
~ | ^ & ~^ ~&
~^
(或者^~
都可以)是异或非,即 XNOR,也称为同或- 长度不一时,右端对齐,补零
- 都可以归约,接受一个向量操作数
- 逻辑运算符
&& || !
- 例子:如果
a
b
各是两个向量,要把它们的每一位或起来,用a || b
即可。
- 例子:如果
- 赋值运算符
= <=
- 拼接
{}
- 需要知道每一个数的大小,
{1,2,3}
是没有大小的常数 - 赋值的左右均可
- 需要知道每一个数的大小,
- 重复:
{num{a,b,c}}
- 注意,两层大括号包裹的整体代表重复,嵌套时不要忽略外面的大括号,写法如
{3'd5, {2{3'd6}}}
- 注意,两层大括号包裹的整体代表重复,嵌套时不要忽略外面的大括号,写法如
- 位移:
<< >>
- 算术位移:
<<< >>>
- 条件:
condition ? value_if_true : value_if_false
数据类型¶
wire
- 也被称为 signal,
- 有方向:source(driver) → sink
assign
连续赋值- 不能有超过一个 driver,无 driver 值未定义
- 向量
- 声明时维度在名字前面,选择时维度在名字后面
- 声明在名字后面时也称为 unpacked array,一般用于内存数组
-
赋值大小不一致时,零扩展或截断¶
隐式网表
assign
和模块端口会隐式生成未定义的网表,它们都是 1 位的。
添加指令 `default_nettype none
可以禁用隐式生成,防止你漏掉 wire
。
状态机¶
例子
module state_machine (
input clk,
input reset,
input go,
input kill,
output done
);
reg [6:0] count;
reg done;
reg [1:0] state_reg;
// states
parameter idle = 2'b00;
parameter active = 2'b01;
parameter finish = 2'b10;
parameter abort = 2'b11;
// machine
always @ ( posedge clk or posedge reset )
begin
if ( reset )
begin
state_reg <= idle;
count <= 7'h00;
done <= 1'b0;
end
else
case ( state_reg )
idle:
begin
count <= 7'h00;
done <= 1'b0;
if ( go )
state_reg <= active;
end
active:
begin
count <= count + 1;
done <= 1'b0;
if ( kill )
state_reg <= abort;
else if ( count == 7'd100 )
state_reg <= finish;
end
//...
default:
begin
count <= 7'h00;
done <= 1'b0;
state_reg <= idle;
end
endcase
end
endmodule
一个状态机的大致写法如下:
- 状态寄存器、计数器等的寄存器声明
- 状态码分配
parameter
- 二进制码、格雷码(功耗较低)、独热码(更节省组合逻辑,增加速度和可靠性)
case
状态机- 每个状态自己做的事情
- 每个状态到其他状态的转移
这是用一个 always
块描述整个状态机的方法。课上讲解了三段式状态机:
- 双寄存器,当前状态和下个状态
- 第一段:
always
块,负责移动到下个状态- 非阻塞赋值
- 第二段:
always
块,负责根据当前状态进行操作,并决定下个状态- 阻塞赋值
- 第三段:
assign
赋值语句,定义输出
三段式状态机其实就是把状态迁移的时序独立出来了。它还可以在组合逻辑后再加一级寄存器,滤去组合逻辑的毛刺。
下面的部分不完善
算术运算¶
或许你会因为 Verilog 中数据运算究竟是考虑无符号数还是补码而感到困惑。这里是一些值得注意的地方:
- 首先,和 C 语言一样,数据具体的位模式是不重要的,重要的是解读这个位模式的方式。
- 从 Verilog-1995 以来,
integer
类型时有符号的,reg
、net
都是无符号的。 - Verilog 的加法和乘法操作会先对操作数扩展成相同的位宽
- I/O、总线。
reg
型需要结构化过程语句如always
进行。- 所有可综合的寄存器都应当使用非阻塞赋值
<=
。
下面是一个带复位的 D-flop:
FPGA 开发中好的习惯
- 总是使用一个全局的异步复位信号。
- 使用
parameter
为状态命名。 - 使用
case
语句,必须要有default
分支(综合程序的要求)。
- 模块化设计:IP 核、原语核。
模块实例化时允许空置的输出
总是使用指定名称的端口连接
- 内存:
控制器,FPGA 不能实现。
- FPGA 可实现的有:SRAM、FIFO、LIFO、DP 等。
- 可以由综合程序推断、厂商原语、厂商的专用工具生成。
- 深度和宽度。
- 通常利用片内存储 RAM 块,我们需要写控制逻辑。
reg [7:0] mem [0:255];
表示 256 个 8 位的寄存器。- 简单双口内存、全双口内存、单口内存的实现。
- 读写时序。当读写地址相同时,读写操作哪个需要设计。
- 三态门实现双向总线。
- 测试模块:
initial
、integer
、#
、wait()
一般只用于测试模块。
module tb_sim_sample_1();
integer i;
parameter CLK_PERIOD = 10;
initial sim_clk= 1'b0;
always #(CLK_PERIOD/2)
sim_clk = ~sim_clk;
initial
begin
random_num = $random(1)
wait(reset);
wait(~reset);
@(posedge sim_clk);
for(j = 0; j < 20; j = j + 1)
begin
@(posedge sim_clk);
end
forever
begin
@(posedge sim_clk);
enable = 1'b0;
end
end
endmodule
always
块实现组合逻辑,在其中使用if-else
和case
语句。此时使用=
阻塞赋值,因为组合逻辑不关心执行顺序。