verilog基础tips
1、阻塞赋值和非阻塞赋值
阻塞:在本语句中“右式计算”和“左式更新”完全完成之后,才开始执行下一条语句
非阻塞:当前语句的执行不会阻塞下一语句的执【非阻塞相对于阻塞赋值多出一个触发器,时序逻辑中要用非阻塞赋值<=】
阻塞赋值:在复位信号无效后第一个时钟周期a=0,b=0,c=0
always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0)begin a=0; b=1; c=2; end else begin b=a; c=b; end end
非阻塞赋值:在复位信号无效后第一个时钟周期a=0;b=1;c=2;
always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0)begin a<=0; b<=1; c<=2; end else begin b<=a; c<=b; end end
2、if else 语句
具有优先级的if else 语句过多会造成时钟频率的下降
3、时钟分频
参考:小数分频
参考:任意小数分频
在一般情况下,应用到时钟分频尽量用IP核,因为这样的时钟偏斜比较小。(时钟偏斜:同一时钟到达不同寄存器的延时不同)
尽量不用自己分频出来的信号直接作为时钟,可以加入标志信号,在利用源时钟的情况下加入标志信号实现原本的功能
module cnt( input clk, input rst_n, output [ 3 : 0] cnt ); always@(posedge clk or negedge rst_n)begin //进行四分频 if(rst_n == 1'b0) div_cnt <= 'h0; else div_cnt <= div_cnt + 1'b1; end always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) clk_4 <= 'h0; else if(div_cnt == 2'd1) clk_4 <= 'h1; else if(div_cnt == 2'd3) clk_4 <= 'h0; end /*------\/-------- by $Tyz 2021-02-20 --------\/--------{{{ * 需要避免的写法,不用自己分频的信号直接作为时钟使用 always@(posedge clk_4 or negedge rst_n)begin if(rst_n == 1'b0) cnt <= 'h0; else cnt <= cnt + 1'b1; end --------/\-------- by $Tyz 2021-02-20 --------/\------}}}*/ always@(posedge clk or negedge rst_n)begin //建议使用的方式,利用flag信号 if(rst_n == 1'b0) clk_flag <= 'h0; else if(div_cnt == 2'd1) clk_flag <= 'h1; else clk_flag <= 'h0; end always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt <= 'h0; else if(clk_flag) cnt <= cnt + 1'b1; end endmodule
3.1 偶数分频
经常用到的就是偶数分频
`timescale 1ns / 1ns /********** 任意偶数分频器************/ module divider_even #(parameter DIV = 4)( input sys_clk, input sys_rst_n, input [7:0] M_N, // 取代DIV了 output reg div_clk // 任意偶数数分频输出时钟 ); reg [7:0] cnt; always@(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) cnt <= 0; else if(cnt == M_N-1) cnt <= 0; else cnt <= cnt + 1; end always@(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) div_clk <= 1'b0; else if((cnt == 0) || (cnt == (M_N/2))) div_clk <= ~div_clk; else div_clk <= div_clk; end endmodule
3.2 奇数分频:
3.2.1 奇数分频可以直接采用计数器进行得到,如5分频就可以进行模5计数,在等于5的时候拉高,否则拉低信号,则可得到占空比为1/5的分频信号
3.2.2 奇数分频要要求占空比为50%。
首先进行模5计数
而后利用clk的上升沿连续拉低pos_clk三个时钟,而后连续拉高两个时钟周期。
之后再利用clk的下降沿连续拉低neg_clk三个时钟,而后拉高两个时钟周期。
最后对pos_clk 和neg_clk 进行或运算得到输出的分频时钟
tips:上面的拉高拉低可以互换,最后的结果都是相同的(但此时最后的输出为pos_clk和neg_clk与运算)
//奇数分频 N分频 // . . . 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ + // clk | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | // + +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ +---+ // - ------- ------- ------- ------- ------- --------------------------------------------------------------------------------------- // bus X 0 X 1 X 2 X 3 X 4 X // - ------- ------- ------- ------- ------- --------------------------------------------------------------------------------------- // +---------------+ // pos_clk | | // ------------------------+ +--------------------------------------------------------------------------------------- // --+ +---------------+ // neg_clk | | | // +-------------------------+ +--------------------------------------------------------------------------------------- // --+ +-------------------+ // clk_out | | | // +---------------------+ +------------------------------------------------------------------------------------------- module even_divider( clk, rst_n, clk_out ); input clk; input rst_n; output clk_out; reg pos_clk; reg neg_clk; reg [ 7 : 0] div_cnt; parameter DIV_NUM = 5; //其中N为奇数 always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) div_cnt <= 'h0; else if(div_cnt == DIV_NUM-1) div_cnt <= 'h0; else div_cnt <= div_cnt + 1'b1; end //pos_clk always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) pos_clk <= 'h0; else if((div_cnt >= (DIV_NUM-1)/2) && (div_cnt <= DIV_NUM-1)) pos_clk <= 'h0; else pos_clk <= 'h1; end //neg_clk always@(negedge clk or negedge rst_n)begin if(rst_n == 1'b0) neg_clk <= 'h0; else if((div_cnt >= (DIV_NUM-1)/2) && (div_cnt <= DIV_NUM-1)) neg_clk <= 'h0; else neg_clk <= 'h1; end assign clk_out = pos_clk | neg_clk; endmodule
3.2.3 小数分频
tips:一般情况下,自己设计的小数分频对时序要求很高的电路并不使用,可以使用软件自带的IP进行小数分频的设计
4、状态机
状态机可分为mealy型和moore型:
按照写法状态机可分为一段式,二段式,三段式:
二段式状态机可以避免三段式状态机中由于组合逻辑而产生的的竞争冒险现象,同时减少代码复杂度。
而时钟采到毛刺的概率较低,可以有效消除毛刺影响
5、RAM
5.1 RAM的分类
参考:单口RAM、伪双口RAM、真双口RAM、单口ROM、双口ROM的区别
tips:异步时钟域的缓存只要是双口器件都可以完成,但FIFO不需对地址进行控制,是最方便的。
RAM分为单口RAM和双口RAM,双口RAM又分为真双口和伪双口。
注意:图中的真双口RAM和伪双口RAM的解释写反了。懒得改了。
5.2 RAM的使用
RAM占用内存:FPGA内部的RAM
对ram和ROM进行初始化:使用 .coe文件(xilinx)
其具体格式如下:如下表示10进制的数值0到7
MEMORY_INITIALIZATION_RADIX=10; MEMORY_INITIALIZATION_VECTOR= 0, 1, 3, 4, 5, 6, 7;
5.3 乒乓操作
参考:FPGA 四大设计思想
参考:四种常用FPGA/CPLD设计思想与技巧介绍及乒乓操作案例分析
输入数据流通过“数据选择单元“将数据流分配到两个数据缓冲区,数据缓冲模块可以为任何存储模块,比较常用的存储模块为:双口RAM(DPRAM),单口RAM(SPRAM)、FIFO等
1 module ram_pingpong( 2 clk, 3 rst_n, 4 pi_data, 5 po_data 6 ); 7 input clk; 8 input rst_n; 9 10 input [ 7 : 0] pi_data; 11 output [ 7 : 0] po_data; 12 13 reg wra_en,wra_en_delay;//观察波形图可看到两个ram输出数据的选取可以通过延时一拍的wra_en信号 14 reg [ 9 : 0] wra_addr; 15 reg [ 9 : 0] rda_addr; 16 reg wrb_en; 17 reg [ 9 : 0] wrb_addr; 18 reg [ 9 : 0] rdb_addr; 19 20 wire [ 7 : 0] oa_data; 21 wire [ 7 : 0] ob_data; 22 23 t_ram ram_A( //8x1024 的伪双口RAM 24 .data (pi_data ), //只作为数据输入 25 .rdaddress (rda_addr ), 26 .rdclock (clk ), 27 .wraddress (wra_addr ), 28 .wrclock (clk ), 29 .wren (wra_en ), 30 .q (oa_data ) //只作为数据输出 31 ); 32 33 t_ram ram_B( //8x1024 34 .data (pi_data ), 35 .rdaddress (rdb_addr ), 36 .rdclock (clk ), 37 .wraddress (wrb_addr ), 38 .wrclock (clk ), 39 .wren (wrb_en ), 40 .q (ob_data ) 41 ); 42 43 44 always@(posedge clk or negedge rst_n)begin //第一个ram,其写入控制信号为根据自身读写地址信号进行判断 45 if(rst_n == 1'b0) 46 wra_en <= 'd1; 47 else if(rda_addr == 'd1023)begin 48 wra_en <= 'd1; 49 end 50 else if(wra_addr == 'd1023)begin 51 wra_en <= 'd0; 52 end 53 54 end 55 56 always@(posedge clk or negedge rst_n)begin //写地址信号在写满后清零,否则在写使能情况下进行加一 57 if(rst_n == 1'b0) 58 wra_addr <= 'd0; 59 else if(wra_addr == 'd1023)begin 60 wra_addr <= 'd0; 61 end 62 else if(wra_en == 1'b1)begin 63 wra_addr <= wra_addr + 1'b1; 64 end 65 end 66 67 always@(posedge clk or negedge rst_n)begin //读地址在读到头后清零,否则在不写的情况下进行读操作 68 if(rst_n == 1'b0) 69 rda_addr <= 'd0; 70 else if(rda_addr == 'd1023)begin 71 rda_addr <= 'd0; 72 end 73 else if(wra_en == 1'b0)begin 74 rda_addr <= rda_addr + 1'b1; 75 end 76 end 77 always@(posedge clk or negedge rst_n)begin 78 if(rst_n == 1'b0) 79 wrb_en <= 'd0; 80 else if(wrb_addr == 'd1023)begin 81 wrb_en <= 'd0; 82 end 83 else if(wra_addr == 'd1023)begin 84 wrb_en <= 'd1; 85 end 86 end 87 88 always@(posedge clk or negedge rst_n)begin 89 if(rst_n == 1'b0) 90 wrb_addr <= 'd0; 91 else if(wrb_addr == 'd1023)begin 92 wrb_addr <= 'd0; 93 end 94 else if(wrb_en == 1'b1)begin 95 wrb_addr <= wrb_addr + 1'b1; 96 end 97 end 98 99 always@(posedge clk or negedge rst_n)begin 100 if(rst_n == 1'b0) 101 rdb_addr <= 'd0; 102 else if(rdb_addr == 'd1023)begin 103 rdb_addr <= 'd1; 104 end 105 else if(wrb_en == 1'b0) 106 rdb_addr <= rdb_addr + 1'b1; 107 end 108 109 always@(posedge clk)begin 110 wra_en_delay <= wra_en; 111 end 112 113 assign po_data = wra_en_delay ? oa_data:ob_data; //数据选择输出,通过观察画出的波形图,知道可利用wra_en延时一个时钟周期进行数据选择输出 114 115 endmodule
1 `timescale 1ns / 1ns 2 `define clk_period 20 3 4 module tb_ram_pingpong; 5 6 // Inputs 7 reg clk; 8 reg rst_n; 9 reg [7:0] pi_data; 10 11 // Outputs 12 wire [7:0] po_data; 13 14 ram_pingpong ram_pingpong( 15 .clk (clk), 16 .rst_n (rst_n), 17 .pi_data (pi_data), 18 .po_data (po_data) 19 ); 20 21 // Clock generate 22 always # (`clk_period/2) clk <= ~clk; 23 24 initial begin 25 // Initialize Inputs 26 clk = 0; 27 rst_n = 0; 28 pi_data = 0; 29 30 // Wait (3+clk_period) ns for global reset to finish 31 #3; 32 #(`clk_period*1); 33 rst_n = 1; 34 gen_data(); 35 // Add stimulus here 36 37 #(`clk_period*10); 38 $stop; 39 end 40 41 task gen_data; 42 integer i; 43 begin 44 for(i=0;i<8192;i=i + 1)begin 45 @(posedge clk); 46 pi_data = i[7:0]; 47 end 48 end 49 endtask 50 51 endmodule
仿真图
6、异步串口通信
uart:通用异步收发传输器,将将数据在串行通信和并行通信间的传输转换。
RS232:uart的一种。有两根线rx和tx,其中rx为接收线,tx为发送线。每次传输位数为8个1比特(一位一位传输)。空闲状态rx。tx都为高电平,起始位为一个0 而后8个1比特数据而后1比特停止位(高电平1)
tips1:RS232先传低位后传高位。还需要关注波特率(一秒内发送的比特数,如9600Bd,则发送1比特数据时间为1/9600s)
tips2:若上位机通过串口发送数据,会自动在8比特的数据前先发送起始位0,也会自动在末尾加上结束为1
tips3:Rx模块,即接收模块需要对输入信号打两拍消除亚稳态。
6.1 代码编写注意
打拍操作:可以不用复位,可用位拼接进行打拍
1 always@(posedge clk)begin 2 {rx2_reg,rx2,rx1}<={rx2,rx1,rx}; 3 end
串转并赋值代码:串转并每比特数据的赋值可以用case语句,也可以用以为寄存器节约资源
1 always@(posedge clk or negedge rst_n)begin 2 if(rst_n == 1'b0) 3 po_data <= 'h0; 4 else if(bit_cnt>='h1 && bit_flag == 1'b1) 5 po_data <= {rx_rs232_f3,po_data[7:1]}; //避免了用case语句进行赋值,利用移位寄存器可以节约资源 6 7 end
6.3 \$readmemb、\$readmemh使用
参考:verilog中$readmemb和$readmemh的使用
从文本中读取数据到memory,应用于测试文件
常用的两种方式:1、\$readmemb("<数据文件名>",<存储器名>);
2、\$readmemh("<数据文件名>",<存储器名>);
tips:\$readmemh和\$readmemb可以写入不定态x和高阻态z
6.4 ISE中的探针使用
Value代表触发的条件,R上升沿,F下降沿,X不关心。
Window代表可存储的出发次数,比如flag=1出发,则在window为2时可以存储两次flag=1的情况
Depth代表出发存储的采样点数
Position代表触发点的位置,为了观察触发前的一段时间设计的
7、vga时序
参考:vga时序参数文档
参考:vga协议
7.1 vga时序参数
vga时序需要消隐信号,如图表示的是640*480@60HZ的vga,实际显示的640*480只是其中的一部分。可以用计数器表示出实际显示的图像区域。
例如640*480@60 的实际像素个数为800*525(如下图 )
此外vga时序有几种不同的同步信号有效方式,下图为其中一种。行场同步信号都是高有效。
观察上图可知,有效显示的位中间黑色处,当hsync过去后,h_back过去后,h_left过去后(有时有人把h_back和h_left合称为h_back),有效数据开始显示。vsync的显示也是相同的。
tips:需要注意的是,在前路待显示图像数据是没有hsync vsync这类的延时,是连续的数据。因而再要显示的时候要对数据读出进行注意,要在显示有效区域读取图像数据
7.2 vga参数计算
640*480@60HZ需要的时钟周期为 25.175M
$800*525*pix\_clock\_period=1/60$
$pix\_clock\_period = 1/800*525*60 = 1/25\_200\_000$
$f\_pix\_clock = 25\_200\_000 \approx 25.175M$
8、FIFO使用
8.1 FIFO行为介绍
FIFO和ram的异同:FIFO 和 RAM 的共同点在于都能存储数据、都有控制写和读的信号;不同点在于 FIFO 没有地址,所以不能任意指定读取某一个数据,数据只能按照数据输入的顺序输出,即先入先出,并且读写可以同时进行。
FIFO的读写特点:
1、如果数据把 FIFO 的深度写满了,数据将不能再进去,也不会覆盖原有的数据;读 FIFO 的数据也只能读一遍,读完一遍 FIFO 就空了,需要再往 FIFO 写数据才能读出新数据,否则读出的数据一直是最后一次读 FIFO 时的数据。
2、FIFO不同模式的读,最后读数据出现的时间会不一样,标准度出具会相对于读使能打拍延时。first_word_throung 会在读使能的同时进行数据的输出。
赛灵思ise中调用IP核时有的两种读模式:first_word为随读随出,standard为延时一拍出数据。
8.2 双FIFO流水控制
为了实现三行对应像素相加需要用到至少两个FIFO进行数据缓存(当然,用移位寄存器也可以,这里用的是双FIFO)
其基本想法是在第一和第二行,由于没有凑够3行,因而需要进行单纯的向FIFO中进行写入操作,当到第三行时进行将一个FIFO中的数据读出并写到另一个FIFO。
那么此时就可以进行三行数据的加和操作。需要注意的是FIFO读出数据打拍延时时的数据和数据有效位对齐,加标志信号和的产生。其中里面最重要的是数据和相应标志位的对齐。
系统框图如上
fifo_ctrl模块简易波形如下
9、sobel算法
9.1 系统框图
9.2 跨时钟域名,时序对齐,PLL使用
vga时钟25M,系统处理数据时钟为50M时钟不同,这里采用简单的双口ram处理办法,uart的数据传输到ram中,而vga这边是不断的从ram中读出数据,一直是),因而vga中间首先显示的是一个黑色框框,之后随着数据的发送,黑框逐渐有了图像显示。
因为这里涉及到了三行的数据进行加和操作,因而三行数据需要进行对齐,但这里不是严格的对齐,因为uart发送一个数据持续的时间很长,因而我们只要保证三行数据在50M clock的某一个时刻能对齐就行。
在使用PLL时间,系统时钟除了用于驱动PLL,一般不用于驱动其他电路,因为系统时钟可能不能同时驱动PLL和其他电路。
tips:另外,由于经过sobel算法后图像会由200*200减小到198*198,因而在显示的时候要注意坐标的位置。要和里面的黑色方框的长宽配合起来。因为这里设计的是当在黑色运动框里面的时候就对读取然的坐标加一,因而黑色方框的大小要和sobel计算后的图像大小完全一致,否则会造成图像是倾斜的。
10、 flash存储器
参考:不同类别存储器的基本原理
10.1 简单介绍
Flash memory即闪存,属于一种非易失性存储器,掉电情况下数据可以得以保存,它的存取速度较快,但是由于其结构,使得它的存储密度较低。
这里用到的M25p16 最大时钟频率为50M,进行擦除时为12.5M
写前需擦除:flash需要先擦除,然后再写。这是因为flash写数据只能通过将flash内部的1变成0进行实现的。它的擦除比较慢,写入比较快。
而对于ram,向有数据的地址写入,会覆盖旧数据。
芯片内部组织:这里用到的芯片为32个扇区(sector),每个扇区为256页(page),每页为256字节(byte),
10.2 SPI总线
(spi有三线制和四线制,这里用的是四线制)
连线图
同步总线:SPI总线是同步总线,因为他有一条时钟线。而像uart就属于异步总线,因为没有时钟线,所以uart的rx在接收的时候需要打两拍。
10.3 SPI总线时序
两种模式:SPI时序支持两种模式,CPOL=0 CPHA=0模式,和CPOL=1 CPHA=1模式,
思维导图