Menu Close

以太网工程中双端口RAM的使用

在以太网工程中, 我们使用了很多双端口IP ,用于数据的交换。 双端口在fpga 设计中,会被经常使用。它有几个优点:

  • 隔离不同是时钟域
  • 隔离上下级的行为逻辑
  • 实现带宽转换(利用面积换取速度)
  • 实现乒乓buffer
  • 实现包结构存储(利用地址指针)

隔离不同的时钟域:利用双端口实现不同时钟域的数据交换。 双端口a,b端可以使用不同的时钟来实现不同的数据转换。

隔离上下级的行为逻辑:利用双端口,可以处理不同的上下级行为逻辑, 在对a端操作时,只要专注a 端的行为逻辑, 不需要考虑b端数据的逻辑耦合。 同理在处理b端的行为逻辑时,也不需要考虑a端的行为。

实现带宽转换:利用拴端口例化时的口线宽度不同,实现不用的数据速率。 这种方法是利用更改双端口 口线宽度,利用面积来换取速度。 举例:a,b端都是100M时钟,a端口线为8bit; b端口线为16bit。很显然,16bit的数据吞吐率 是 8bit 数据的2倍。

实现乒乓buffer: 乒乓buffer是在数据通讯中经常使用的一种手段, 利用乒乓buffer 可以合理的利用fpga资源,提高通讯的速度。也就是说在读取数据时, 写数据依然可以正常操作。

实现包结构存储:利用双端口的地址可以自由移动的特性,可以有效的写入或者读取数据。 这一点要比fifo 灵活得多, fifo写入的数据将不能被更改, 同样,fifo 读取时只能顺序读取,不能随机地址读取。双端口正好利用了地址自由移动的特性, 可以有效的写入数据,当数据发生错误时,只要标识这些数据是作废的,就可以重复的使用地址空间了。同样的, 在读取双端口的数据时, 也是可以跳跃的方式读取, 而不象fifo 只能顺序读取数据。

%title插图%num

 

目的:写入数据到双端口,写入的数据包的长度为1- 1500左右(bytes)。利用双端口实现数据的传输和时钟域的转换。这里我们使用乒乓buffer,每个buffer的深度为2048 bytes,所以每个buffer完全可以保证存储一个1500 bytes 的数据包。由于是乒乓buffer,在例化双端口时,双端口的容量为4096 bytes。

使用a,b 端都是8bit的双端口实现数据传输

由于要存储1500bytes的数据, 所以每个buffer的容量为2048bytes,完全可以保证数据存储。 所以在数据存储时,地址永远不会超过1500,(每个地址,存储一个8bit的数据),这样,我们就利用2046, 2047 这两个子地址,作为存储当前包的封口和长度标识。(pkg_flag, length )

乒乓buffer的使用:

%title插图%num

在数据流从a端口写入:

双端口的a 端的地址有乒乓buffer 指针和子地址组成。 addra = {ida,  addra_sub}; 其中ida 为乒乓buffer的指针, addra_sub 为当前乒乓buffer的子地址。

1)ida = 0, 跳转到状态2

2)addra_sub = 2047,  从2047子地址中读取数据, 跳转到状态3

3)如果数据的最高位为1   (data[7]), 表示这个buffer里面有 有效数据, 暂时不能被破坏,一直读取2047 这个子地址里面的数据。 直到

如果数据的最高位为0,标识当前的buffer为空,这个buffer可以用来存储数据,跳转到状态4

4)addra_sub = 0; 地址指针指向 子地址0 , 开始存储(写)数据, 每写1 byte 数据, 地址 加一(addra_sub = addra_sub + 1) 。 如果一个整包全部写入后,跳转到状态5

5)检查写入的这些数据是否都是有效的。 举例: 比如写入一个整包的数据后,发现crc 检验是错误的, 代表整包是不正确的。如果整包的数据都是有效的, 那么将addra_sub = 2046 , 写入数据length 的低8位【7:0】, 在addra_sub = 2047写入 数据length 的【11:8】 位, 同时在2047子地址的最高为写入 1(pkg_flag),表示这个包写入成功,跳转到状态6

6)更改地址到 乒乓buffer 的buffer 1 上,  ida = 1 , 返回状态2

 

在数据流从b端口读出:

双端口的b端的地址有乒乓buffer 指针和子地址组成。 addrb = {idb,  addrb_sub}; 其中idb 为乒乓buffer的指针, addrb_sub 为当前乒乓buffer的子地址。

1)idb = 0, 跳转到状态2

2)addrb_sub = 2047,  从2047子地址中读取数据, 跳转到状态3

3)如果数据的最高位为1   (data_out[7]), 表示这个buffer里面有 有效数据, length【11:8】 = data_out[3:0], 跳转到状态4;

如果数据的最高位为0,标识当前的buffer为空,一直读取数据,直到data_out[7] 为 1

4)addrb_sub = 2046,  从2046子地址中读取数据,length【7:0】 = data_out[7:0],  跳转到状态5

5)addrb_sub = 0; 地址指针指向 子地址0 , 开始读取数据, 每读1 byte 数据, 地址 加一(addrb_sub = addrb_sub + 1) 。 当读取的数据长度等于 length,跳转到状态6

6)addrb_sub = 2047,在这个子地址上回写数据 0(当前buffer 使用结束, 清除 pkg_flag 位), 跳转到状态7

7)更改地址到 乒乓buffer 的buffer 1 上,  idb = 1 , 返回状态2

 

 

使用a,b 端都是16bit的双端口实现数据传输

由于要存储1500bytes的数据, 所以每个buffer的容量为2048bytes,完全可以保证数据存储。 所以在数据存储时,地址永远不会超过1500,(每个地址,存储一个16bit的数据),这样,我们就利用1023 这个子地址,作为存储当前包的封口和长度标识。(pkg_flag, length )

乒乓buffer的使用:

%title插图%num

在数据流从a端口写入:

双端口的a 端的地址有乒乓buffer 指针和子地址组成。 addra = {ida,  addra_sub}; 其中ida 为乒乓buffer的指针, addra_sub 为当前乒乓buffer的子地址。

1)ida = 0, 跳转到状态2

2)addra_sub = 1023,  从1023子地址中读取数据, 跳转到状态3

3)如果数据的最高位为1   (data[15]), 表示这个buffer里面有 有效数据, 暂时不能被破坏,一直读取1023 这个子地址里面的数据。 直到

如果数据的最高位为0,标识当前的buffer为空,这个buffer可以用来存储数据,跳转到状态4

4)addra_sub = 0; 地址指针指向 子地址0 , 开始存储(写)数据, 每写2 byte 数据, 地址 加一(addra_sub = addra_sub + 1) 。 如果一个整包全部写入后,跳转到状态5

5)检查写入的这些数据是否都是有效的。 举例: 比如写入一个整包的数据后,发现crc 检验是错误的, 代表整包是不正确的。如果整包的数据都是有效的, 那么将addra_sub = 1023 , 写入数据length 【11:0】, 同时在1023子地址的数据最高位写入 1(pkg_flag),表示这个包写入成功,跳转到状态6

6)更改地址到 乒乓buffer 的buffer 1 上,  ida = 1 , 返回状态2

 

在数据流从b端口读出:

双端口的b端的地址有乒乓buffer 指针和子地址组成。 addrb = {idb,  addrb_sub}; 其中idb 为乒乓buffer的指针, addrb_sub 为当前乒乓buffer的子地址。

1)idb = 0, 跳转到状态2

2)addrb_sub = 1023,  从2047子地址中读取数据, 跳转到状态3

3)如果数据的最高位为1   (data_out[15]), 表示这个buffer里面有 有效数据, length【11:0】 = data_out[11:0], 跳转到状态4;

如果数据的最高位为0,标识当前的buffer为空,一直读取数据,直到data_out[15] 为 1

4)addrb_sub = 0; 地址指针指向 子地址0 , 开始读取数据, 每读2 byte 数据, 地址 加一(addrb_sub = addrb_sub + 1) 。 当读取的数据长度等于 length,跳转到状态5

5)addrb_sub = 1023,在这个子地址上回写数据 0(当前buffer 使用结束, 清除 pkg_flag 位), 跳转到状态6

6)更改地址到 乒乓buffer 的buffer 1 上,  idb = 1 , 返回状态2

 

 

 

使用a端都是8bit,b 端是16bit的双端口实现数据传输

由于要存储1500bytes的数据, 所以每个buffer的容量为2048bytes,完全可以保证数据存储。 所以在数据存储时,地址永远不会超过1500,(a端口,每个地址,可以写入8bit的数据;b端口,每个地址可以读出16bit ),这样,我们就利用2046, 2047 这两个子地址,作为存储(写)当前包的封口和长度标识。(pkg_flag, length )

乒乓buffer的使用:

%title插图%num

在数据流从a端口写入:

双端口的a 端的地址有乒乓buffer 指针和子地址组成。 addra = {ida,  addra_sub}; 其中ida 为乒乓buffer的指针, addra_sub 为当前乒乓buffer的子地址。

1)ida = 0, 跳转到状态2

2)addra_sub = 2047,  从2047子地址中读取数据, 跳转到状态3

3)如果数据的最高位为1   (data[7]), 表示这个buffer里面有 有效数据, 暂时不能被破坏,一直读取2047 这个子地址里面的数据。 直到

如果数据的最高位为0,标识当前的buffer为空,这个buffer可以用来存储数据,跳转到状态4

4)addra_sub = 0; 地址指针指向 子地址0 , 开始存储(写)数据, 每写1 byte 数据, 地址 加一(addra_sub = addra_sub + 1) 。 如果一个整包全部写入后,跳转到状态5

5)检查写入的这些数据是否都是有效的。 举例: 比如写入一个整包的数据后,发现crc 检验是错误的, 代表整包是不正确的。如果整包的数据都是有效的, 那么将addra_sub = 2046 , 写入数据length 的低8位【7:0】, 在addra_sub = 2047写入 数据length 的【11:8】 位, 同时在2047子地址的最高为写入 1(pkg_flag),表示这个包写入成功,跳转到状态6

6)更改地址到 乒乓buffer 的buffer 1 上,  ida = 1 , 返回状态2

 

在数据流从b端口读出:

双端口的b端的地址有乒乓buffer 指针和子地址组成。 addrb = {idb,  addrb_sub}; 其中idb 为乒乓buffer的指针, addrb_sub 为当前乒乓buffer的子地址。

1)idb = 0, 跳转到状态2

2)addrb_sub = 1023,  从2047子地址中读取数据, 跳转到状态3

3)如果数据的最高位为1   (data_out[15]), 表示这个buffer里面有 有效数据, length【11:0】 = data_out[11:0], 跳转到状态4;

如果数据的最高位为0,标识当前的buffer为空,一直读取数据,直到data_out[15] 为 1

4)addrb_sub = 0; 地址指针指向 子地址0 , 开始读取数据, 每读2 byte 数据, 地址 加一(addrb_sub = addrb_sub + 1) 。 当读取的数据长度等于 length,跳转到状态5

5)addrb_sub = 1023,在这个子地址上回写数据 0(当前buffer 使用结束, 清除 pkg_flag 位), 跳转到状态6

6)更改地址到 乒乓buffer 的buffer 1 上,  idb = 1 , 返回状态2

 

 

从上面的3个例子可以看出, 写入端(a 端)写入数据时,并不需要关系b端的情况; 同时 读出端(b端)读取数据时,也不关系a端的写入情况。 即 a,b端都是可以独立工作的,从而实现行为上的分离; 同时 a,b端口的时钟是可以不同的, 这样就实现的不同时钟域之间的数据转换。

Posted in FPGA, FPGA, Verilog, Verilog, 教材与教案, 文章

发表评论

相关链接