Asynchronous FIFO and synchronous FIFO-翻译自外网

news2024/11/17 0:03:19

Synchronous FIFO

先进先出 (FIFO) 是一种非常流行且有用的设计块,用于模块之间的同步和握手机制。

FIFO 的深度: FIFO 中的槽数或行数称为 FIFO 的深度。

FIFO 的宽度:每个槽或行中可以存储的位数称为 FIFO 的宽度。

在同步 FIFO 中,数据读取和写入操作使用相同的时钟频率。通常,它们与高时钟频率一起使用以支持高速系统。

synchronous fifo

Signals:

wr_en: 写使能

wr_data: 写数据write data

full: FIFO is full 满

empty: FIFO is empty 空

rd_en: 读使能 read enable

rd_data: 读数据read data

w_ptr: 写指针write pointer

r_ptr: 读指针 read pointer

FIFO 可以根据 wr_en 信号在时钟的每个 posege 存储/写入 wr_data,直到满为止。每次将数据写入 FIFO 存储器时,写入指针都会递增。

根据rd_en信号,可以在时钟的每个周期从FIFO中取出或读取数据,直到为空为止。每次从 FIFO 存储器读取数据时,读指针都会递增。

method
empty condition

w_ptr == r_ptr 即写指针和读指针具有相同的值。

full condition

满的情况意味着 FIFO 中的每个槽都被占用,但是 w_ptr 和 r_ptr 将再次具有相同的值。因此,无法确定是满状态还是空状态。因此,故意将 FIFO 的最后一个槽保持为空,满条件可以写为 (w_ptr+1’b1) == r_ptr)

code
module synchronous_fifo #(parameter DEPTH=8, DATA_WIDTH=8) (
  input clk, rst_n,
  input w_en, r_en,
  input [DATA_WIDTH-1:0] data_in,
  output reg [DATA_WIDTH-1:0] data_out,
  output full, empty
);
  
  reg [$clog2(DEPTH)-1:0] w_ptr, r_ptr;
  reg [DATA_WIDTH-1:0] fifo[DEPTH];
  
  // Set Default values on reset.
  always@(posedge clk) begin
    if(!rst_n) begin
      w_ptr <= 0; r_ptr <= 0;
      data_out <= 0;
    end
  end
  
  // To write data to FIFO
  always@(posedge clk) begin
    if(w_en & !full)begin
      fifo[w_ptr] <= data_in;
      w_ptr <= w_ptr + 1;
    end
  end
  
  // To read data from FIFO
  always@(posedge clk) begin
    if(r_en & !empty) begin
      data_out <= fifo[r_ptr];
      r_ptr <= r_ptr + 1;
    end
  end
  
  assign full = ((w_ptr+1'b1) == r_ptr);
  assign empty = (w_ptr == r_ptr);
endmodule
testbench
module sync_fifo_TB;
  parameter DATA_WIDTH = 8;
  
  reg clk, rst_n;
  reg w_en, r_en;
  reg [DATA_WIDTH-1:0] data_in;
  wire [DATA_WIDTH-1:0] data_out;
  wire full, empty;
  
  // Queue to push data_in
  reg [DATA_WIDTH-1:0] wdata_q[$], wdata;

  synchronous_fifo s_fifo(clk, rst_n, w_en, r_en, data_in, data_out, full, empty);

  always #5ns clk = ~clk;
  
  initial begin
    clk = 1'b0; rst_n = 1'b0;
    w_en = 1'b0;
    data_in = 0;
    
    repeat(10) @(posedge clk);
    rst_n = 1'b1;

    repeat(2) begin
      for (int i=0; i<30; i++) begin
        @(posedge clk);
        w_en = (i%2 == 0)? 1'b1 : 1'b0;
        if (w_en & !full) begin
          data_in = $urandom;
          wdata_q.push_back(data_in);
        end
      end
      #50;
    end
  end

  initial begin
    clk = 1'b0; rst_n = 1'b0;
    r_en = 1'b0;

    repeat(20) @(posedge clk);
    rst_n = 1'b1;

    repeat(2) begin
      for (int i=0; i<30; i++) begin
        @(posedge clk);
        r_en = (i%2 == 0)? 1'b1 : 1'b0;
        if (r_en & !empty) begin
          #1;
          wdata = wdata_q.pop_front();
          if(data_out !== wdata) $error("Time = %0t: Comparison Failed: expected wr_data = %h, rd_data = %h", $time, wdata, data_out);
          else $display("Time = %0t: Comparison Passed: wr_data = %h and rd_data = %h",$time, wdata, data_out);
        end
      end
      #50;
    end

    $finish;
  end
  
  initial begin 
    $dumpfile("dump.vcd"); $dumpvars;
  end
endmodule

method2
Empty condition

count == 0 i.e. FIFO contains nothing.

Full condition

count == FIFO_DEPTH i.e. counter value has reached till the depth of FIFO

method3
Empty condition

w_ptr == r_ptr i.e. write and read pointers has the same value. MSB of w_ptr and r_ptr also has the same value.

Full condition

w_ptr == r_ptr i.e. write and read pointers has the same value, but the MSB of w_ptr and r_ptr differs.

Asynchronous FIFO

在异步 FIFO 中,数据读取和写入操作使用不同的时钟频率。由于写入和读取时钟不同步,因此称为异步 FIFO。通常,这些用于数据需要从一个时钟域传递到另一个时钟域的系统,这通常被称为“时钟域交叉”。因此,异步 FIFO 有助于同步工作在不同时钟上的两个系统之间的数据流。

asynchronous fifo usage

Gray code

在这里插入图片描述

Asynchronous FIFO Block Diagram

asynchronous fifo

Signals:

wr_en: 写使能write enable

wr_data: 写数据write data

full: FIFO is full满

empty: FIFO is empty 空

rd_en: read enable 读使能

rd_data: read data 读数据

b_wptr: binary write pointer 二进制写指针

g_wptr: gray write pointer 格雷码写指针

b_wptr_next: binary write pointer next 指示用于二进制写入新数据的下一个可用地址。

g_wptr_next: gray write pointer next 指示用于格雷码写入新数据的下一个可用地址。

b_rptr: binary read pointer 二进制读指针

g_rptr: gray read pointer 格雷码读指针

b_rptr_next: binary read pointer next 指示用于二进制读新数据的下一个可用地址。

g_rptr_next: gray read pointer next 指示用于格雷码读入新数据的下一个可用地址。

b_rptr_sync: binary read pointer synchronized

b_wptr_sync: binary write pointer synchronized

同步必要性:

  • 异步 FIFO:它们具有独立的读写时钟,通常以不同的频率运行。
  • 时钟域交叉 (CDC):这些域之间的数据传输带来了同步挑战。
  • 亚稳态:跨时钟域的信号可能进入亚稳态(不确定值),可能导致数据损坏。
Asynchronous FIFO Operation

在同步 FIFO 的情况下,写入和读取指针在同一时钟上生成。然而,在异步 FIFO 的情况下,写指针与写时钟域对齐,而读指针与读时钟域对齐。因此,需要跨域来计算 FIFO 满和空的情况。这会导致实际设计中的亚稳态。为了解决这种亚稳态问题,可以使用2个触发器或3个触发器同步器来传递写入和读取指针。为了便于解释,我们将使用 2 个触发器同步器。请注意,单个“2 FF 同步器”只能解决一位的亚稳态问题。因此,根据写入和读取指针,需要多个 2FF 同步器。

img

module synchronizer #(parameter WIDTH=3) (input clk, rst_n, [WIDTH:0] d_in, output reg [WIDTH:0] d_out);
  reg [WIDTH:0] q1;
  always@(posedge clk) begin
    if(!rst_n) begin
      q1 <= 0;
      d_out <= 0;
    end
    else begin
      q1 <= d_in;
      d_out <= q1;
    end
  end
endmodule
Usage of Binary to Gray code converter and vice-versa in Asynchronous FIFO

到目前为止,我们讨论了如何在各个时钟域中获取异步写入和读取指针。但是,我们不应该传递二进制格式的写入和读取指针值。由于亚稳态,整体写入或读取指针值可能不同。

示例:当写时钟域的二进制值 wr_ptr = 4’b1101 通过 2FF 同步器传输时,读时钟域的 wr_ptr 值可能会接收为 4’b1111 或任何其他不可接受的值。而格雷码则保证与之前的值相比只有一位变化。因此,写指针和读指针都需要首先转换为其相应域中的等效格雷码,然后将它们传递到相反的域。要检查另一个域中 FIFO 满和空的情况,我们有两种方法。

Way 1

将接收到的格雷码格式的指针转换为二进制格式,然后检查满和空的情况。

FIFO full condition
g2b_converter g2b_wr(g_rptr_sync, b_rptr_sync);
wrap_around = b_rptr_sync[PTR_WIDTH] ^ b_wptr[PTR_WIDTH];
wfull = wrap_around & (b_wptr[PTR_WIDTH-1:0] == b_rptr_sync[PTR_WIDTH-1:0]);
FIFO empty condition
g2b_converter g2b_rd(g_wptr_sync, b_wptr_sync);
rempty = (b_wptr_sync == b_rptr_next);
Way 2

借助接收到的格雷码写入和读取指针直接检查满和空状况。这是高效的,因为它不需要额外的硬件来将格雷码写入和读取指针转换为等效的二进制形式。

FIFO full condition
wfull = (g_wptr_next == {~g_rptr_sync[PTR_WIDTH:PTR_WIDTH-1], g_rptr_sync[PTR_WIDTH-2:0]});
FIFO empty condition
rempty = (g_wptr_sync == g_rptr_next);
Asynchronous FIFO Verilog Code

同步器 g_rptr_sync 的输出作为“写指针处理程序”模块的输入,用于生成 FIFO 满状态。如果二进制写指针 (b_wptr) 满足 (w_en & !full) 条件,则它会递增。该 b_wptr 值被馈送到 fifo_mem 模块以将数据写入 FIFO。

Write Pointer Handler
module wptr_handler #(parameter PTR_WIDTH=3) (
  input wclk, wrst_n, w_en,
  input [PTR_WIDTH:0] g_rptr_sync,
  output reg [PTR_WIDTH:0] b_wptr, g_wptr,
  output reg full
);

  reg [PTR_WIDTH:0] b_wptr_next;
  reg [PTR_WIDTH:0] g_wptr_next;
   
  reg wrap_around;
  wire wfull;
  
  assign b_wptr_next = b_wptr+(w_en & !full);
  assign g_wptr_next = (b_wptr_next >>1)^b_wptr_next;
  
  always@(posedge wclk or negedge wrst_n) begin
    if(!wrst_n) begin
      b_wptr <= 0; // set default value
      g_wptr <= 0;
    end
    else begin
      b_wptr <= b_wptr_next; // incr binary write pointer
      g_wptr <= g_wptr_next; // incr gray write pointer
    end
  end
  
  always@(posedge wclk or negedge wrst_n) begin
    if(!wrst_n) full <= 0;
    else        full <= wfull;
  end

  assign wfull = (g_wptr_next == {~g_rptr_sync[PTR_WIDTH:PTR_WIDTH-1], g_rptr_sync[PTR_WIDTH-2:0]});

endmodule
Read Pointer Handler

同步器 g_wptr_sync 的输出作为“读指针处理程序”模块的输入,以生成 FIFO 空条件。如果二进制读指针 (b_rptr) 满足 (r_en & !empty) 条件,则它会递增。该 b_rptr 值被馈送到 fifo_mem 模块以从 FIFO 读取数据。

module rptr_handler #(parameter PTR_WIDTH=3) (
  input rclk, rrst_n, r_en,
  input [PTR_WIDTH:0] g_wptr_sync,
  output reg [PTR_WIDTH:0] b_rptr, g_rptr,
  output reg empty
);

  reg [PTR_WIDTH:0] b_rptr_next;
  reg [PTR_WIDTH:0] g_rptr_next;

  assign b_rptr_next = b_rptr+(r_en & !empty);
  assign g_rptr_next = (b_rptr_next >>1)^b_rptr_next;
  assign rempty = (g_wptr_sync == g_rptr_next);
  
  always@(posedge rclk or negedge rrst_n) begin
    if(!rrst_n) begin
      b_rptr <= 0;
      g_rptr <= 0;
    end
    else begin
      b_rptr <= b_rptr_next;
      g_rptr <= g_rptr_next;
    end
  end
  
  always@(posedge rclk or negedge rrst_n) begin
    if(!rrst_n) empty <= 1;
    else        empty <= rempty;
  end
endmodule
FIFO Memory

基于二进制编码的写和读指针,数据分别写入 FIFO 或从 FIFO 读取。

module fifo_mem #(parameter DEPTH=8, DATA_WIDTH=8, PTR_WIDTH=3) (
  input wclk, w_en, rclk, r_en,
  input [PTR_WIDTH:0] b_wptr, b_rptr,
  input [DATA_WIDTH-1:0] data_in,
  input full, empty,
  output reg [DATA_WIDTH-1:0] data_out
);
  reg [DATA_WIDTH-1:0] fifo[0:DEPTH-1];
  
  always@(posedge wclk) begin
    if(w_en & !full) begin
      fifo[b_wptr[PTR_WIDTH-1:0]] <= data_in;
    end
  end
  /*
  always@(posedge rclk) begin
    if(r_en & !empty) begin
      data_out <= fifo[b_rptr[PTR_WIDTH-1:0]];
    end
  end
  */
  assign data_out = fifo[b_rptr[PTR_WIDTH-1:0]];
endmodule
Top Module
`include "synchronizer.v"
`include "wptr_handler.v"
`include "rptr_handler.v"
`include "fifo_mem.v"

module asynchronous_fifo #(parameter DEPTH=8, DATA_WIDTH=8) (
  input wclk, wrst_n,
  input rclk, rrst_n,
  input w_en, r_en,
  input [DATA_WIDTH-1:0] data_in,
  output reg [DATA_WIDTH-1:0] data_out,
  output reg full, empty
);
  
  parameter PTR_WIDTH = $clog2(DEPTH);
 
  reg [PTR_WIDTH:0] g_wptr_sync, g_rptr_sync;
  reg [PTR_WIDTH:0] b_wptr, b_rptr;
  reg [PTR_WIDTH:0] g_wptr, g_rptr;

  wire [PTR_WIDTH-1:0] waddr, raddr;

  synchronizer #(PTR_WIDTH) sync_wptr (rclk, rrst_n, g_wptr, g_wptr_sync); //write pointer to read clock domain
  synchronizer #(PTR_WIDTH) sync_rptr (wclk, wrst_n, g_rptr, g_rptr_sync); //read pointer to write clock domain 
  
  wptr_handler #(PTR_WIDTH) wptr_h(wclk, wrst_n, w_en,g_rptr_sync,b_wptr,g_wptr,full);
  rptr_handler #(PTR_WIDTH) rptr_h(rclk, rrst_n, r_en,g_wptr_sync,b_rptr,g_rptr,empty);
  fifo_mem fifom(wclk, w_en, rclk, r_en,b_wptr, b_rptr, data_in,full,empty, data_out);

endmodule
Testbench Code
module async_fifo_TB;

  parameter DATA_WIDTH = 8;

  wire [DATA_WIDTH-1:0] data_out;
  wire full;
  wire empty;
  reg [DATA_WIDTH-1:0] data_in;
  reg w_en, wclk, wrst_n;
  reg r_en, rclk, rrst_n;

  // Queue to push data_in
  reg [DATA_WIDTH-1:0] wdata_q[$], wdata;

  asynchronous_fifo as_fifo (wclk, wrst_n,rclk, rrst_n,w_en,r_en,data_in,data_out,full,empty);

  always #10ns wclk = ~wclk;
  always #35ns rclk = ~rclk;
  
  initial begin
    wclk = 1'b0; wrst_n = 1'b0;
    w_en = 1'b0;
    data_in = 0;
    
    repeat(10) @(posedge wclk);
    wrst_n = 1'b1;

    repeat(2) begin
      for (int i=0; i<30; i++) begin
        @(posedge wclk iff !full);
        w_en = (i%2 == 0)? 1'b1 : 1'b0;
        if (w_en) begin
          data_in = $urandom;
          wdata_q.push_back(data_in);
        end
      end
      #50;
    end
  end

  initial begin
    rclk = 1'b0; rrst_n = 1'b0;
    r_en = 1'b0;

    repeat(20) @(posedge rclk);
    rrst_n = 1'b1;

    repeat(2) begin
      for (int i=0; i<30; i++) begin
        @(posedge rclk iff !empty);
        r_en = (i%2 == 0)? 1'b1 : 1'b0;
        if (r_en) begin
          wdata = wdata_q.pop_front();
          if(data_out !== wdata) $error("Time = %0t: Comparison Failed: expected wr_data = %h, rd_data = %h", $time, wdata, data_out);
          else $display("Time = %0t: Comparison Passed: wr_data = %h and rd_data = %h",$time, wdata, data_out);
        end
      end
      #50;
    end

    $finish;
  end
  
  initial begin 
    $dumpfile("dump.vcd"); $dumpvars;
  end
endmodule

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1385498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大屏项目:react中实现3d效果的环形图包括指引线

参考链接3d环形图 3d效果的环形图 项目需求实现方式指引线&#xff08;线的样式字体颜色&#xff09; 项目需求 需要在大屏上实现一个3d的环形图&#xff0c;并且自带指引线&#xff0c;指引线的颜色和每段数据的颜色一样&#xff0c;文本内容变成白色&#xff0c;数字内容变…

投标文件组成分析

标注组成&#xff1a; 标书内容&#xff1a; 一、前面内容 1、投标首页&#xff08;正本/副本&#xff09;/ 2、评分索引表&#xff08;来源于“招标文件的评分细则表”&#xff0c;一般在原有的“评分细则表”的后面添加一列“所在页码”&#xff0c;里面填写得分对应“页…

CentOS stream 9最小化安装说明

Server with GUI&#xff1a;An integrated , easy-to-manage server with a graphical interface. 带有图形用户界面的服务器&#xff1a;集成、易于管理的服务器&#xff0c;带有图形界面。Server&#xff1a;An integrated , easy-to-manage server. 服务器&#xff1a;集成…

服务器 conda update 失败解决方法

1. 强制 conda update 租借一台服务器&#xff0c;发现 conda 版本是4.10.3&#xff0c;需要升级&#xff0c;使用了如下命令都没有效果&#xff0c;仍然是一样的版本 conda update conda update --all conda update -n base -c defaults conda最后强制用conda-forge通道更新…

工作再忙,这个门禁管理小技巧也要学!

在现代社会&#xff0c;随着城市化和科技的迅猛发展&#xff0c;安全管理已经成为社会的一项首要任务。在这个背景下&#xff0c;门禁监控系统作为一种高效、先进的安全管理工具&#xff0c;逐渐成为各类场所、机构和企业的不可或缺的一部分。 客户案例 医疗机构 湖南某大型医…

2.IHRM人力资源 - 登录

一、登录页结构与表单开发 我们要实现的登录界面 目前的登录界面 1.1 登录页结构 复制下面的代码到views/login/index.vue页面下 <template><div class"login-container"><div class"logo"/><div class"form"><h1&…

【计算机二级考试C语言】C常量

C 常量 常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。 常量可以是任何的基本数据类型&#xff0c;比如整数常量、浮点常量、字符常量&#xff0c;或字符串字面值&#xff0c;也有枚举常量。 常量就像是常规的变量&#xff0c;只…

反序列化提升刷题

例题&#xff1a; <?php //flag is in flag.php highlight_file(__FILE__); error_reporting(0); class Modifier { private $var; public function append($value) { include($value); echo $flag; } public function __invoke(){ …

Vue中keep-alive缓存的详解(深度理解)

文章目录 一、Keep-alive 是什么二、使用场景三、原理分析四、思考题&#xff1a;缓存后如何获取数据beforeRouteEnteractived 参考文献 一、Keep-alive 是什么 keep-alive是vue中的内置组件&#xff0c;能在组件切换过程中将状态保留在内存中&#xff0c;防止重复渲染DOM ke…

吉祥物如何解锁虚拟主持人身份,赋能品牌营销?

在互联网突破时空的整体语境下&#xff0c;一个吉祥物可以解锁虚拟主持人身份&#xff0c;结合动作捕捉技术&#xff0c;活跃于品牌线上线下营销活动场景&#xff0c;让吉祥物虚拟主持人凭借其“萌”、的特征&#xff0c;带给用户亲近感&#xff0c;快速拉近品牌与用户的距离&a…

对Transformer的理解。

要理解Transformer&#xff0c;需要先理解注意力机制&#xff0c;下面大部分内容来自台大教授李宏毅老师讲课资料。 注意力机制 之前使用的MLP&#xff0c;CNN&#xff0c;RNN模型可以解决一些简单序列问题&#xff0c;但当序列长度太长容易失去效果&#xff0c;原因是看了新…

怎样获取power shell 的全部可用命令?2/5(篇幅有点长,分成5份)

在power shell 窗口中&#xff0c;有一个获取全部可用命令的命令&#xff1a;get-command&#xff0c;获取到的命令有1640多个&#xff0c;够学习了吧&#xff1f;那么&#xff0c;power shell 命令有哪些类别呢&#xff1f; PowerShell命令可以分为以下几类&#xff1a; Cmdl…

统一存储双控NAS同步备份应用方案

随着业务量的增加&#xff0c;企业必须找到一种有效的解决方案保护数据安全&#xff0c;防止不可预测的存储系统故障。传统的数据备份往往是专用的数据格式&#xff0c;不能保留完整的用户目录信息。因此&#xff0c;IT 人员必须在数据恢复后重新配置才可重新恢复业务。为了解决…

Java零基础教学文档servlet(1)

【Web开发和HTTP协议】 1. Web开发概述 1.1 web概述 万维网&#xff08;英语&#xff1a;World Wide Web&#xff09;亦作WWW、Web、全球广域网&#xff0c;是一个透过互联网访问的&#xff0c;由许多互相链接的超文本组成的信息系统。英国科学家蒂姆伯纳斯-李于1989年发明了…

推荐一款通过ssh连接linux服务的开源工具WindTerm

文章目录 前言WindTerm介绍WindTerm使用主密码和锁屏总结 前言 工作一入门便是游戏服务器开发&#xff0c;所以常常有连接Linux服务器的需求&#xff0c;之前用的最多的是Xshell&#xff0c;最近这个软件个人版只能免费使用一个月了&#xff0c;超过时间会提示更新无法正常使用…

FX110网:保加利亚外汇牌照FSC,进入欧盟市场的另一扇大门

汇市经纪商们对欧盟金融工具市场法规&#xff08;MiFID&#xff09;都不会陌生。通过MiFID体系&#xff0c;欧盟所有国家的监管机构连成了一张网络。因此&#xff0c;任何想要进入欧洲市场的汇商&#xff0c;都希望获得加入MiFID监管机构的牌照&#xff0c;其中最受欢迎的莫过于…

什么是充放电振子理论?

CHAT回复&#xff1a;充放电振子模型&#xff08;Charging-Reversal Oscillator Model&#xff09;是一种解释ENSO现象的理论模型&#xff0c;这个模型把ENSO现象比喻成一个“热力学振荡系统”。 在这个模型中&#xff0c;ENSO现象由三个组成部分&#xff1a;充电&#xff08;C…

web开发学习笔记(2.js)

1.引入 2.js的两种引入方式 3.输出语句 4.全等运算符 5.定义函数 6.数组 7.数组属性 8.字符串对象的对应方法 9.自定义对象 10.json对象 11.bom属性 12.window属性 13.定时刷新时间 14.跳转网址 15.DOM文档对象模型 16.获取DOM对象&#xff0c;根据DOM对象来操作网页 如下图…

JavaScript采集各大电商平台关于预制菜酸菜鱼销售量

因为我喜欢吃酸菜鱼&#xff0c;但是自己弄又弄不来&#xff0c;想从网上找找看看&#xff0c;但是种类多&#xff0c;自己逐个选择又太浪费时间。索性利用自己专业爬虫知识&#xff0c;一边检验我最近代码水平&#xff0c;另一方面还能选择到满意的年货。过去写个各大平台预制…

YOLOv8 Ultralytics:使用Ultralytics框架进行FastSAM图像分割

YOLOv8 Ultralytics&#xff1a;使用Ultralytics框架进行FastSAM图像分割 前言相关介绍前提条件实验环境安装环境项目地址LinuxWindows 使用Ultralytics框架进行FastSAM图像分割参考文献 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容…