天天看點

chisel 仲裁器Arbiter和隊列Queue(ready-valid接口)

一、ready-valid接口

Arbiter

Queue

都使用了

ready-valid

接口,該類型的端口在單一資料信号的基礎上又添加了ready和valid信号以使用

ready-valid

握手協定。它包含3個信号:

  • ready:高有效時表示資料接收者

    consumer

    已經準備好接收信号,由consumer驅動。
  • valid:高有效時表示資料生産者

    producer

    已經準備好待發送的資料了,由producer驅動。
  • bits:是要在producer與consumer之間傳輸的資料。

需要注意的是,valid和ready信号之間不能存在組合邏輯關系,valid信号應該隻依賴于此時的源資料是否有效,ready信号應該隻依賴于此時的資料接收者是否準備好接收資料了。當在某個時鐘周期,valid和ready同時有效時,資料被視為傳輸。

建立ready-valid接口很簡單,使用單例對象Decoupled即可建立,有以下兩種形式:

  • Decoupled(...)

    :可以傳入任意的資料類型,然後傳回一個ready-valid接口,此時ready是input信号,valid和bits都是output信号。是以它是屬于資料生産者producer的端口。
  • Flipped(Decoupled(...))

    :Flipped()會将ready-valid接口的信号方向進行取反,是以此時ready是output信号,valid和bits都是input信号。是以它是屬于資料接收者consumer的端口。

資料接收者和發送者都是相對的,一定要根據具體的情況正确設定信号方向。

chisel 仲裁器Arbiter和隊列Queue(ready-valid接口)

二、仲裁器Arbiter

Chisel内建了兩種仲裁器,一種是優先仲裁器,另一種是循環仲裁器。

  • 優先仲裁器的輸入通道的優先級是固定的,每次都是選擇多個有效通道中優先級最高的。
  • 而循環仲裁器每次都從不同的起點開始仲裁,采用輪詢方式檢視各個通道是否有請求,優先選擇先查到的有效通道。由于起點是依次變化的,是以每個通道總體來說具有相同的優先級。

第一種仲裁器優先仲裁器

Arbiter

在chisel3.util包下面,隻定義了Arbiter類,沒有單例對象,是以每次都需要通過new來建立Arbiter對象。

建立Arbiter對象的方式如下所示:

需要提供兩個參數,gen是傳輸的資料的類型,n是待仲裁對象的個數,也即資料發送者producer的個數。資料接收者consumer的個數為預設為1。

Arbiter内部使用

ArbiterIO

定義端口,而ArbiterIO内部又使用

Decoupled()

建立最終所需的ready-valid接口,定義如下:

class ArbiterIO[T <: Data](private val gen: T, val n: Int) extends Bundle {
   val in  = Flipped(Vec(n, Decoupled(gen)))
   val out = Decoupled(gen)
   val chosen = Output(UInt(log2Ceil(n).W))
}
           

可以看出,它會建立n個和producer連接配接的ready-valid接口,1個和consumer連接配接的ready-valid接口,以及一個表示最終選擇了哪個producer的chosen變量,該變量的值表示被選擇的producer在所有待仲裁對象中的索引,從0開始。

下面定義一個二選一仲裁器MyArbiter,并在代碼中例化了Arbiter:

class MyArbiter extends Module {
  val io = IO(new Bundle {
      val in = Flipped(Vec(2, Decoupled(UInt(8.W))))
      val out = Decoupled(UInt(8.W))
      val chosen = Output(UInt())
  })
  val arbiter = Module(new Arbiter(UInt(8.W), 2))  // 2 to 1 Priority Arbiter
  arbiter.io.in <> io.in
  io.out <> arbiter.io.out
  io.chosen := arbiter.io.chosen
}
           

生成的verliog代碼如下:

module Arbiter(
  output       io_in_0_ready,
  input        io_in_0_valid,
  input  [7:0] io_in_0_bits,
  output       io_in_1_ready,
  input        io_in_1_valid,
  input  [7:0] io_in_1_bits,
  input        io_out_ready,
  output       io_out_valid,
  output [7:0] io_out_bits,
  output       io_chosen
);
  wire  grant_1 = ~io_in_0_valid; // @[Arbiter.scala 31:78]
  assign io_in_0_ready = io_out_ready; // @[Arbiter.scala 134:19]
  assign io_in_1_ready = grant_1 & io_out_ready; // @[Arbiter.scala 134:19]
  assign io_out_valid = ~grant_1 | io_in_1_valid; // @[Arbiter.scala 135:31]
  // @[Arbiter.scala 126:27 Arbiter.scala 128:19 Arbiter.scala 124:15]
  assign io_out_bits = io_in_0_valid ? io_in_0_bits : io_in_1_bits; 
  // @[Arbiter.scala 126:27 Arbiter.scala 127:17 Arbiter.scala 123:13]
  assign io_chosen = io_in_0_valid ? 1'h0 : 1'h1; 
endmodule

module MyArbiter(
  input        clock,
  input        reset,
  output       io_in_0_ready,
  input        io_in_0_valid,
  input  [7:0] io_in_0_bits,
  output       io_in_1_ready,
  input        io_in_1_valid,
  input  [7:0] io_in_1_bits,
  input        io_out_ready,
  output       io_out_valid,
  output [7:0] io_out_bits,
  output       io_chosen
);
  wire  arbiter_io_in_0_ready; // @[Arbiter.scala 37:23]
  wire  arbiter_io_in_0_valid; // @[Arbiter.scala 37:23]
  wire [7:0] arbiter_io_in_0_bits; // @[Arbiter.scala 37:23]
  wire  arbiter_io_in_1_ready; // @[Arbiter.scala 37:23]
  wire  arbiter_io_in_1_valid; // @[Arbiter.scala 37:23]
  wire [7:0] arbiter_io_in_1_bits; // @[Arbiter.scala 37:23]
  wire  arbiter_io_out_ready; // @[Arbiter.scala 37:23]
  wire  arbiter_io_out_valid; // @[Arbiter.scala 37:23]
  wire [7:0] arbiter_io_out_bits; // @[Arbiter.scala 37:23]
  wire  arbiter_io_chosen; // @[Arbiter.scala 37:23]
  Arbiter arbiter ( // @[Arbiter.scala 37:23]
    .io_in_0_ready(arbiter_io_in_0_ready),
    .io_in_0_valid(arbiter_io_in_0_valid),
    .io_in_0_bits(arbiter_io_in_0_bits),
    .io_in_1_ready(arbiter_io_in_1_ready),
    .io_in_1_valid(arbiter_io_in_1_valid),
    .io_in_1_bits(arbiter_io_in_1_bits),
    .io_out_ready(arbiter_io_out_ready),
    .io_out_valid(arbiter_io_out_valid),
    .io_out_bits(arbiter_io_out_bits),
    .io_chosen(arbiter_io_chosen)
  );
  assign io_in_0_ready = arbiter_io_in_0_ready; // @[Arbiter.scala 38:17]
  assign io_in_1_ready = arbiter_io_in_1_ready; // @[Arbiter.scala 38:17]
  assign io_out_valid = arbiter_io_out_valid; // @[Arbiter.scala 39:10]
  assign io_out_bits = arbiter_io_out_bits; // @[Arbiter.scala 39:10]
  assign io_chosen = arbiter_io_chosen; // @[Arbiter.scala 40:13]
  assign arbiter_io_in_0_valid = io_in_0_valid; // @[Arbiter.scala 38:17]
  assign arbiter_io_in_0_bits = io_in_0_bits; // @[Arbiter.scala 38:17]
  assign arbiter_io_in_1_valid = io_in_1_valid; // @[Arbiter.scala 38:17]
  assign arbiter_io_in_1_bits = io_in_1_bits; // @[Arbiter.scala 38:17]
  assign arbiter_io_out_ready = io_out_ready; // @[Arbiter.scala 39:10]
endmodule
           

Verilog代碼中生成了兩個module,第一個module Arbiter對應的是例化的優先仲裁器Arbiter,第二個module MyArbiter對應的是頂層子產品MyArbiter。

下面是一個例子,通過一個Arbiter的具體的輸入輸出資料的情況,來了解一下其工作邏輯:

test(new Module {
    // Example circuit using a priority arbiter
    val io = IO(new Bundle {
      val in = Flipped(Vec(2, Decoupled(UInt(8.W))))
      val out = Decoupled(UInt(8.W))
    })
    // Arbiter doesn't have a convenience constructor, so it's built like any Module
    val arbiter = Module(new Arbiter(UInt(8.W), 2))  // 2 to 1 Priority Arbiter
    arbiter.io.in <> io.in
    io.out <> arbiter.io.out
  }) { c =>
    c.io.in(0).valid.poke(false.B)
    c.io.in(1).valid.poke(false.B)
    c.io.out.ready.poke(false.B)
    println(s"Start:")
    println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
    println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
    c.io.in(1).valid.poke(true.B)  // Valid input 1
    c.io.in(1).bits.poke(42.U)
    c.io.out.ready.poke(true.B)
    // What do you think the output will be?
    println(s"valid input 1:")
    println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
    println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
    c.io.in(0).valid.poke(true.B)  // Valid inputs 0 and 1
    c.io.in(0).bits.poke(43.U)
    // What do you think the output will be? Which inputs will be ready?
    println(s"valid inputs 0 and 1:")
    println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
    println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
    c.io.in(1).valid.poke(false.B)  // Valid input 0
    // What do you think the output will be?
    println(s"valid input 0:")
    println(s"\tin(0).ready=${c.io.in(0).ready.peek().litValue}, in(1).ready=${c.io.in(1).ready.peek().litValue}")
    println(s"\tout.valid=${c.io.out.valid.peek().litValue}, out.bits=${c.io.out.bits.peek().litValue}")
}
           

Elaborating design…

Done elaborating.

Start:

in(0).ready=0, in(1).ready=0

out.valid=0, out.bits=0

valid input 1:

in(0).ready=1, in(1).ready=1

out.valid=1, out.bits=42

valid inputs 0 and 1:

in(0).ready=1, in(1).ready=0

out.valid=1, out.bits=43

valid input 0:

in(0).ready=1, in(1).ready=0

out.valid=1, out.bits=43

test Helper_Anon Success: 0 tests passed in 2 cycles in 0.077434 seconds 25.83 Hz

沒有什麼需要特别說明的,因為它就是一個組合邏輯的子產品。

第二種仲裁器循環仲裁器

RRArbiter

也在chisel3.util包下面,并且隻定義了RRArbiter類,沒有單例對象,是以每次都需要通過new來建立RRArbiter對象。它的建立與調用方式和Arbiter是一樣的,隻是内部實作的仲裁邏輯不同。

三、隊列Queue

Chisel内建了隊列

Queue

,它會建立一個使用ready-valid接口 的FIFO,在chisel3.util包下面既定義了Queue類,也定義了其單例對象,是以有兩種建立Queue對象的方式。

Queue内部使用

QueueIO

定義端口,QueueIO最終仍然是使用

Decoupled()

建立所需的ready-valid接口,定義如下:

class QueueIO[T <: Data](private val gen: T, val entries: Int) extends Bundle
{ 
   val enq = Flipped(EnqIO(gen))
   val deq = Flipped(DeqIO(gen))
   val count = Output(UInt(log2Ceil(entries + 1).W))
}

object EnqIO {
  def apply[T<:Data](gen: T): DecoupledIO[T] = Decoupled(gen)
}

object DeqIO {
  def apply[T<:Data](gen: T): DecoupledIO[T] = Flipped(Decoupled(gen))
}
           
  • enq

    是用來寫資料的端口,是以它和資料生産者producer連接配接;
  • deq

    是用來讀資料的端口,是以它和資料接收者consumer連接配接;
  • count

    表示此時Queue中的資料個數。

可以通過以下兩種形式使用Queue:

第一個參數是存儲的資料的類型,第二個參數是存儲的資料的深度。該方式傳回的是一個Queue對象,該對象包含QueueIO屬性,是以我們可以在代碼中通路QueueIO的

enq deq count

這三種端口信号。

第一個參數是

ReadyValidIO[T]

類型的端口,第二個參數是存儲的資料的深度,預設值為2。該方式傳回的是

DecoupledIO[T]

類型的讀資料端口,也即上述的deq,是以我們不能在代碼中通路enq和count。

以上兩種形式由于傳回的對象不一樣,是以在使用時也有一些不同,下面通過兩個例子分别展示一下這兩種形式的具體使用方法。

  • 第一種形式的使用案例:
class MyQueue extends Module {
  val io = IO(new Bundle {
    val in = Flipped(Decoupled(UInt(8.W)))
    val out = Decoupled(UInt(8.W))
    val cnt = Output(UInt(4.W))
  })
  val q = Module(new Queue(UInt(8.W), entries = 16))
  q.io.enq <> io.in
  io.out <> q.io.deq
  io.cnt := q.io.count
 }
           
  • 第二種形式的使用案例:
class MyQueue extends Module {
    val io = IO(new Bundle {
    val in = Flipped(Decoupled(UInt(8.W)))
    val out = Decoupled(UInt(8.W))
  })
    val q = Queue(io.in, 2)
  	io.out <> q
}
           

上述兩段代碼都調用了Queue,是以在各自生成的verilog代碼中,會定義Queue對應的

module Queue

,該module會在頂層

module MyQueue

中被例化。兩者生成的module Queue的端口定義分别如下:

module Queue(
  input        clock,
  input        reset,
  output       io_enq_ready,
  input        io_enq_valid,
  input  [7:0] io_enq_bits,
  input        io_deq_ready,
  output       io_deq_valid,
  output [7:0] io_deq_bits,
  output [4:0] io_count
);
           
module Queue(
  input        clock,
  input        reset,
  output       io_enq_ready,
  input        io_enq_valid,
  input  [7:0] io_enq_bits,
  input        io_deq_ready,
  output       io_deq_valid,
  output [7:0] io_deq_bits
);
           

可以看出,module Queue的端口中都有所需的兩對ready-valid握手信号,并且這兩對信号方向相反,這是因為它們分别是用來寫資料和讀資料的。

在第二種形式中,是不會有io_count端口的,因為我們無法使用QueueIO中的count。

此外,Queue對象的

empty和full

屬性我們也通路不到,但是由于在

class Queue

中有如下定義:

io.deq.valid := !empty
  io.enq.ready := !Full
           

是以,我們就可以通過

io.deq.valid

io.enq.ready

間接地通路

empty

full

信号,通過這兩個信号來完成和

empty

full

信号有關的一些邏輯。

下面是一個例子,通過一個Queue(上述第二種使用形式)的具體的輸入輸出資料的情況,來了解一下其工作邏輯,注意它是一個時序子產品:

  • 首先,先看下生成的verilog代碼
module Queue(
  input        clock,
  input        reset,
  output       io_enq_ready,
  input        io_enq_valid,
  input  [7:0] io_enq_bits,
  input        io_deq_ready,
  output       io_deq_valid,
  output [7:0] io_deq_bits,
  output [4:0] io_count
);
`ifdef RANDOMIZE_MEM_INIT
  reg [31:0] _RAND_0;
`endif // RANDOMIZE_MEM_INIT
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_1;
  reg [31:0] _RAND_2;
  reg [31:0] _RAND_3;
`endif // RANDOMIZE_REG_INIT
  reg [7:0] ram [0:15]; // @[Decoupled.scala 218:16]
  wire [7:0] ram_io_deq_bits_MPORT_data; // @[Decoupled.scala 218:16]
  wire [3:0] ram_io_deq_bits_MPORT_addr; // @[Decoupled.scala 218:16]
  wire [7:0] ram_MPORT_data; // @[Decoupled.scala 218:16]
  wire [3:0] ram_MPORT_addr; // @[Decoupled.scala 218:16]
  wire  ram_MPORT_mask; // @[Decoupled.scala 218:16]
  wire  ram_MPORT_en; // @[Decoupled.scala 218:16]
  reg [3:0] enq_ptr_value; // @[Counter.scala 60:40]
  reg [3:0] deq_ptr_value; // @[Counter.scala 60:40]
  reg  maybe_full; // @[Decoupled.scala 221:27]
  wire  ptr_match = enq_ptr_value == deq_ptr_value; // @[Decoupled.scala 223:33]
  wire  empty = ptr_match & ~maybe_full; // @[Decoupled.scala 224:25]
  wire  full = ptr_match & maybe_full; // @[Decoupled.scala 225:24]
  wire  do_enq = io_enq_ready & io_enq_valid; // @[Decoupled.scala 40:37]
  wire  do_deq = io_deq_ready & io_deq_valid; // @[Decoupled.scala 40:37]
  wire [3:0] _value_T_1 = enq_ptr_value + 4'h1; // @[Counter.scala 76:24]
  wire [3:0] _value_T_3 = deq_ptr_value + 4'h1; // @[Counter.scala 76:24]
  wire [3:0] ptr_diff = enq_ptr_value - deq_ptr_value; // @[Decoupled.scala 257:32]
  wire [4:0] _io_count_T_1 = maybe_full & ptr_match ? 5'h10 : 5'h0; // @[Decoupled.scala 259:20]
  wire [4:0] _GEN_8 = {{1'd0}, ptr_diff}; // @[Decoupled.scala 259:62]
  assign ram_io_deq_bits_MPORT_addr = deq_ptr_value;
  assign ram_io_deq_bits_MPORT_data = ram[ram_io_deq_bits_MPORT_addr]; // @[Decoupled.scala 218:16]
  assign ram_MPORT_data = io_enq_bits;
  assign ram_MPORT_addr = enq_ptr_value;
  assign ram_MPORT_mask = 1'h1;
  assign ram_MPORT_en = io_enq_ready & io_enq_valid;
  assign io_enq_ready = ~full; // @[Decoupled.scala 241:19]
  assign io_deq_valid = ~empty; // @[Decoupled.scala 240:19]
  assign io_deq_bits = ram_io_deq_bits_MPORT_data; // @[Decoupled.scala 242:15]
  assign io_count = _io_count_T_1 | _GEN_8; // @[Decoupled.scala 259:62]
  always @(posedge clock) begin
    if(ram_MPORT_en & ram_MPORT_mask) begin
      ram[ram_MPORT_addr] <= ram_MPORT_data; // @[Decoupled.scala 218:16]
    end
    if (reset) begin // @[Counter.scala 60:40]
      enq_ptr_value <= 4'h0; // @[Counter.scala 60:40]
    end else if (do_enq) begin // @[Decoupled.scala 229:17]
      enq_ptr_value <= _value_T_1; // @[Counter.scala 76:15]
    end
    if (reset) begin // @[Counter.scala 60:40]
      deq_ptr_value <= 4'h0; // @[Counter.scala 60:40]
    end else if (do_deq) begin // @[Decoupled.scala 233:17]
      deq_ptr_value <= _value_T_3; // @[Counter.scala 76:15]
    end
    if (reset) begin // @[Decoupled.scala 221:27]
      maybe_full <= 1'h0; // @[Decoupled.scala 221:27]
    end else if (do_enq != do_deq) begin // @[Decoupled.scala 236:28]
      maybe_full <= do_enq; // @[Decoupled.scala 237:16]
    end
  end
endmodule

module MyQueue(
  input        clock,
  input        reset,
  output       io_in_ready,
  input        io_in_valid,
  input  [7:0] io_in_bits,
  input        io_out_ready,
  output       io_out_valid,
  output [7:0] io_out_bits,
  output [3:0] io_cnt
);
  wire  q_clock; // @[Queue.scala 14:17]
  wire  q_reset; // @[Queue.scala 14:17]
  wire  q_io_enq_ready; // @[Queue.scala 14:17]
  wire  q_io_enq_valid; // @[Queue.scala 14:17]
  wire [7:0] q_io_enq_bits; // @[Queue.scala 14:17]
  wire  q_io_deq_ready; // @[Queue.scala 14:17]
  wire  q_io_deq_valid; // @[Queue.scala 14:17]
  wire [7:0] q_io_deq_bits; // @[Queue.scala 14:17]
  wire [4:0] q_io_count; // @[Queue.scala 14:17]
  Queue q ( // @[Queue.scala 14:17]
    .clock(q_clock),
    .reset(q_reset),
    .io_enq_ready(q_io_enq_ready),
    .io_enq_valid(q_io_enq_valid),
    .io_enq_bits(q_io_enq_bits),
    .io_deq_ready(q_io_deq_ready),
    .io_deq_valid(q_io_deq_valid),
    .io_deq_bits(q_io_deq_bits),
    .io_count(q_io_count)
  );
  assign io_in_ready = q_io_enq_ready; // @[Queue.scala 16:12]
  assign io_out_valid = q_io_deq_valid; // @[Queue.scala 17:10]
  assign io_out_bits = q_io_deq_bits; // @[Queue.scala 17:10]
  assign io_cnt = q_io_count[3:0]; // @[Queue.scala 18:10]
  assign q_clock = clock;
  assign q_reset = reset;
  assign q_io_enq_valid = io_in_valid; // @[Queue.scala 16:12]
  assign q_io_enq_bits = io_in_bits; // @[Queue.scala 16:12]
  assign q_io_deq_ready = io_out_ready; // @[Queue.scala 17:10]
endmodule
           
  • 然後,再看下測試案例
test(new Module {
    // Example circuit using a Queue
    val io = IO(new Bundle {
      val in = Flipped(Decoupled(UInt(8.W)))
      val out = Decoupled(UInt(8.W))
    })
    val queue = Queue(io.in, 2)  // 2-element queue
    io.out <> queue
  }) { c =>
    c.io.out.ready.poke(false.B)
    c.io.in.valid.poke(true.B)  // Enqueue an element
    c.io.in.bits.poke(42.U)
    println(s"Starting:")
    println(s"\tio.in: ready=${c.io.in.ready.peek().litValue}")
    println(s"\tio.out: valid=${c.io.out.valid.peek().litValue}, bits=${c.io.out.bits.peek().litValue}")
    c.clock.step(1)

    c.io.in.valid.poke(true.B)  // Enqueue another element
    c.io.in.bits.poke(43.U)
    // What do you think io.out.valid and io.out.bits will be?
    println(s"After first enqueue:")
    println(s"\tio.in: ready=${c.io.in.ready.peek().litValue}")
    println(s"\tio.out: valid=${c.io.out.valid.peek().litValue}, bits=${c.io.out.bits.peek().litValue}")
    c.clock.step(1)

    c.io.in.valid.poke(true.B)  // Read a element, attempt to enqueue
    c.io.in.bits.poke(44.U)
    c.io.out.ready.poke(true.B)
    // What do you think io.in.ready will be, and will this enqueue succeed, and what will be read?
    println(s"On first read:")
    println(s"\tio.in: ready=${c.io.in.ready.peek()}")
    println(s"\tio.out: valid=${c.io.out.valid.peek()}, bits=${c.io.out.bits.peek()}")
    c.clock.step(1)

    c.io.in.valid.poke(false.B)  // Read elements out
    c.io.out.ready.poke(true.B)
    // What do you think will be read here?
    println(s"On second read:")
    println(s"\tio.in: ready=${c.io.in.ready.peek()}")
    println(s"\tio.out: valid=${c.io.out.valid.peek()}, bits=${c.io.out.bits.peek()}")
    c.clock.step(1)

    // Will a third read produce anything?
    println(s"On third read:")
    println(s"\tio.in: ready=${c.io.in.ready.peek()}")
    println(s"\tio.out: valid=${c.io.out.valid.peek()}, bits=${c.io.out.bits.peek()}")
    c.clock.step(1)
}
           

Elaborating design…

Done elaborating.

Starting:

io.in: ready=1

io.out: valid=0, bits=0

After first enqueue:

io.in: ready=1

io.out: valid=1, bits=42

On first read:

io.in: ready=Bool(false)

io.out: valid=Bool(true), bits=UInt<8>(42)

On second read:

io.in: ready=Bool(true)

io.out: valid=Bool(true), bits=UInt<8>(43)

On third read:

io.in: ready=Bool(true)

io.out: valid=Bool(false), bits=UInt<8>(42)

test Helper_Anon Success: 0 tests passed in 7 cycles in 0.013966 seconds 501.23 Hz

chisel 仲裁器Arbiter和隊列Queue(ready-valid接口)

關鍵在于上圖中的幾段代碼:

  • 隻要非空,io_out_valid就為1,如果io_out_ready也為1,那麼do_deq就會為1,讀資料就被使能了;
  • 隻要不滿,io_in_ready就為1,如果io_in_valid也為1,那麼do_enq就會為1,寫資料就被使能了;
  • 讀寫要執行一下step才能完成;
  • 隻要queue中有資料,那麼io_out_bits就會随時将此時指向的資料放在端口上,是以你會看到即使沒有使能讀資料,io_out_bits也會有值。

繼續閱讀