chipsalliance / chisel

Chisel: A Modern Hardware Design Language
https://www.chisel-lang.org/
Apache License 2.0
3.99k stars 597 forks source link

Problem Regarding n-to-1 Multiplexer #3761

Closed Norhua closed 9 months ago

Norhua commented 9 months ago

Type of issue: Bug Report

Please tell us about your environment:

I am attempting to implement a multi-input and multi-output xbar for AXI. My idea for the xbar is to collect the output of each master/slave, then use a MUX to select the current master/slave output to connect to the xbar for forwarding. For each master/slave input, a MUX is used to select whether it is the forwarding signal of the xbar or the default signal of 0. In this way, when the xbar is idle, both the master and slave think that the other is busy, and then they will block their information until the MUX is turned on.

In my implementation, there are two n-to-1 multiplexers. One can be compiled correctly, but the other one throws an error.

  left := MuxLookup(m_sel, leftDefault) (
            (0 until nr_m).map(i => ((1.U << i), masterOut(i)))) // A compilation error will occur.

  right := MuxLookup(s_sel, rightDefault) (
            (0 until nr_s).map(i => ((1.U << i), slaveOut(i))))

Here is my complete implementation:

import chisel3._
import chisel3.util._
import scala.reflect.runtime.universe._

class AXIIN extends Bundle {
  val araddr  = Input(UInt(32.W))
  val arvalid = Input(Bool())

  val rready  = Input(Bool())

  val awaddr  = Input(UInt(32.W))
  val awvalid = Input(Bool())

  val wdata   = Input(UInt(32.W))
  val wstrb   = Input(UInt(8.W))
  val wvalid  = Input(Bool())

  val bready  = Input(Bool())
}
class AXIOUT extends Bundle {
  val arready = Output(Bool())

  val rdata   = Output(UInt(32.W))
  val rrsp    = Output(UInt(2.W))
  val rvalid  = Output(Bool())

  val awready = Output(Bool())

  val wready  = Output(Bool())

  val bresp   = Output(UInt(2.W))
  val bvalid  = Output(Bool())
}

class XbarIO(nr_m: Int, nr_s: Int) extends Bundle {
  val master = Vec(nr_m, new AXI)

  val slave = Vec(nr_s, Flipped(new AXI))
}

class Xbar_tmp(nr_m: Int = 2, nr_s: Int = 1) extends Module {
  val io = IO(new XbarIO(nr_m, nr_s))

  val m_sel = RegInit(0.U(nr_m.W)) // One-hot code
  val addr = RegInit(0.U(32.W))

  val masterOut = Wire(Vec(nr_m, new AXIIN))
  val slaveOut = Wire(Vec(nr_s, new AXIOUT))

  val left = Wire(new AXIIN)
  val right = Wire(new AXIOUT)

  val leftDefault = Wire(new AXIIN)
  for (enty <- leftDefault.getElements) {
    enty := 0.U
  }
  val rightDefault = Wire(new AXIOUT)
  for (enty <- rightDefault.getElements) {
    enty := 0.U
  }

  // sfm
  val s_idle :: s_arbiter :: s_link :: s_wait_ok :: Nil = Enum(4)
  val state = RegInit(s_idle)
  state := MuxLookup(state, s_idle) (
    List(
    s_idle    -> Mux(io.master.map(m => m.arvalid || m.awvalid).reduce(_ || _), s_arbiter, s_idle), // If there is a handshake signal from the master, enter the arbitration stage
    s_arbiter -> s_link, // Assign values to m_sel and addr, then addr will get s_sel through the decoder API

    s_link    -> Mux(right.arready, s_link, s_wait_ok), // If the slave is not idle, enter the waiting for completion
    s_wait_ok -> Mux(right.arready, s_idle, s_wait_ok)  // If the slave enters idle, it means completion, and return to idle state
    )
  )

  m_sel := MuxLookup(state, m_sel) (
    List(
      s_idle -> 0.U,
      s_arbiter -> PriorityMux(io.master.map(m => m.arvalid || m.awvalid), (0 until nr_m).map(i => 1.U << i))
    )
  )
  addr := MuxLookup(state, addr) (
    List(
      s_idle -> 0.U,
      s_arbiter -> PriorityMux(io.master.map(m => m.arvalid || m.awvalid), io.master.map(m => Mux(m.arvalid, m.rdata, m.wdata)))
    )
  )
  val slaveSelector = Module(new SlaveSelector) // 通过 decoder api 获取 s_sel
  slaveSelector.io.addr := addr
  val s_sel = slaveSelector.io.s_sel

  left := MuxLookup(m_sel, leftDefault) (
            (0 until nr_m).map(i => ((1.U << i), masterOut(i)))) // This statement will fail
  // left := Mux(state === s_idle, leftDefault, masterOut(0))    // This statement will also fail with the same error. 
  // left := masterOut(1)                                        //  This statement will compile successfully
  // left := MuxLookup(m_sel, leftDefault) (         // This statement will also fail with the same error. 
  //   List(
  //     (1.U << 0) -> masterOut(0),
  //     (1.U << 1) -> masterOut(1)
  //   )
  // )
  right := MuxLookup(s_sel, rightDefault) (
            (0 until nr_s).map(i => ((1.U << i), slaveOut(i))))
  // right := slaveOut(1)

  for (i <- 0 until nr_m) {
    for ((name, element) <- io.master(i).elements) {
      if (typeOf[AXIOUT].members.exists(_.name.toString == name)) { // master's input
        element := Mux(m_sel === (1.U << i), right.elements(name), 0.U)
      }else {
        masterOut(i).elements(name) := element
      }
    }
  }
  for (i <- 0 until nr_s) {
    for ((name, element) <- io.slave(i).elements) {
      if (typeOf[AXIIN].members.exists(_.name.toString == name)) { // slave's input
        element := Mux(s_sel === (1.U << i), left.elements(name), 0.U)
      }else {
        slaveOut(i).elements(name) := element
      }
    }
  }
}

What is the current behavior? However, when compiling this code, the part that selects the current master output will throw an error. The error message is as follows:

➜  npc git:(pa3) ✗ make sim
mkdir -p ./build
mill -i __.test.runMain Main -td ./build
No mill version specified.
You should provide a version via '.mill-version' file or --mill-version option.
Using mill version 0.11.6
[81/81] playground.test.runMain
Exception in thread "main" circt.stage.phases.Exceptions$FirtoolNonZeroExitCode: /home/amadeus/.cache/llvm-firtool/1.62.0/bin/firtool returned a non-zero exit code. Note that this version of Chisel (6.0.0) was published against firtool version 1.62.0.
------------------------------------------------------------------------------
ExitCode:
1
STDOUT:

STDERR:
<stdin>:328:5: error: Node cannot be analog and must be passive or passive under a flip '!firrtl.bundle<araddr flip: uint<32>, arvalid flip: uint<1>, rready flip: uint<1>, awaddr flip: uint<32>, awvalid flip: uint<1>, wdata flip: uint<32>, wstrb flip: uint<8>, wvalid flip: uint<1>, bready flip: uint<1>>'
    node _left_T_3 = mux(_left_T_2, masterOut[0], leftDefault) @[playground/src/device/AXI/Xbar_tmp.scala 90:41]
    ^

------------------------------------------------------------------------------
1 targets failed
playground.test.runMain subprocess failed
make: *** [Makefile:11: verilog] Error 1

The strange thing about this problem is that the code related to the master side is symmetrically the same as the code related to the slave side. But if we comment out the code that selects the current master output and change it to default to select the second master, and keep the code on the slave side unchanged, the compilation can surprisingly pass.

  // left := MuxLookup(m_sel, leftDefault) (
  //           (0 until nr_m).map(i => ((1.U << i), masterOut(i))))
  // left := Mux(state === s_idle, leftDefault, masterOut(0))    // This statement will also fail with the same error.

  // Modify the code that generates left
  left := masterOut(1)                                        // This statement will compile successfully
  // left := MuxLookup(m_sel, leftDefault) (
  //   List(
  //     (1.U << 0) -> masterOut(0),
  //     (1.U << 1) -> masterOut(1)
  //   )
  // )

  //  Keep unchanged
  right := MuxLookup(s_sel, rightDefault) (
            (0 until nr_s).map(i => ((1.U << i), slaveOut(i))))

I would greatly appreciate any clues on how to solve this problem.

jackkoenig commented 9 months ago

Answered on Stack Overflow: https://stackoverflow.com/a/77892503/2483329

TLDR: Avoid using direction specifiers inside of Bundles when all fields go the same direction, instead use them outside.