![3f6b0d7dd50a1fe755f3f58976e5d0fe.png](https://i-blog.csdnimg.cn/blog_migrate/72cdfac79a333dde4a6d8d6c234f2741.jpeg)
Verilog HDLBits--Edge Detection
疫情期间,宅家的你不妨一起,做些对得起自己、对得起守候的事情! 希望疫情早点结束,我们一切都好!
这篇文章主要讲述HDLBits的基础练习中,有关Verilog边沿检测类问题。是本人做到目前为止觉得有必要拿出来细细琢磨的一小部分。主要讲述本人在初期踩过的坑,和一时半会儿没有转过来的弯儿,以及对于学习Verilog的细微理解。希望对于一些初学者和正在入门的道友们,通过对练习题的学习和成功编译,让大家认识到Verilog的学习并没有传说中那么高的门槛。尤其对代码成功编译的喜悦,也会极大提高大家的学习积极性。 后面对于有必要的部分,会继续更新,敬请期待!
0. HDLBits简介
传送门:HDLBits---Verilog Online Learning---link. 首先安利这款非常棒的Verilog在线学习网站---HDLBits。(自从被男票安利后,漫漫自学路全靠它支撑走到现在。)这是国外一家开源FPGA学习平台,主要有两大功能:
1. Verilog 在线仿真
2. Verilog 基础练习
该平台是基于Icarus Verilog(简称iVerilog,著名的HDL开源仿真工具)仿真验证的。在线仿真提供方便的网页版仿真验证功能;基础练习部分涵盖了一系列Verilog基础知识,从简单的wire、逻辑门,到组合电路、时序电路、TestBench编写等。类似于软件编程的牛客网、LeetCode,以在线编程,在线编译、在线仿真验证的形式,考察和讲述Verilog电路设计的相关内容,具有很强的操作实践性。此外,平台还提供了运行结果的参考匹配,以Mismatch(类似于代码通过率)波形形式显示在波形图中。这对于代码的改正和优化有着直观的参考意义。总之,超级棒的一个Verilog在线学习网站。
下面直接上题。
1. Problem94: Detect an Edge
要求: 对于8bits向量中的每一位,当输入信号由0变为1时进行检测(类似于上升沿检测)。当信号从0到1变化后,输出位被置位。 这里有一些示例。为清楚起见,in[1]和pedge[1]单独显示。
![e31175c25762841236090a5a50226c21.png](https://i-blog.csdnimg.cn/blog_migrate/f4fe44135491321815603734953b5158.png)
上升沿检测,脉冲结果输出。附上答案、电路图及解析。
1.1 正确答案
1 module top_module (
2 input clk,
3 input [7:0] in,
4 output [7:0] pedge
5 );
6 reg[7:0] in_last;
7 //D flip-flop
8 always@(posedge clk)
9 in_last <= in;
10 assign pedge <= ~in_last & in;
11 endmodule
核心代码:上升沿检测逻辑~in_last & in。
解析
:信号前一状态 in_last 为 0 低电平,当前状态 in 为 1 高电平,即可检测出该信号的上升沿。因此电路组成:保存信号前一状态的D触发器 + 输出组合逻辑 out = ~in_last & in 。电路结构见1.2中电路图。
- tip:代码要尽量做到模块化,按照其功能分模块编写。这样可以避免代码在同一个begin-end语句中,极有可能出现的时序错误。
1.2 电路图
![e2f286def01371c064e9c61588c3e7cb.png](https://i-blog.csdnimg.cn/blog_migrate/84e9593c1ca77bc885434b7fb2783f6a.png)
电路图虽然很简单,但在入门初期,多画画电路图,百益无害。尤其对于那些对C、C++、Python等软件编程语言烂熟于心而又来学习Verilog的童鞋们,画电路图有助于帮助你转换从软件到硬件的编程思路、理解Verilog过程块的并行运行方式、更清晰地区分时序与组合逻辑电路的运作。
2. Problem95: Detect Both Edge
要求: 对于8bits向量中的每一位,当输入信号发生变化时就进行检测(两个边沿都进行检测)。当信号发生变化后,输出位被置位。 这里有一些示例。为清楚起见,in[1]和pedge[1]单独显示。
![d3e7528b5358af8a696f3d1a84de7dfa.png](https://i-blog.csdnimg.cn/blog_migrate/b37af0cb5acd5acd1d1b347a1591ba11.png)
双边沿检测,脉冲结果输出。先附答案和电路图。
2.1 正确答案
1 module top_module (
2 input clk,
3 input [7:0] in,
4 output [7:0] anyedge
5 );
6 reg[7:0] in_last;
7 always @(posedge clk)//D flip-flop
8 in_last <= in;
9 // Method1: posedge +negedge
10 //assign anyedge = (~in_last & in) | (in_last & ~in);
11 // Method2: XOR
12 assign anyedge = in ^ in_last
13 endmodule
解析
: 该题有如下两种解法。
- 上升沿加下降沿检测。上升沿核心检测逻辑: ~in_last & in。下降沿类似上升沿核心逻辑为: in_last & ~in。最后运用或逻辑组合起来即可。核心代码:anyedge = (~in_last & in) | (in_last & ~in)。
- 异或逻辑检测。边沿检测,从整体来看,即信号发生变化就进行检测输出。因此使用异或逻辑 。核心代码:in ^ in_last 。
2.2 电路图
这里只给出了方法2的电路图。
![ad1166fc1a21a26f063479acba4c59f5.png](https://i-blog.csdnimg.cn/blog_migrate/302505d2ffc93a3de52bd6c48ff7cfa8.png)
再次说明一下,电路图虽简单,对于初学者和习惯于软件编程的转行者,却能起到意想不到的思路扭转功能。尤其对于Verilog并行计算的运作机制的理解,将会有非常大的帮助。
3. Problem96: Edge Capture Register
要求: 对于32bits向量中的每一位,当输入信号由1变为0时进行检测(即下降沿检测)。其中检测表示在复位信号(同步)到达前,输出将保持1。 每一个输出位就像一个RS触发器,即当对应位出现1 to 0的变化时,输出位将置1;而当reset信号为高电平,输出位将在下一个时钟的上升沿被复位。如果信号的下降沿和reset事件在同一时刻发生,将优先执行复位操作。在下图示例波形的最后4个时钟周期中,reset事件比‘set’事件早一个周期出现,因此这里没有前述冲突。 为清楚起见,in[1]和out[1]在波形中分别单独显示。
![4b0b5264352226baeb987af72de62b3c.png](https://i-blog.csdnimg.cn/blog_migrate/a27e2faf31008637d71366c8011a300b.png)
Hints: 不同于94、95两题的边沿检测,该题边沿检测输出保持置位状态1,直到复位信号到达(而前两题输出只需维持一个时钟周期)。 下面先附答案和电路图。
3.1 正确答案
1 module top_module (
2 input clk,
3 input reset,
4 input [31:0] in,
5 output [31:0] out
6 );
7 reg[31:0] in_last;//in's last state
8 always@(posedge clk)//D Flip-Flop
9 in_last <= in;
10 always@(posedge clk)
11 if(reset)
12 out <= '0;
13 else
14 out <= out | in_last & ~in;
15 endmodule
解析
:首先电路需要检测下降沿,其次在检测到的下降沿所在位进行置位操作,同时还不能影响到其他已置位的输出位。==因此,代码核心部分第14行,在下降沿检测的基础上,采用或逻辑,使得无论是新检测到的下降沿,还是先前已被置位的下降沿,都能使输出保持1状态。
3.2 电路图
![79058859bbcdd1a65ad794a8fc48432e.png](https://i-blog.csdnimg.cn/blog_migrate/868213bce1995583b8e55256be3521ca.png)
附上电路图供需要的童鞋们理解。
3.3 时序仿真结果
![ddd6849fb6a9057c4151f247c73979f0.png](https://i-blog.csdnimg.cn/blog_migrate/29a98baa6ccdc398c6503f2a5966aab6.jpeg)
![0369f4743362d68b644e832d8a6e8812.png](https://i-blog.csdnimg.cn/blog_migrate/fb126309f888e3a9e5c1c111552a37c9.jpeg)
有过软件编程学习经历的同学,遇到这类中等难度的题,很容易走向for循环的思路。但for循环语句在Verilog中属于不可综合语言,一般只用来编写TestBench测试文件,而不能用来设计可综合电路。这点,西电蔡觉平老师在公开课上强调得很到位。而且,蔡老师把Verilog的设计思想循序渐进地透露得很清楚,讲课也颇有风趣,是一部相当不错的入门级视频课程。
课程链接: 西电蔡觉平老师B站学习视频地址:
3.4 踩坑经
同样有入坑经历的小伙伴,希望能对你有所启发;没有入过坑的小伙伴也可以通过下面的描述猜一猜是哪里的问题。
坑1. 时序仿真结果慢一个时钟周期(需两个时钟周期才出结果)。
坑2. D触发器与同步复位信号放到同一if--else语句中,时序仿真出错。
错误代码示范
1 module top_module (//Verilog
2 input clk,
3 input reset,
4 input [31:0] in,
5 output [31:0] out
6 );
7 reg[31:0] in_last;//in's last state
8 reg[31:0] out_temp;//negtive edge detected result
9 always@(posedge clk) begin
10 if(reset)
11 out <= '0;
12 else begin
13 in_last <= in;
14 out_temp <= in_last & ~in;
15 out <= out | out_temp;//keep 1
16 end
17 end
18 endmodule
对应时序仿真结果
![286d44c2670508dbeacbf6fec82e60ce.png](https://i-blog.csdnimg.cn/blog_migrate/c0889e83087bf92de337e66058a95252.jpeg)
![5c25796474aaec5d97370010adf30360.png](https://i-blog.csdnimg.cn/blog_migrate/87b58b27d2d142f4740d8039f9e2d9e5.jpeg)
3.4.1 坑1:
在输出out为“e、2、3、7”时,总比参考结果慢一个时钟周期。原因在于代码第14行,out_temp为reg寄存器类型,这使得时序结果比正确的结果多耗费一个时钟周期。
可以采用如下两种方法解决该问题: 1)将第14行单独用一个过程赋值语句描述。
1)assign capture = ~in & temp;
2)将第14、15行合并成一行。
(2)out <= out | in_last & ~in;
在以后的电路设计中,若想减少时序延时,应尽量减少reg寄存器类型变量赋值,用组合电路/过程赋值语句代替。
3.4.2 坑2:
当参考结果为20时,我的结果输出10。原因在于第13行,将D触发器写到与reset相关的if--else语句中,使得reset操作直接影响到了D触发器的实时更新,从而导致结果错误。在仿真波形上表现为在倒数第四个时钟上升沿,in_last寄存器中的存储值未及时更新,仍为10。解决方法则是将第13行描述的触发器单独用一个always块描述。
always@(posedge clk) in_last <= in;
4. Problem97: Dual-edge Triggered Flip-Flop
要求: 一般的触发器都是时钟上升沿或下降沿触发的。双边沿触发器则是在时钟的两个边沿都会引起触发。然而,FPGA没有双边沿触发的触发器,且always@(posedge clk, negedge clk)中的敏感事件列表不合乎语法。 设计一种电路,使其功能类似于双边沿触发器。 Tips: 1)在FPGA中虽不能设计双边沿触发器,但是能够设计单独的上升沿触发器和下降沿触发器。2)该问题属于中等难度的电路设计问题,只需要对基本的Verilog语法有所掌握即可。这是一个电路设计问题,而非编码能力考察。在编码实现前,手绘下电路图会对编码设计有所帮助。
![b5bb58c2479648a78e99229bafb55211.png](https://i-blog.csdnimg.cn/blog_migrate/a6e7a8b7b0693ad84385543e14b1ee84.png)
分析: 题目要求很清楚,这里不再赘述。该问题有两种解法,一种是Mux选择输出法,另一种是网站给出的XOR方法。
急性子的朋友们可以不看我这一段的啰嗦:正如题目中给出的hint所说,该题考察的是电路设计能力,而非编码能力。因此可以先尝试画出电路图,再去编码实现。这一点更是为软件编程思想固化的童鞋们提供了相当不错的设计思路。本人也是深受软件编程思想的影响,通过对电路图的分析,开始慢慢get到Verilog的设计思想,希望跟我有同样烦恼的小伙伴看到这里可以早点战胜它。个人觉得其实对于任何Verilog设计,都要考虑其电路的具体实现(并不一定要画出来,但要考虑代码对应的硬件实现。),这对于后续的面积、延时、功耗等方面的优化也会有极大的帮助。
4.1 方法1--Mux选择输出法
4.1.1 电路图
![815127877c6a1cc170c477d1ff878b94.png](https://i-blog.csdnimg.cn/blog_migrate/66307a9058559b48b8a59c3b93b238ac.png)
4.1.2 正确答案
1 module top_module (
2 input clk,
3 input d,
4 output q
5 );
6 reg neg_q, pos_q;
7
8 always@(negedge clk)//negedge triggered flip-flop
9 neg_q <= d;
10 always@(posedge clk)//posedge triggered flip-flop
11 pos_q <= d;
12 assign q = clk ? pos_q : neg_q;//a mux for out
13
14 endmodule
这种方法比较容易理解。个人感觉mux选择器的selection控制端稍微有些问题。其实mux部分更直观的思路是分别检测出时钟clk的上升沿和下降沿,再做选择控制。然而,边沿检测需要更高频率的时钟,目前只基于这个平台无法做到,因此单针对这道题,这样写也能success。
4.2 方法2--XOR法
4.2.1 正确答案
1 module top_module(
2 input clk,
3 input d,
4 output q);
5
6 reg p, n;
7 // A positive-edge triggered flip-flop
8 always @(posedge clk)
9 p <= d ^ n;
10 // A negative-edge triggered flip-flop
11 always @(negedge clk)
12 n <= d ^ p;
13 // Why does this work?
14 // After posedge clk, p changes to d^n. Thus q = (p^n) = (d^n^n) = d.
15 // After negedge clk, n changes to d^p. Thus q = (p^n) = (p^d^q) = d.
16 // At each (positive or negative) clock edge, p and n FFs alternately
17 // load a value that will cancel out the other and cause the new value of d to remain.
18 assign q = p ^ n;
19 // Can't synthesize this.
20 /*always @(posedge clk, negedge clk) begin
q <= d;
end*/
21 endmodule
该方法是网站给出的solution。注释部分已讲得很清楚,顺着其思路理解也能理解。但个人总感觉这种方法比较绕,还未弄清楚该方法更多的意义。还望各路大神不吝赐教。