数字电路设计基础

数字电路设计基础

验证三要素:

  • 灌激励:产生输入信号
  • 集响应:收集输出信号
  • 作比较:比较结果

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的模块接口

FIFO内部结构图

  • FIFO control:作用是产生读写指针和full,empty信号

FIFO Control内部结构:

空满标志的意义:

  • 满信号有效表示FIFO已经处于满状态,不能再执行写入动作
    • 满信号复位表示FIFO非满,还可以执行写入动作
  • 空信号有效表示FIFO处于空状态,这时没有有效数据可以读出
    • 空信号复位说明FIFO非空,这时可以进行读操作
  • 正确产生空/满标志是整个FIFO设计的最关键部分,这部分的好坏直接影响着整个FIFO的性能,空/满标志产生的原则是:当整个FIFO被写满时而不会溢出,当整个么FIFO被读空时而不会多读。之
  • 每进行一次读写操作,相应的指针就增加一次,指向下一个位置。当指针移动到最后的位置时,它又重新回到初始位置。
  • FIFO非空或非满的状态下,这个过程将随着读写控制信号的变化一直变化下去。如果FIFO处于空状态,再进行一个读出动作会产生向下溢出(underflow),一个无效的数据被读出;样地,对于一个写满的FIFO如果进行一个写入动作,会产生向上溢出(overflow),一个有用的数据会被新写入。这会导致先前的数据被覆盖。为了避免这种错误的产生,应该对FIFO设置满和空两个信号

关于溢出与空满的另外一种解释:

空信号:我们可以想象一下当写信号较慢的时候,读信号较快,那么读信号指针就会追上写信号指针则会产生EMPTY的空信号。或者reset复位时也是空信号。、

满信号:当写指针快于读指针,写信号较快,很容易使得写指针越过最大深度后追上读指针,那么就会产生满溢出信号。

空满信号的简单示意图

如何判断是空是满?
可以用不同的方法判断:

  • 是写指针从后面追上了读指针
  • 还是读指针从后面追上了写指针

一种同步FIFO的设计方法:
本文所应用的方法是分别将读/写地址寄存器扩展一位,将最高位设置为状态位,其余低位作为地址位,指针由地址位以及状态位组成
首先把读、写状态位全部复位,如果地址循环了奇数次,则状态位置1,偶数次则又重新复位,应用地址位和状态位的结合实现对空、满标志位的控制。当读写指针的地址位和状态位全部吻合的时候,读写指针经历了相同次数的循环移动,也就是说,FIFO处于空状态;如果读写指针的地址位相同而状态位相反,写指针比读指针多循环一次,标志FIFO处于满状态

1
2
3
//可以使用下面的公式来判断空满状态:
empty=(w_ptr[n:0]==r_ptr[n:0])
full=(w_ptr[n-10]==r_ptr[n-10]&&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
        • 错误想法:w_addr=depth

方法一:计数器

FIFO接口

接口类型

内部信号

①例化一个512x8的双端RAM

②空信号产生

  • 原理:

    • 使用计数器:读写计数器为0时
  • 复位状态下空为1

  • 正常工作状态下,空状态判断
    • 就是空:计数器为0、没有在写数据
    • 空往外面读一个 :计数器为1、正在读数据、没有在写数据

③满信号产生

  • 原理:

    • 使用计数器:统计读写了多少个
  • 复位状态为0

  • 正常工作状态下,满状态判断
    • 已经是满:计数器全1、没有在读数据
    • 正在变为满:计数器除了最低位全为1、正在写数据、没有在读数据

④产生读地址与写地址

  • 读地址:
    • 每一个周期加一次,

⑤计数器

  • 读写使能其中一个为高时
    • 写使能为高+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)
);

/*
* 1. 判断fifo的空满
*/
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));

/*
* 2. 产生读写地址
*/
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;

/*
* 3. 读写计数(写:计数器加1;读:计数器减1)
*/
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
    • 错误想法:w_addr=depth

示例源码:

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
);
/* 数据宽度为8bit,深度为16的RAM */
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; //读地址加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; //写地址加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

/* 异步FIFO */
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 //满信号
);

//RAM存储器
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; //读地址+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; //读地址+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
);

//reg [4:0] w_addr;
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站)

跨时钟域不能把写指针和读指针直接进行比较

异步FIFO内部结构图1

  • 左边写指针、满信号产生
  • 右边读指针、空信号产生
  • 读写指针跨时钟域到对方的信号产生模块中的步骤:
    • 先经过二进制2格雷码转换
    • 再打两拍(为什么打两拍,因为格雷码只有一位不同,打两拍就够了)
    • 到对方时钟域,进行读写/写读指针比较,进而生成空/满比较

异步FIFO内部结构图2


94219763_p0


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!