数字电路设计基础 验证三要素:
灌激励:产生输入信号
集响应:收集输出信号
作比较:比较结果
Testbench送激励最好是下降沿 送激励
FSM
2.1 RAM的设计 一个16x8的双端口RAM
RAM宽度8bit
RAM深度16
ADDR位宽2^4,取值范围0~15
双端口RAM端口列表:
公共:reset
写:w_clk, w_en, w_addr, w_data
读:r_clk, r_en, r_addr, r_data
单端口RAM端口列表:
公共:clk, rst_n, addr, wr_en
写:w_data
读:r_data
2.2 FIFO FIFO的作用: FIFO存储器是系统的缓冲环节,如果没有FIFO存储器,整个系统就不可能正常工作它主要有几方面的功能:
对连续的数据流进行缓存,防止在进机和存储操作时丢失数据;
数据集中起来进行进机和存储,可避免频繁的总线操作,减轻CPU的负担;
允许系统进行DMA操作,提高数据的传输速度。这是至关重要的一点 ,如果不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作
比如FIFO的一端时AD数据采集,另一端时计算机的PCI总线,假设其AD采集的速率为16位100KBPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲 对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的
同步FIFO与异步FIFO:
同步/sync_fifo:写和读都在一个clock下工作
异步/asyn_fifo:写和读不在一个clock下工作
FIFO的原理: FIFO就是对这个双口RAM进行操作。FIFO产生empty和full信号。告诉外面,满和空的状态。满的话就不能在写数据进去了。空的话就不能将数据读出来
FIFO结构图:
FIFO control:作用是产生读写指针和full,empty信号
空满标志的意义:
满信号 有效表示FIFO已经处于满状态,不能再执行写入动作
空信号 有效表示FIFO处于空状态,这时没有有效数据可以读出
正确产生空/满标志是整个FIFO设计的最关键部分,这部分的好坏直接影响着整个FIFO的性能,空/满标志产生的原则是 :当整个FIFO被写满时而不会溢出,当整个么FIFO被读空时而不会多读。之
每进行一次读写操作 ,相应的指针就增加一次,指向下一个位置。当指针移动到最后的位置时,它又重新回到初始位置。
FIFO非空或非满的状态 下,这个过程将随着读写控制信号的变化一直变化下去。如果FIFO处于空状态,再进行一个读出动作会产生向下溢出(underflow ),一个无效的数据被读出;样地,对于一个写满的FIFO如果进行一个写入动作,会产生向上溢出(overflow ),一个有用的数据会被新写入。这会导致先前的数据被覆盖。为了避免这种错误的产生,应该对FIFO设置满和空两个信号
关于溢出与空满的另外一种解释:
空信号: 我们可以想象一下当写信号较慢的时候,读信号较快,那么读信号指针就会追上写信号指针则会产生EMPTY的空信号。或者reset复位时也是空信号。、
满信号: 当写指针快于读指针,写信号较快,很容易使得写指针越过最大深度后追上读指针,那么就会产生满溢出信号。
如何判断是空是满? 可以用不同的方法判断:
是写指针从后面追上了读指针
还是读指针从后面追上了写指针
一种同步FIFO的设计方法: 本文所应用的方法是分别将读/写地址寄存器扩展一位,将最高位设置为状态位,其余低位作为地址位,指针由地址位以及状态位组成 。 首先把读、写状态位全部复位,如果地址循环了奇数次,则状态位置1,偶数次则又重新复位,应用地址位和状态位的结合实现对空、满标志位的控制。当读写指针的地址位和状态位全部吻合的时候,读写指针经历了相同次数的循环移动,也就是说,FIFO处于空状态;如果读写指针的地址位相同而状态位相反,写指针比读指针多循环一次,标志FIFO处于满状态
empty=(w_ptr[n:0 ]==r_ptr[n:0 ]) full=(w_ptr[n-1 :0 ]==r_ptr[n-1 :0 ]&&w_ptr[n]!r_ptr[n])
FIFO MEM FIFO MEM虽然FIFOmem由厂家提供,但我们做仿真的时候必须要把它写出来 所以我们要用语言把它描述出来。让它和实际上的mem功能相同
写和读的时间差: 在真实情况下,写进去的数是不能立即读出来的。按照要求必须延迟两个clock,两个clock以后,数据稳定了才能将它读出来(当然,前面讲的是同一地址下的读写)。而我们logic不会出现这种情况,写进去的数据可以立即读出,当然这是不正确的,所以我们写mem的时候考虑到这点。
读和写的使能: 如果系统reset后,读写使能同时有效,这时RAM输出的数据并不是输入的数据
如果读写地址不同,读写使能同为1时,读不会延迟两个clock。 用这种方法可以处理这种情况
2.3 同步FIFO的设计
FIFO原则:满不能写,空不能读
关键:full和empty信号是如何产生的
方法:
方法1:用长度计数器factor。执行一次写操作,factor加1,执行一次读操作,factor减1
满的时候:factor=depth;空的时候:factor=0
方法2:地址位扩展一位,用最高位来判断空满
空信号:读和写地址相同的时候
满信号:waddr超了一圈且赶上raddr时full
方法一:计数器
原理:
复位状态下空为1
正常工作状态下,空状态判断
就是空:计数器为0、没有在写数据
空往外面读一个 :计数器为1、正在读数据、没有在写数据
原理:
复位状态为0
正常工作状态下,满状态判断
已经是满:计数器全1、没有在读数据
正在变为满:计数器除了最低位全为1、正在写数据、没有在读数据
示例源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 `timescale 1ns/10ps module SYNCFIFO#(parameter DATA_WIDTH = 8 , ADDR_WIDTH = 9 ) (input wire fifo_rst, input wire clock, input wire read_enable, input wire write_enable, input reg [DATA_WIDTH-1 :0 ] write_data, output reg [DATA_WIDTH-1 :0 ] read_data, output reg full, output reg empty, output reg [ADDR_WIDTH-1 :0 ] fcounter ); wire read_allow = (read_enable && !empty); wire write_allow = (write_enable && !full); dp_ram fifo_ram( .write_clock (clock), .read_clock (clock), .write_allow (write_allow), .read_allow (read_allow), .read_addr (read_addr), .write_addr (write_addr), .write_data (write_data), .read_data (read_data) ); always @(posedge clock or posedge fifo_rst) if (fifo_rst) empty <= 'b1 ; else empty <= (!write_enable && (fcounter[8 :1 ] == 8'h0 ) && ((fcounter[0 ] == 0 )||read_enable)); always @(posedge clock or posedge fifo_rst) if (fifo_rst) full <= 'b1 ; else full <= (!read_enable && (fcounter[8 :1 ] == 8'hFF ) && ((fcounter[0 ] == 1 )||write_enable)); always @(posedge clock or posedge fifo_rst) if (fifo_rst) read_addr <= 'h0 ; else if (read_allow) read_addr <= read_addr + 'b1 ; always @(posedge clock or posedge fifo_rst) if (fifo_rst) write_addr <= 'h0 ; else if (write_allow) write_addr <= write_addr + 'b1 ; always @(posedge clock or posedge fifo_rst) if (fifo_rst) fcounter <= 'h0 ; else if ((!read_allow && write_allow)||(read_allow && !write_allow)) begin if (write_allow) fcounter <= fcounter + 'b1 ; else fcounter <= fcounter - 'b1 ; end endmodule
方法二:地址位扩展一位 原理
空信号:读和写地址相同的时候
满信号:waddr超了一圈且赶上raddr时full
示例源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 `timescale 1ns/10ps module sync_fifo( input clk, rst, write_enable, read_enable, input [7 :0 ] data_in, output reg [7 :0 ] data_out, output empty, full ); reg [7 :0 ] mem[15 :0 ]; wire [3 :0 ] w_addr, w_addr; reg [4 :0 ] r_addr_a, w_addr_a; assign r_addr = r_addr_a[3 :0 ]; assign w_addr = w_addr_a[3 :0 ]; always @(posedge clk or negedge rst) begin if (!rst) r_addr_a <= 5'b0 ; else begin if (rd_en == 1 && empty == 0 ) begin data_out <= mem[r_addr]; r_addr_a <= r_addr_a + 1 ; end end end always @(posedge clk or negedge rst) begin if (!rst) w_addr_a <= 5'b0 ; else begin if (wr_en == 1 && full == 0 ) begin mem[w_addr] <= data_in; w_addr_a <= w_addr_a + 1 ; end end end assign empty = (r_addr_a == w_addr_a) ? 1 : 0 ; assign full = (r_addr_a[4 ] != w_addr_a[4 ] && r_addr_a[3 :0 ] == w_addr_a[3 :0 ]) ? 1 : 0 ;endmodule
2.4 异步FIFO的设计(某课网)
使用扩展地址位的方式判断空满
读写信号时钟不同,带来的跨时钟域关键问题:
整体结构
Write_control:控制写操作与满信号(w_full)的判断与产生。
Read_control:控制读操作与空信号(r_empty)的判断与产生。
RAM:双端口数据存取RAM。
Bin_to_gray:二进制码转格雷码模块。用于将读写地址二进制码转成格雷码。
SYN:跨时钟同步模块,即将读地址的格雷码(r_g_addr)向w_clk同步;将写地址的格雷码(w_g_addr)向r_clk同步。主要操作就是通过寄存器打两拍
关键点解释:
跨时钟域传递信号做时钟同步一般通过打两拍。
采用格雷码编码(解决汇聚问题),因为格雷码每次跳转只会有一位发生变化,所以如果出现不确定状态也只会有两种状况,即正确变化了和不变。因此在读写时钟不一样的情况下,纵使读写地址每bit同步过程中出现延时不一致,也不会使得FIFO在实际空或者满之后,FIFO却没有正确的产生出空满信号。只有可能是实际没有空或者满,但产生了空满信号,但这对于FIFO的功能不会有影响,只会使得FIFO的读或者写操作暂停。
读比写时钟更快,只会只出现实际没满,但误判为满;不会对功能(数据流)造成错误。
写比读时钟更快,只会出现实际没空,但误判为空;不会对功能(数据流)造成错误。
示例源码 源码1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 module gray( input [4 :0 ] b_in, output [4 :0 ] g_out );assign g_out[4 ] = b_in[4 ];assign g_out[3 ] = b_in[3 ];assign g_out[2 ] = b_in[3 ]^b_in[2 ];assign g_out[1 ] = b_in[2 ]^b_in[1 ];assign g_out[0 ] = b_in[1 ]^b_in[0 ];endmodule module fifo( input [7 :0 ] data_in, input rclk, wclk, wr_en, rd_en, rst, output reg [7 :0 ] data_out, output reg halffull, output empty, full );reg [7 :0 ] mem[15 :0 ];wire [3 :0 ] w_addr, r_addr; wire [4 :0 ] w_addr_g, r_addr_g; reg [4 :0 ] w_addr_b, r_addr_b; reg [4 :0 ] w_addr_r, r_addr_w; gray g1(.b_in (w_addr_b), .g_out (w_addr_g)); gray g2(.b_in (r_addr_b), .g_out (r_addr_g));assign w_addr = w_addr_b[3 :0 ];assign r_addr = r_addr_b[3 :0 ];always @(posedge rclk or negedge rst)begin if (!rst) begin r_addr_b <= 5'b0 ; r_addr_w <= 5'b0 ; end else begin r_addr_w <= w_addr_g; if (rd_en == 1 && empty == 0 ) begin data_out <= mem[r_addr]; r_addr_b <= r_addr_b + 1 ; end end end always @(posedge wclk or negedge rst)begin if (!rst) begin w_addr_b <= 5'b0 ; w_addr_r <= 5'b0 ; end else begin w_addr_r <= r_addr_g; if (wr_en == 1 && full == 0 ) begin mem[w_addr] <= data_in; w_addr_b <= w_addr_b + 1 ; end end end assign empty = (r_addr_g == r_addr_w) ? 1 : 0 ;assign full = (w_addr_g[4 ] != w_addr_r[4 ] && w_addr_b[3 :0 ] == w_addr_r[3 :0 ] ? 1 : 0 ) ? 1 : 0 ;endmodule
源码2
top module:asyn_fifo.v
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 module asyn_fifo #( parameter DEPTH = 256 , parameter WIDTH_A = 8 , parameter WIDTH_D = 16 )( input w_clk, input rst_n, input w_req, input [WIDTH_D-1 :0 ] w_data, input r_clk, input r_req, output [WIDTH_D-1 :0 ] r_data, output w_full , output r_empty );wire [WIDTH_A:0 ] w_addr;wire [WIDTH_A:0 ] r_gaddr_syn;wire [WIDTH_A:0 ] r_addr;wire [WIDTH_A:0 ] w_gaddr;wire [WIDTH_A:0 ] r_gaddr;wire [WIDTH_A:0 ] w_gaddr_syn; write_part #( .WIDTH_A (WIDTH_A) )write_control( .w_clk ( w_clk ), .w_rst ( rst_n ), .w_req ( w_req ), .r_gaddr ( r_gaddr_syn ), .w_full ( w_full ), .w_addr ( w_addr ), .w_gaddr ( w_gaddr ) ); RAM #( .DEPTH (DEPTH ), .WIDTH_A (WIDTH_A), .WIDTH_D (WIDTH_D) ) RAM_inst ( .r_clk ( r_clk ) , .w_clk ( w_clk ) , .rst_n ( rst_n ) , .w_addr ( w_addr[WIDTH_A-1 :0 ] ) , .w_data ( w_data ) , .w_en ( w_req & (!w_full) ) , .r_addr ( r_addr[WIDTH_A-1 :0 ] ) , .r_en ( r_req & (!r_empty) ) , .r_data ( r_data ) ); syn #( .WIDTH_D (WIDTH_A) ) syn_w_2_r ( .syn_clk ( r_clk ) , .syn_rst ( rst_n ) , .data_in ( w_gaddr ) , .syn_data ( w_gaddr_syn ) ); syn #( .WIDTH_D (WIDTH_A) ) syn_r_2_w ( .syn_clk ( w_clk ) , .syn_rst ( rst_n ) , .data_in ( r_gaddr ), .syn_data ( r_gaddr_syn ) ); read_part #( .WIDTH_A (WIDTH_A) )read_control( .r_clk ( r_clk ), .r_rst ( rst_n ), .r_req ( r_req ), .w_gaddr ( w_gaddr_syn ), .r_empty ( r_empty ), .r_addr ( r_addr ), .r_gaddr ( r_gaddr ) ); endmodule
sub module:bin_to_gray.v RAM.v syn.v read_part.v write_part.v
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module bin_to_gray #( parameter WIDTH_D = 5 )( input [WIDTH_D-1 :0 ] bin_c, output [WIDTH_D-1 :0 ] gray_c ); wire h_b;assign h_b = bin_c[WIDTH_D-1 ]; reg [WIDTH_D-2 :0 ] gray_c_d;integer i; always @( * ) for ( i=0 ;i<WIDTH_D-1 ;i=i+1 ) gray_c_d[i] = bin_c[i]^bin_c[i+1 ]; assign gray_c = {h_b,gray_c_d};endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 module RAM #( parameter DEPTH = 256 , parameter WIDTH_A = 9 , parameter WIDTH_D = 16 )( input r_clk, input w_clk, input rst_n, input [WIDTH_A-1 :0 ] w_addr, input [WIDTH_D-1 :0 ] w_data, input w_en, input [WIDTH_A-1 :0 ] r_addr, input r_en, output reg [WIDTH_D-1 :0 ] r_data );reg [15 :0 ] mem[0 :DEPTH-1 ];integer i;always @( posedge w_clk ) if ( !rst_n ) for (i=0 ;i<DEPTH;i=i+1 ) mem[i] <= 'h0000 ; else if ( w_en ) mem[w_addr] <= w_data; always @( posedge r_clk ) if ( !rst_n ) r_data <= 'h0000 ; else if ( r_en ) r_data <= mem[r_addr]; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 module read_part #( parameter WIDTH_A = 8 )( input r_clk, input r_rst, input r_req, input [WIDTH_A:0 ] w_gaddr, output r_empty , output reg [WIDTH_A:0 ] r_addr , output reg [WIDTH_A:0 ] r_gaddr );always @( posedge r_clk ) if ( !r_rst ) r_addr <= 'h00 ; else if ( r_req&&(!r_empty) ) r_addr <= r_addr + 1'b1 ; wire [WIDTH_A:0 ] r_gaddr_w; bin_to_gray #( .WIDTH_D (WIDTH_A+1 ) ) bin_to_gray_inst ( .bin_c ( r_addr ), .gray_c ( r_gaddr_w ) ); always @( posedge r_clk ) if ( !rst_n ) r_gaddr <= 'h0 ; else r_gaddr <= r_gaddr_w; assign r_empty = (w_gaddr==r_gaddr_w)?1'b1 :1'b0 ; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 module write_part #( parameter WIDTH_A = 8 )( input w_clk, input w_rst, input w_req, input [WIDTH_A:0 ] r_gaddr, output w_full , output reg [WIDTH_A:0 ] w_addr , output reg [WIDTH_A:0 ] w_gaddr ); always @( posedge w_clk ) if ( !w_rst ) w_addr <= 'h00 ; else if ( w_req&&(!w_full) ) w_addr <= w_addr + 1'b1 ;wire [WIDTH_A:0 ] w_gaddr_w; bin_to_gray #( .WIDTH_D (WIDTH_A+1 ) ) bin_to_gray_inst ( .bin_c ( w_addr ), .gray_c ( w_gaddr_w ) ); always @( posedge w_clk ) if ( !rst_n ) w_gaddr <= 'h0 ; else w_gaddr <= w_gaddr_w; assign w_full = ({~w_gaddr_w[WIDTH_A],~w_gaddr_w[WIDTH_A-1 ],w_gaddr_w[WIDTH_A-2 :0 ]}==r_gaddr)?1'b1 :1'b0 ;endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 module syn #( parameter WIDTH_D = 5 ) ( input syn_clk, input syn_rst, input [WIDTH_D:0 ] data_in, output [WIDTH_D:0 ] syn_data );reg [WIDTH_D:0 ] syn_reg_1,syn_reg_2;always @( posedge syn_clk ) if ( !syn_rst ) begin syn_reg_1 <= 'h00 ; syn_reg_2 <= 'h00 ; end else begin syn_reg_1 <= data_in; syn_reg_2 <= syn_reg_1; end assign syn_data = syn_reg_2;endmodule
异步FIFO可能出现的问题 FIFO产生empty和full的信号,读的一侧需要将写的一侧的地址同步过来,进行两侧地址的比较,由于读和写是两个不同的时钟,可能会导致同步过来的地址发生错误。因此地址需要经过格雷码的转换
2.4 异步FIFO(b站) 跨时钟域不能把写指针和读指针直接进行比较
左边写指针、满信号产生
右边读指针、空信号产生
读写指针跨时钟域到对方的信号产生模块中的步骤:
先经过二进制2格雷码转换
再打两拍(为什么打两拍,因为格雷码只有一位不同,打两拍就够了)
到对方时钟域,进行读写/写读指针比较,进而生成空/满比较