simplify old posts' tags and categories

some blog contents are alse modified
This commit is contained in:
2023-04-13 00:32:29 +08:00
parent 95d3112612
commit c5f8b0a74c
19 changed files with 402 additions and 310 deletions
+155 -156
View File
@@ -21,7 +21,6 @@ tags:
- 异步复位
- 条件语句
categories:
- [Verilog]
- [Josh 的学习笔记, Verilog]
abbrlink: ecb88422
date: 2020-12-02 01:05:32
@@ -54,15 +53,15 @@ RTL 级语言的最重要特性就是:{% label primary @RTL 级描述是可综
典型的 RTL 设计包含以下3个部分:
1. 时钟域描述
描述设计所使用的所有时钟,时钟之间的主从与派生关系,时钟域之间的转换。
2. 时序逻辑描述(寄存器描述)
根据时钟沿的变换,描述寄存器之间的数据传输方式。
3. 组合逻辑描述
描述电平敏感信号的逻辑组合方式与逻辑功能。
RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了设计的性能,如何调整时序逻辑、组合逻辑的连接关系和拓扑结构以达到最佳的性能就是后面讨论的编码风格(Coding Style)的核心。
@@ -70,11 +69,11 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
工程师们设计 RTL 级代码的顺序各种各样,推荐使用如下的设计步骤:
1. **功能定义与模块划分**
根据系统功能的定义和模块划分准则(详见Part 5 第 4 节)划分各个功能模块。
2. **定义所有模炔的接口**
首先清晰定义每个模块的接口,完成每个模块的信号列表。这种思路也和 Modular Design(模块化设计方法)一致,利于模块重用、调试、修改。
3. **设计的时钟域**
@@ -88,7 +87,7 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
关键路径是指设计中时序要求最难以满足的路径。设计的时序要求主要体现在频率、建立时间、保持时间等时序指标上。在设计初期,设计者可以根据系统的频率要求,粗略地分析出设计的时序难点(如最高频率的路径,计数器的最低位,包含复杂组合逻辑的时序路径等),通过一些时序优化手段(如 Pipeline、Retiming、逻辑复制等,这些时序优化手段在 Part 5 第 7 节有详细介绍)**从代码上缓解设计的时序压力**,这种方法比单依靠综合与布局布线工具的自动优化有效得多。
5. **顶层设计**
常用的设计方法有两种,一种是自顶而下的设计方法,即先描述设计的顶层,然后描述设计的每个子模块;另一种是由底向上的设计方法,即首先描述设计的子模块,最后定义设计的顶层。RTL 设计推荐使用自顶而下的设计方法,因为这种设计方法与模块规划的顺序一致,而且更利于进行 Modular Design(模块化设计方法),并行开展设计工作,提高模块重用率。
6. **FSM 设计**
@@ -100,7 +99,7 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
首先根据时钟域规划好寄存器组,然后描述各个寄存器组之间的数据传输方式。
8. **组合逻辑设计**
一般来说,**大段的组合逻辑最好与时序逻辑分开描述**,这样更利于时序约束和时序分析,使综合器和布局布线器达到更好的优化效果。
# 3. 常用 RTL 级建模
@@ -110,66 +109,66 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
有很多文章论述阻塞赋值和非阻塞赋值的概念,[《Part 3 —— 描述方式和设计层次》的 3.2 小节](https://josh-gao.top/posts/fd117896.html#3-2-%E8%BF%87%E7%A8%8B%E8%B5%8B%E5%80%BC%E8%AF%AD%E5%8F%A5)详细讨论了因不当使用阻塞赋值和非阻塞赋值引起的仿真与设计意图不一致。为了避免不当使用阻塞賦值和非阻塞赋值而造成的种种歧义和错误,这里推荐:
1. **对于时序逻辑,即 `always` 模块的敏感表为{% label primary @沿敏感信号 %}(多为时钟或复位的正沿或负沿),统一使用非阻塞賦值 `<=`**。例如:
```verilog // cnt1.v
/******************************
时序逻辑中使用非阻塞赋值。
******************************/
module cnt1 (clock, cnt_out);
input clock;
output [3:0] cnt_out;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [3:0] cnt_out;
always @(posedge clock)
cnt_out <= cnt_out + 1;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
2. **对于 `always` 模块的敏感表为{% label primary @电平敏感 %}信号的组合逻辑,统一使用阻塞賦值**。例如:
<span id="cnt2.v"></span>
```verilog cnt2.v
/******************************
always 模块的敏感表为电平敏感信号的组合逻辑, 使用阻塞赋值。请注意, 此例
"cnt_out_plus"虽然被指定为 reg 型, 但是实现时是纯组合逻辑。
******************************/
module cnt2 (cnt_out, cnt_out_plus);
input [3:0] cnt_out;
output [3:0] cnt_out_plus;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [3:0] cnt_out_plus;
always @(cnt_out)
cnt_out_plus = cnt_out + 1;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
3. **对于 `assign` 关键字描述的{% label primary @组合逻辑 %},称为连续赋值语句,统一使用 `=`,变量被定义为 `wire` 型信号**。例如:
```verilog cnt3.v
/******************************
assign 描述的组合逻辑。"cnt_out_plus"被定义为 wire 型信号。
******************************/
module cnt3 (cnt_out, cnt_out_plus);
input [3:0] cnt_out;
output [3:0] cnt_out_plus;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
wire [3:0] cnt_out_plus;
assign cnt_out_plus = cnt_out + 1;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -197,35 +196,35 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
******************************/
module cnt4 (clock, cnt_out);
input clock;
output [3:0] cnt_out;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [3:0] cnt_out;
always @(negedge clock)
cnt_out <= cnt_out + 1;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
3. **异步复位/置位**:绝大多数目标器件的寄存器模型都包含异步复位/置位端。异步复位/置位是指无论时钟沿是否有效,当复位/置位信号有效沿到达时,复位/置位立即发挥功能。指定异步复位/置位时,**只需在 `always` 的敏感表中加入复位/置位信号的有效沿即可**。下例描述的异步复位电路是最常用的寄存器复位形式之一。
```verilog
/******************************
最常用的异步复位电路, 设异步复位信号"reset_"是低有效信号(也即下降沿开始复位)。用户可
以在 begin/end 结构之间填入复杂的用户逻辑。
******************************/
// reg_counter.v
module reg_counter (clock, reset_, cnt_reg_s, cnt_reg_a);
input clock;
input reset_;
output [3:0] cnt_reg_s;
output [3:0] cnt_reg_a;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [3:0] cnt_reg_s; //asynchronous reset
always @(posedge clock or negedge reset_)
@@ -236,7 +235,7 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
cnt_reg_s [3] <= ~cnt_reg_s [3];
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
reg [3:0] cnt_reg_a; //synchronous reset
always @(posedge clock)
if (!reset_)
@@ -245,25 +244,25 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
cnt_reg_a [2:0] <= cnt_reg_a [2:0] + 1;
cnt_reg_a [3] <= ~cnt_reg_a [3];
end
endmodule
```
4. **同步复位/置位**:任何寄存器都可实现同步复位/置位功能。指定同步复位/置位时,**`always` 的敏感表中仅有时钟沿信号**,当同步复位/置位信号变化时,同步复位/置位并不立即发生,仅仅当时钟沿采到同步复位/置位的有效电平时,才会在时钟沿到达时刻进行复位/置位操作。
```verilog reg_counter.v
/******************************
常用的同步复位电路, 设异步复位信号"reset_"是低电平有效信号。请比较上例, 对比 always
常用的同步复位电路, 设异步复位信号"reset_"是低电平有效信号。请比较上例, 对比 always
的敏感表。
******************************/
module reg_counter (clock, reset_, cnt_reg_s, cnt_reg_a);
input clock;
input reset_;
output [3:0] cnt_reg_s;
output [3:0] cnt_reg_a;
reg [3:0] cnt_reg_s; //asynchronous reset
always @ (posedge clock or negedge reset_)
if (!reset_)
@@ -272,7 +271,7 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
cnt_reg_s [2:0] <= cnt_reg_s [2:0] + 1;
cnt_reg_s [3] <= ~cnt_reg_s [3];
end
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [3:0] cnt_reg_a; //synchronous reset
always @ (posedge clock)
@@ -283,24 +282,24 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
cnt_reg_a [3] <= ~cnt_reg_a [3];
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
> 关于同步复位和异步复位的优缺点,在 [3.9 小节](#0309) 有详细介绍。
5. **同时使用时钟上升和下降沿的问题**:有时因为数据采样或调整数据相位等需求,设计者会在一个 `always` 的敏感表中同时使用时钟的 `posedge` 和 `negedge`,或者在两个 `always` 的敏感表中分别使用时钟的 `posedge` 和 `negedge` 对某些寄存器电路操作。在这两种描述下,当时钟上沿或下沿到达时,该寄存器电路都会做相应的操作。**这个双沿电路往往可以等同于使用了原时钟的倍频时钟的单沿操作电路**。对于实现在 PLD 的设计而言,同时使用时钟的上、下沿往往是不推荐的,因为 PLD 内嵌的 PLL/DLL 和一些时钟电路往往只能对时钟的一个沿保证非常好的指标,而另个沿的抖动、偏斜、斜率等指标不见得非常优化,有时同时使用时钟的正负沿会因时钟的抖动、偏斜、占空比、斜率等问题造成一定的性能恶化。因此推荐的做法是:将原时钟通过 PLL/DLL 倍频,然后使用倍频时钟的单沿(如上升沿)进行操作。但是电路设计不可一概而论,如果必须使用时钟的双沿对同一个寄存器操作时,请设计者明确此时相当于使用了倍频时钟。
```verilog clock_edge.v
/******************************
某电路使用 50MHz 时钟双沿操作相当于使用同相位 100MHz 倍频时钟操作。
******************************/
module clock_edge (clk_50M, clk_100M, rst_, cnt1, cnt2);
input clk_50M, clk_100M, rst_;
output [3:0] cnt1, cnt2;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [3:0] cnt_temp1, cnt_temp2;
wire [3:0] cnt1;
@@ -309,45 +308,45 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
cnt_temp1 <= 4'b0000;
else
cnt_temp1 <= cnt_temp2 + 1;
always @(negedge clk_50M or negedge rst_ )
if (!rst_)
cnt_temp2 <= 4'b0000;
else
cnt_temp2 <= cnt_temp1 + 1;
assign cnt1 = (clk_50M)? cnt_temp2 : cnt_temp1;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
reg [3:0] cnt2;
always @(posedge clk_100M or negedge rst_ )
if (!rst_)
cnt2 <= 4'b0000;
else
cnt2 <= cnt2 + 1;
endmodule
```
这里相当于使用与 50MHz 时钟 `clk_50M` 同相位的 2 倍频时钟 `cIk_100M` 进行累加计算,等效描述如下:
```verilog
reg [3:0] cnt2;
always @(posedge clk_100M or negedge rst_ )
if (!rst_)
cnt2 <= 4'b0000;
else
cnt2 <= cnt2 + 1;
```
以上代码综合后,对应的电路结构如[图 4-1](#图4-1) 所示。
<a name="图4-1"></a>
<div align="center">
<img src="https://josh-blog-1257563604.cos.ap-beijing.myqcloud.com/img/2020-12-02-josh-verilog-part-4/2020-12-02-josh-verilog-part-4-010-RegisterCircuitModeling-01.png!900px" alt="某双沿加法电路与等价倍频时钟单沿加法电路结构示意图"/>
</div>
RTL 仿真波形如[图 4-2](#图4-2) 所示。
<a name="图4-2"></a>
@@ -369,20 +368,20 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
/******************************
某组合逻辑译码电路。
******************************/
module decode_cmb (addr, CS, cs1, cs2, cs3, cs4);
input [7:0] addr; // only the 2 MSB bits used
input CS; // Low effect
output cs1, cs2, cs3, cs4; // Low effect
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg cs1, cs2, cs3, cs4;
parameter chip1_decode = 2'b00,
chip2_decode = 2'b01,
chip3_decode = 2'b10,
chip4_decode = 2'b11;
always @(CS or addr)
if (CS)
{cs1, cs2, cs3, cs4} = 4'b1111;
@@ -392,10 +391,10 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
chip2_decode: {cs1, cs2, cs3, cs4} = 4'b1011;
chip3_decode: {cs1, cs2, cs3, cs4} = 4'b1101;
chip4_decode: {cs1, cs2, cs3, cs4} = 4'b1110;
endcase
endcase
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -407,16 +406,16 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
</div>
2. `assign` 等语句描述的组合逻辑电路
这种形式描述组合逻辑电路适用于描述那些相对简单的组合逻辑。信号一般被定义为 `wire` 型,常用的 `assign` 结构除了直接赋值逻辑表达式外,还可以使用 `?` 语句。
```verilog decode_cmb2.v
/******************************
使用 assign 结构描述该组合逻辑译码电路。
******************************/
module decode_cmb2 (addr, CS, cs1, cs2, cs3, cs4);
input [7:0] addr; // only the 2 MSB bits used
input CS; // Low effect
output cs1, cs2, cs3, cs4; // Low effect
@@ -427,13 +426,13 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
chip2_decode = 2'b01,
chip3_decode = 2'b10,
chip4_decode = 2'b11;
assign cs1 = (!CS && (addr[7:6] == chip1_decode))? 0 : 1 ;
assign cs2 = (!CS && (addr[7:6] == chip2_decode))? 0 : 1 ;
assign cs3 = (!CS && (addr[7:6] == chip3_decode))? 0 : 1 ;
assign cs4 = (!CS && (addr[7:6] == chip4_decode))? 0 : 1 ;
assign cs1 = (!CS && (addr[7:6] == chip1_decode))? 0 : 1 ;
assign cs2 = (!CS && (addr[7:6] == chip2_decode))? 0 : 1 ;
assign cs3 = (!CS && (addr[7:6] == chip3_decode))? 0 : 1 ;
assign cs4 = (!CS && (addr[7:6] == chip4_decode))? 0 : 1 ;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -457,7 +456,7 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
/******************************
将双向总线 data_bus 分为输入总线 data_in 和输出总线 data_in, 然后分别使用。
******************************/
module bibus (clk, rst, sel, data_bus, addr);
input clk, rst, sel;
input [7:0] addr;
@@ -465,11 +464,11 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
inout [7:0] data_bus;
wire [7:0] data_in, data_out;
assign data_in = data_bus;
assign data_bus = (sel) ? data_out : 8'bZ;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
decode decode_inst (
.clock (clk),
.reset (rst),
@@ -485,27 +484,27 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
```verilog
assign data_bus = (sel) ? data_out : 8'bZ;
```
2. 如果三态总线的使能关系比较复杂,不是单一信号,此时可以使用嵌套的问号表达式,或者使用语句描述(当然也可以用 `if ... else` 结构)。
```verilog complex_bibus.v
/******************************
双向总线 databus 的输出由 3 个使能信号 sel1、sel2、se13共同确定, 下述代码中中使用嵌
套的问号表达式描述了该三态总线的使能选择。
******************************/
module complex_bibus (clk, rst, sel1, sel2, sel3, data_bus, addr);
input clk, rst;
input sel1, sel2, sel3;
input [7:0] addr;
wire [7:0] data_in;
//wire [7:0] data_out; //use wire type
wire [7:0] decode_out;
wire [7:0] cnt_out;
assign data_in = data_bus;
decode decode_inst (
.clock (clk),
.reset (rst),
@@ -513,20 +512,20 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
.addr_bus (addr),
.data_bus_out (decode_out)
);
counter counter_inst (
.clock (clk),
.reset (rst),
.data_bus_in (data_in),
.cnt_out (cnt_out)
);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
inout [7:0] data_bus;
assign data_bus = (sel1)? decode_out : ((sel2)? cnt_out : ((sel3)? 8'b11111111:
assign data_bus = (sel1)? decode_out : ((sel2)? cnt_out : ((sel3)? 8'b11111111:
8'bZZZZZZZZ));
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -543,9 +542,9 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
input clk, rst;
input sel1, sel2, sel3;
input [7:0] addr;
assign data_in = data_bus;
decode decode_inst (
.clock (clk),
.reset (rst),
@@ -553,34 +552,34 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
.addr_bus (addr),
.data_bus_out (decode_out)
);
counter counter_inst (
.clock (clk),
.reset (rst),
.data_bus_in (data_in),
.cnt_out (cnt_out)
);
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
inout [7:0] data_bus;
wire [7:0] data_in;
reg [7:0] data_out; //use reg type, but not registers
wire [7:0] decode_out;
wire [7:0] cnt_out;
always @(decode_out or cnt_out or sel1 or sel2 or sel3) begin
case ({sel1, sel2, sel3})
3'b100: data_out = decode_out;
3'b010: data_out = cnt_out;
3'b001: data_out = 8'b11111111;
default: data_out = 8'bZZZZZZZZ;
default: data_out = 8'bZZZZZZZZ;
endcase
end
assign data_bus = data_out;
assign data_bus = data_out;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -590,26 +589,26 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
/******************************
三态实例中调用的子模块
******************************/
module counter (clock,
reset,
data_bus_in,
cnt_out
);
input clock, reset;
input [7:0] data_bus_in;
output [7:0] cnt_out;
reg [7:0] cnt_out;
always @(posedge clock or posedge reset)
if (reset)
cnt_out <= 8'b00000000;
else
cnt_out <= data_bus_in + 1;
endmodule
```
@@ -617,17 +616,17 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
/******************************
三态实例中调用的子模块
******************************/
module decode (clock, reset, data_bus_in, addr_bus, data_bus_out);
input clock, reset;
input [7:0] data_bus_in;
input [7:0] addr_bus;
output [7:0] data_bus_out;
reg [7:0] data_bus_out;
always @(posedge clock or posedge reset)
if (reset)
data_bus_out <= 8'b00000000;
@@ -638,7 +637,7 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
8'b 10100000: data_bus_out <= 8'b11111111;
default: data_bus_out <= data_bus_in;
endcase
endmodule
```
@@ -647,43 +646,43 @@ RTL 描述中时序逻辑和组合逻辑的连接关系和拓扑结构决定了
mux 也是一种组合逻辑电路,它的常用建模方式也有两种:对于简单的 mux,可以直接用 `assign` 和 `?` 表达式建模,对于复杂的 mux,则需要使用 `always` 和 `if ... else`、`case` 等条件判断语句建模。
1. **简单的 mux 用 `?` 表达式建模。信号被定义为 `wire` 型,`?` 表达式的判断条件是的选择端。**
```verilog mux.v
/******************************
使用"?"表达式描述一个 2 选 1 的 mux。
******************************/
module mux (en, a, b, mux_out);
input en;
input a, b;
output mux_out;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
wire mux_out;
assign mux_out = (en)? a : b;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
2. **复杂的 mux 用 `case` 或嵌套的 `if ... else` 建模。信号被定义为 `reg` 型,每个`case` 或 `if ... else` 的条件分支即一路 mux 的选择输出。**
```verilog
/******************************
使用 case 描述一个 4 选 1 的 mux。
******************************/
// mux2.v
module mux2 (en, a, b, c, d, mux_out);
input [1:0] en;
input a, b, c, d;
output mux_out;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg mux_out;
always @(en or a or b or c or d)
case(en)
2'b00: mux_out = a;
@@ -692,7 +691,7 @@ mux 也是一种组合逻辑电路,它的常用建模方式也有两种:对
2'b11: mux_out = d;
endcase
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -744,8 +743,8 @@ always @(posedge clk)
if (WR && CS) // write
RAM8x64 [addr] <= data_in [7:0];
else if (~WR && CS) // read
mem_data <= RAM8x64 [addr];
mem_data <= RAM8x64 [addr];
assign data_out = (en)? mem_data[7:0] : {~mem_data[7], mem_data[6:0]};
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
@@ -769,57 +768,57 @@ endmodule
一般来说,PLD 中的**主要时钟处理为分频和移相**。偶数分频十分简单,只需用高速时钟做一个同小计数器,然后在相应的位抽头即可。奇数分频电路相对复杂一些。移相的基本方法是通过高速时钟调整相位,或者通过时钟反向调整相位。
1. **偶数分频 + 相位控制**
```verilog clk_div_phase.v
/******************************
将一个 200kHz 时钟做 2 分频、4 分频、8 分频, 要求分频后的 3 个时钟同相, 而且与源时
钟近似同相。在这个设计中, 因为输入时钟速率很低, 仅有 200kHz, 而一般 PLD 内嵌的 PLL
钟近似同相。在这个设计中, 因为输入时钟速率很低, 仅有 200kHz, 而一般 PLD 内嵌的 PLL
的输入频率下限都在 MHz 级,所以无法使用 PLL 完成分频与相位调整要求。另外对于低速时
钟的分频,使用计数器既能满足时序要求,也比较节约器件资源。
******************************/
module clk_div_phase (rst, clk_200K, clk_100K, clk_50K, clk_25K);
input clk_200K;
input rst;
output clk_100K, clk_50K, clk_25K;
wire clk_100K, clk_50K, clk_25K;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [2:0] cnt;
reg [2:0] cnt;
always @ (posedge clk_200K or negedge rst)
if (!rst)
cnt <= 3'b000;
else
cnt <= cnt + 1;
assign clk_100K = ~cnt [0];
assign clk_50K = ~cnt [1];
assign clk_25K = ~cnt [2];
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
这个设计的**难点在于如何调整所有时钟的相位关系**。本例巧妙地通过对计数器每个 bit 的反向,完成了所有分频后时钟的相位调整,保证了3个分频后时钟的相位严格同相。对硬件结构比较清晰则会发现,这 3 个派生时钟与源时钟相比有一个非常小的相位差,**这个相位差是由寄存器的固有 $T_{\mathrm{co}}$(延迟)和计数器累加的组合逻辑造成的**,一般来说在 PLD 中寄存器固有 $T_{\mathrm{co}}$ 的典型值为 1~2ns,而简单的加法运算的组合逻辑门延迟也约为 ns 级,这两个延迟的总和与时钟周期相比微乎其微。如果忽略这个 ns 级的延迟,则通过每个分频时钟的反向,使 3 个分频时钟与源时钟同相,也就是说这 4 个时钟有共同的上升沿。
2. **奇数分频**
```verilog clk_3div.v
/******************************
对源时钟做 3 分频, 要求 3 分频时钟占空比为50%。
******************************/
module clk_3div (clk, reset, clk_out);
input clk, reset;
output clk_out;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
reg [1:0] state;
reg clk1;
always @(posedge clk or negedge reset)
if(!reset)
state <= 2'b00;
@@ -829,18 +828,18 @@ endmodule
2'b01: state <= 2'b11;
2'b11: state <= 2'b00;
default:state <= 2'b00;
endcase
endcase
always @(negedge clk or negedge reset)
if(!reset)
clk <= 1'b0;
else
clk1 <= state[0];
assign clk_out = state[0] & clk1;
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
endmodule
```
3 分频、5 分频等奇数分频时钟可以使用 `case` 结构或简单的状态机(FSM)描述,设计**难点在于如何通过组合逻辑调整分频时钟的占空比。**
@@ -894,14 +893,14 @@ endmodule
/******************************
同步复位电路建模。
******************************/
module syn_rst (clk, rst_, cnt1, cnt2);
input clk;
input rst_;
output [4:0] cnt1 , cnt2;
reg [4:0] cnt1 , cnt2;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
always @(posedge clk)
if (!rst_) begin
@@ -912,11 +911,11 @@ endmodule
if (cnt1 < 2'b11)
cnt1 <= cnt1 + 1;
else
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -942,7 +941,7 @@ endmodule
#### 3.9.1.3. 同步复位的缺点
- 很多目标器件(如 FPGA 和 CPLD)和 ASIC 库的触发器本身并不包含同步复位端口,使用同步复位会增加更多逻辑资源。
- 同步复位的最大问题在于必须保证复位信号的有效时间足够长,从而才能保证所有触发器都有效地复位。由于仅当时钟沿采样到复位信号时才会进行复位操作,所以**同步复位信号的持续时间起码要大于设计的最长时钟周期**,以保证所有时钟的有效沿都能采样到同步复位信号。其实仅仅保证同步复位信号的持续时间大于最慢的时钟周期还是不够的,设计中**还要考虑到同步复位信号树通过所有相关组合逻辑路径的延时以及由于时钟布线产生的偏斜(skew)**,只有同步复位大于时钟最大周期加上同步信号穿过的组合逻辑路径延迟加上时钟偏斜时,才能保证同步复位可靠、彻底。同步复位实现结构如下图所示,假设同步复位逻辑树组合逻辑的延时为 $\mathtt{t1}$,复位信号传播路径的最大延迟为 $\mathtt{t2}$,最慢时钟的周期为 $\mathtt{Period max}$,时钟的 skew 为 $\mathtt{(clk2 - clk1)}$,则同步复位的周期 $\mathtt{Tsyn\_rst}$ 应该满足下述公式:
$$
\mathtt{Tsyn\_rst > Period max + \left(clk2 - clk1 \right) + t1 + t2}
@@ -958,19 +957,19 @@ $$
#### 3.9.2.1. 异步复位电路建模
1. 指定异步复位时,只需在 `always` 的敏感表中加入复位信号的有效沿即可,当复位信号有效沿到达时,无论时钟沿是否有效,复位立即发挥功能。
```verilog asyn_rst.v
/******************************
异步复位电路建模。
******************************/
module asyn_rst (clk, rst_, cnt1, cnt2);
input clk;
input rst_;
output [4:0] cnt1 , cnt2;
reg [4:0] cnt1 , cnt2;
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
always @(posedge clk or negedge rst_)
if (!rst_) begin
@@ -981,11 +980,11 @@ $$
if (cnt1 < 2'b11)
cnt1 <= cnt1 + 1;
else
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
endmodule
```
@@ -999,23 +998,23 @@ $$
#### 3.9.2.2. 异步复位的优点
- 由于多数目标器件(如FPGA 和 CPLD)和 ASIC 库的触发器都包含异步复位端口,异步复位会节约逻辑资源。
- 异步复位设计简单。
- 对于大多数 FPGA, 都有专用的全局异步复位/置位资源(GSR, Global Set Reset),使用GSR 资源,异步复位到达所有寄存器的偏斜(skew)最小。
#### 3.9.2.3. 异步复位的缺点
- 异步复位的作用和释放与时钟沿没有直接关系,在异步复位生效时问题并不明显,但是当异步复位释放时,如果异步复位信号释放时间和时钟的有效沿到达时间几乎一致,则容易造成触发器输出为**亚稳态**,造成逻辑错误。
- 另外,如果异步复位逻辑树的组合逻辑产生了**毛刺**,则毛刺的有效沿会使触发器误复位,造成逻辑错误。
#### 3.9.2.4. 推荐的复位电路设计方式
1. 推荐的复位电路设计方式是异步复位,同步释放。这种方式,可以有效地继承异步复位设计简单的优势,并克服异步复位的上述风险与缺陷。
2. 在 FPGA/CPLD 等可编程逻辑器件设计中,使用异步复位,同步释放可以节约器件资源,并获得稳定可靠地复位效果,是推荐的复位设计方式。
```verilog asyn_rst_syn_release.v
/******************************
异步复位,同步释放电路建模。异步复位,同步释放的具体设计方法很多,关键是如何保证同步
@@ -1046,8 +1045,8 @@ always @(posedge clk or negedge reset_reg)
if (cnt1 < 2'b11)
cnt1 <= cnt1 + 1;
else
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
cnt1 <= cnt1;
cnt2 <= cnt1 - 1;
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
@@ -1122,7 +1121,7 @@ always @(a or b or c or d or sel0 or sel1 or sel2 or sel3) begin
else if (sel1)
z = b;
else if (sel0)
z = a;
z = a;
end
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑