当前位置:网站首页>Chisel tutorial - 04 Control flow in chisel

Chisel tutorial - 04 Control flow in chisel

2022-07-07 23:41:00 github-3rr0r

control flow

motivation

So far in this series ,Chisel There is a strong correspondence between the software and hardware in . But after the introduction of control flow, it is different , There should be great differences on the views of software and hardware .

This section will introduce control flow in both generator software and hardware . If you reconnect to a Chisel What will happen to the connection ? How to make a multiplexer have more than two inputs ? This section will give the answers to these two questions .

Finally, connect semantics

Mentioned earlier ,Chisel Through operators := To connect components , For various reasons , It is allowed to emit multiple connection statements to the same component .

For multiple statements re assigned after assignment , The last connection statement will take effect :

class LastConnect extends Module {
    
  val io = IO(new Bundle {
    
    val in = Input(UInt(4.W))
    val out = Output(UInt(4.W))
  })
  io.out := 1.U
  io.out := 2.U
  io.out := 3.U
  io.out := 4.U
}

// Test LastConnect
test(new LastConnect) {
     c => c.io.out.expect(4.U) } // Assert that the output correctly has 4
println("SUCCESS!!") // Scala Code: if we get here, our tests passed!

when,elsewhen and otherwise

Chisel The main conditional logic implementation in is when,elsewhen and otherwise structure , It usually looks like this :

when(someBooleanCondition) {
    
  // things to do when true
}.elsewhen(someOtherBooleanCondition) {
    
  // things to do on this condition
}.otherwise {
    
  // things to do if none of th boolean conditions are true
}

The three of them must appear in the above order , The following can be omitted , There can be as many elsewhen Clause .

Any true clause condition will terminate execution , The actions performed by clause can be complex statement blocks or nested when.

And Scala Medium if The difference is ,when The clause in does not return a value . So it can't be used like that :

val result = when(squareIt) {
     x * x}.otherwise {
    x}

The solution will be in the later connection (Wire) Section mentions .

Here is a when structure Chisel Examples of conditional statements :

import chisel3._
import chisel3.util._

class Max3 extends Module {
    
  val io = IO(new Bundle {
    
    val in_a = Input(UInt(16.W))
    val in_b = Input(UInt(16.W))
    val in_c = Input(UInt(16.W))
    val out  = Output(UInt(16.W))
  })

  when(io.in_a >= io.in_b && io.in_a >= io.in_c) {
    
    io.out := io.in_a
  }.elsewhen(io.in_b >= io.in_c) {
    
    io.out := io.in_b
  }.otherwise {
    
    io.out := io.in_c
  }
}

object Max3 extends App {
    
  println(getVerilogString(new Max3()))
}

Output is :

module Max3(
  input         clock,
  input         reset,
  input  [15:0] io_in_a,
  input  [15:0] io_in_b,
  input  [15:0] io_in_c,
  output [15:0] io_out
);
  wire [15:0] _GEN_0 = io_in_b >= io_in_c ? io_in_b : io_in_c; // @[MyModule.scala 14:34 15:12 17:12]
  assign io_out = io_in_a >= io_in_b & io_in_a >= io_in_c ? io_in_a : _GEN_0; // @[MyModule.scala 12:50 13:12]
endmodule

Test part :

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {
    
  behavior of "MyOperators"
  it should "get right results" in {
    
    test(new Max3) {
     c =>
      // verify that the max of the three inputs is correct
      c.io.in_a.poke(6.U)
      c.io.in_b.poke(4.U)
      c.io.in_c.poke(2.U)
      c.io.out.expect(6.U)  // input 1 should be biggest
      c.io.in_b.poke(7.U)
      c.io.out.expect(7.U)  // now input 2 is
      c.io.in_c.poke(11.U)
      c.io.out.expect(11.U) // and now input 3
      c.io.in_c.poke(3.U)
      c.io.out.expect(7.U)  // show that decreasing an input works as well
      c.io.in_a.poke(9.U)
      c.io.in_b.poke(9.U)
      c.io.in_c.poke(6.U)
      c.io.out.expect(9.U)  // still get max with tie
    }

    println("SUCCESS!!") // Scala Code: if we get here, our tests passed!
  }
}

The test passed .

Wire structure

Mentioned earlier when Cannot return value ,Chisel Medium Wire Structure is one of the ways to solve this problem .Wire Defines a circuit component , He can show up := Left or right of the operator .

The following is a four input use Wire For example, the sorting circuit of :

To demonstrate , First, design a small combinatorial sorter , Accept four numerical inputs and get four numerical outputs , Consider the following graph :

 Insert picture description here

The red line indicates that the value on the left is smaller than that on the right , Just copy the value directly , The black line indicates that when the value on the left is larger than the value on the right, the two values are exchanged .

The figure shows a series of row Opening grid , have access to wire To construct these lattices , As a place to store copied or exchanged values .

The following is the code implementation , At present, it is quite lengthy , There will be ways to reduce it later :

import chisel3._
import chisel3.util._

class Sort4 extends Module {
    
  val io = IO(new Bundle {
    
    val in0 = Input(UInt(16.W))
    val in1 = Input(UInt(16.W))
    val in2 = Input(UInt(16.W))
    val in3 = Input(UInt(16.W))
    val out0 = Output(UInt(16.W))
    val out1 = Output(UInt(16.W))
    val out2 = Output(UInt(16.W))
    val out3 = Output(UInt(16.W))
  })

  val row10 = Wire(UInt(16.W))
  val row11 = Wire(UInt(16.W))
  val row12 = Wire(UInt(16.W))
  val row13 = Wire(UInt(16.W))

  when(io.in0 < io.in1) {
    
    row10 := io.in0
    row11 := io.in1
  }.otherwise {
    
    row10 := io.in1
    row11 := io.in0
  }

  when(io.in2 < io.in3 ) {
    
    row12 := io.in2
    row13 := io.in3
  }.otherwise {
    
    row12 := io.in3
    row13 := io.in2
  }

  val row20 = Wire(UInt(16.W))
  val row21 = Wire(UInt(16.W))
  val row22 = Wire(UInt(16.W))
  val row23 = Wire(UInt(16.W))

  when(row10 < row13) {
    
    row20 := row10
    row23 := row13
  }.otherwise {
    
    row20 := row13
    row23 := row10
  }

  when(row11 < row12) {
    
    row21 := row11
    row22 := row12
  }.otherwise {
    
    row21 := row12
    row22 := row11
  }

  when(row20 < row21) {
    
    io.out0 := row20
    io.out1 := row21
  }.otherwise {
    
    io.out0 := row21
    io.out1 := row20
  }

  when(row22 < row23) {
    
    io.out2 := row22
    io.out3 := row23
  }.otherwise {
    
    io.out2 := row23
    io.out3 := row22
  }
}

object Sort4 extends App {
    
  println(getVerilogString(new Sort4()))
}

Output Verilog The code is as follows :

module Sort4(
  input         clock,
  input         reset,
  input  [15:0] io_in0,
  input  [15:0] io_in1,
  input  [15:0] io_in2,
  input  [15:0] io_in3,
  output [15:0] io_out0,
  output [15:0] io_out1,
  output [15:0] io_out2,
  output [15:0] io_out3
);
  wire [15:0] row10 = io_in0 < io_in1 ? io_in0 : io_in1; // @[MyModule.scala 21:25 22:11 25:11]
  wire [15:0] row11 = io_in0 < io_in1 ? io_in1 : io_in0; // @[MyModule.scala 21:25 23:11 26:11]
  wire [15:0] row12 = io_in2 < io_in3 ? io_in2 : io_in3; // @[MyModule.scala 29:26 30:11 33:11]
  wire [15:0] row13 = io_in2 < io_in3 ? io_in3 : io_in2; // @[MyModule.scala 29:26 31:11 34:11]
  wire [15:0] row20 = row10 < row13 ? row10 : row13; // @[MyModule.scala 42:23 43:11 46:11]
  wire [15:0] row23 = row10 < row13 ? row13 : row10; // @[MyModule.scala 42:23 44:11 47:11]
  wire [15:0] row21 = row11 < row12 ? row11 : row12; // @[MyModule.scala 50:23 51:11 54:11]
  wire [15:0] row22 = row11 < row12 ? row12 : row11; // @[MyModule.scala 50:23 52:11 55:11]
  assign io_out0 = row20 < row21 ? row20 : row21; // @[MyModule.scala 58:23 59:13 62:13]
  assign io_out1 = row20 < row21 ? row21 : row20; // @[MyModule.scala 58:23 60:13 63:13]
  assign io_out2 = row22 < row23 ? row22 : row23; // @[MyModule.scala 66:23 67:13 70:13]
  assign io_out3 = row22 < row23 ? row23 : row22; // @[MyModule.scala 66:23 68:13 71:13]
endmodule

Test code :

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {
    
  behavior of "MyOperators"
  it should "get right results" in {
    
    test(new Sort4) {
     c =>
      // verify the inputs are sorted
      c.io.in0.poke(3.U)
      c.io.in1.poke(6.U)
      c.io.in2.poke(9.U)
      c.io.in3.poke(12.U)
      c.io.out0.expect(3.U)
      c.io.out1.expect(6.U)
      c.io.out2.expect(9.U)
      c.io.out3.expect(12.U)

      c.io.in0.poke(13.U)
      c.io.in1.poke(4.U)
      c.io.in2.poke(6.U)
      c.io.in3.poke(1.U)
      c.io.out0.expect(1.U)
      c.io.out1.expect(4.U)
      c.io.out2.expect(6.U)
      c.io.out3.expect(13.U)

      c.io.in0.poke(13.U)
      c.io.in1.poke(6.U)
      c.io.in2.poke(4.U)
      c.io.in3.poke(1.U)
      c.io.out0.expect(1.U)
      c.io.out1.expect(4.U)
      c.io.out2.expect(6.U)
      c.io.out3.expect(13.U)
    }
    println("SUCCESS!!")
  }
}

The test passed .

The tester can also be used Scala in List Characteristics of (Scala Standard Library 2.12.13 - scala.collection.immutable.List (scala-lang.org)), Construct permutations and combinations of various inputs :

test(new Sort4) {
     c =>
  List(1, 2, 3, 4).permutations.foreach {
     
    case i0 :: i1 :: i2 :: i3 => {
    
        println(s"Sorting $i0 $i1 $i2 $i3")
        c.io.in0.poke(i0.U)
        c.io.in1.poke(i1.U)
        c.io.in2.poke(i2.U)
        c.io.in3.poke(i3.U)
        c.io.out0.expect(1.U)
        c.io.out1.expect(2.U)
        c.io.out2.expect(3.U)
        c.io.out3.expect(4.U)
    }
    case _ => println(s"Matching Error!")
  }
}
println("SUCCESS!!")

there case yes Scala The classic use of . front List(1, 2, 3, 4).permutations.foreach The result is a variety of permutations List The iterator , Every List Input as a variable into the later partial function . hinder case Used to match patterns ,i0 :: i1 :: i2 :: i3 Is to construct a pattern , Used to match four values in the list , The function is equivalent to unpacking the values in the list to four variables .

practice

Polynomial circuit

x 2 − 2 x + 1 2 x 2 + 6 x + 3 4 x 2 − 10 x − 5 x^2 - 2x + 1\\ 2x^2 + 6x + 3\\ 4x^2 - 10x -5 x22x+12x2+6x+34x210x5

Construct a circuit to calculate the above polynomial , A selector input is used to decide which polynomial to calculate .

Tips : Use Wire To hold the x 2 x^2 x2, So you only need to calculate once .

First, use the idea of test driven development Scala Write a model :

def poly0(x: Int): Int = {
    x * x - 2 * x + 1}
def poly1(x: Int): Int = {
    2 * x * x + 6 * x + 3}
def poly2(x: Int): Int = {
    4 * x * x - 10 * x - 5}

def main(args: Array[String]): Unit = {
    
    assert(poly0(0) == 1)
    assert(poly1(0) == 3)
    assert(poly2(0) == -5)

    assert(poly0(1) == 0)
    assert(poly1(1) == 11)
    assert(poly2(1) == -11)
}

The test passed , Now wrap these three as a parameterized function , Like hardware . Use Scala Medium if sentence , Based on input select Select the corresponding polynomial :

def poly0(x: Int): Int = {
    x * x - 2 * x + 1}
def poly1(x: Int): Int = {
    2 * x * x + 6 * x + 3}
def poly2(x: Int): Int = {
    4 * x * x - 10 * x - 5}

def poly(select: Int, x: Int): Int = {
    
    if(select == 0) {
    
        poly0(x)
    } else if(select == 1) {
    
        poly1(x)
    } else {
    
        poly2(x)
    }
}

def main(args: Array[String]): Unit = {
    
    assert(poly(1, 0) == 3)
    assert(poly(1, 1) == 11)
    assert(poly(2, 1) == -11)
}

The test passed .

Now let's follow the original idea :

import chisel3._
import chisel3.util._

class Polynomial extends Module {
    
  val io = IO(new Bundle {
    
    val select = Input(UInt(2.W))
    val x = Input(SInt(32.W))
    val out = Output(SInt(32.W))
  })

  val result = Wire(SInt(32.W))
  val square = Wire(SInt(32.W))

  square := io.x * io.x
  when(io.select === 0.U) {
    
    result := square - 2.S * io.x + 1.S
  }.elsewhen(io.select === 1.U) {
    
    result := 2.S * square + 6.S * io.x + 3.S
  }.otherwise {
    
    result := 4.S * square - 10.S * io.x -5.S
  }

  io.out := result
}

object Polynomial extends App {
    
  println(getVerilogString(new Polynomial))
}

Output is as follows :

module Polynomial(
  input         clock,
  input         reset,
  input  [1:0]  io_select,
  input  [31:0] io_x,
  output [31:0] io_out
);
  wire [63:0] _square_T = $signed(io_x) * $signed(io_x); // @[MyModule.scala 14:18]
  wire [34:0] _result_T = 3'sh2 * $signed(io_x); // @[MyModule.scala 16:28]
  wire [31:0] square = _square_T[31:0]; // @[MyModule.scala 12:20 14:10]
  wire [34:0] _GEN_3 = {
   {3{square[31]}},square}; // @[MyModule.scala 16:22]
  wire [34:0] _result_T_3 = $signed(_GEN_3) - $signed(_result_T); // @[MyModule.scala 16:22]
  wire [34:0] _result_T_6 = $signed(_result_T_3) + 35'sh1; // @[MyModule.scala 16:35]
  wire [34:0] _result_T_7 = 3'sh2 * $signed(square); // @[MyModule.scala 18:19]
  wire [35:0] _result_T_8 = 4'sh6 * $signed(io_x); // @[MyModule.scala 18:34]
  wire [35:0] _GEN_4 = {
   {1{_result_T_7[34]}},_result_T_7}; // @[MyModule.scala 18:28]
  wire [35:0] _result_T_11 = $signed(_GEN_4) + $signed(_result_T_8); // @[MyModule.scala 18:28]
  wire [35:0] _result_T_14 = $signed(_result_T_11) + 36'sh3; // @[MyModule.scala 18:41]
  wire [35:0] _result_T_15 = 4'sh4 * $signed(square); // @[MyModule.scala 20:19]
  wire [36:0] _result_T_16 = 5'sha * $signed(io_x); // @[MyModule.scala 20:35]
  wire [36:0] _GEN_5 = {
   {1{_result_T_15[35]}},_result_T_15}; // @[MyModule.scala 20:28]
  wire [36:0] _result_T_19 = $signed(_GEN_5) - $signed(_result_T_16); // @[MyModule.scala 20:28]
  wire [36:0] _result_T_22 = $signed(_result_T_19) - 37'sh5; // @[MyModule.scala 20:42]
  wire [36:0] _GEN_0 = io_select == 2'h1 ? $signed({
   {1{_result_T_14[35]}},_result_T_14}) : $signed(_result_T_22); // @[MyModule.scala 17:33 18:12 20:12]
  wire [36:0] _GEN_1 = io_select == 2'h0 ? $signed({
   {2{_result_T_6[34]}},_result_T_6}) : $signed(_GEN_0); // @[MyModule.scala 15:27 16:12]
  assign io_out = _GEN_1[31:0]; // @[MyModule.scala 11:20]
endmodule

The test code is as follows :

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {
    
  def poly0(x: Int): Int = {
    x * x - 2 * x + 1}
  def poly1(x: Int): Int = {
    2 * x * x + 6 * x + 3}
  def poly2(x: Int): Int = {
    4 * x * x - 10 * x - 5}
  def poly(select: Int, x: Int): Int = {
    
    if(select == 0) {
    
      poly0(x)
    } else if(select == 1) {
    
      poly1(x)
    } else {
    
      poly2(x)
    }
  }
  
  behavior of "MyOperators"
  it should "get right results" in {
    
    test(new Polynomial) {
     c =>
      for(x <- 0 to 20) {
    
        for(select <- 0 to 2) {
    
          c.io.select.poke(select.U)
          c.io.x.poke(x.S)
          c.io.out.expect(poly(select, x).S)
        }
      }
    }
    println("SUCCESS!!")
  }
}

The test passed .

Finite state machine (FSM)

It's too painful to optimize the logic of state machine through Karnaugh map , Comprehensive tools can be used to optimize , It will also produce non intuitive 、 Unreadable code . While using Chisel It is a wise choice to write with control flow and final connection semantics .

A master will experience four states in his study career : Free (idel), Write code (coding), Write a paper (writing), graduation (graduate). The transition of these States is based on three conditions : coffee , Suddenly enlightened idea, From the tutor pressure. once graduation 了 , Will return Free state .

Below FSM Figure shows these States and the transitions between them :

 Insert picture description here

All transformations without labels in the figure ( That is, no input ) Will return to Free State rather than stay in the current state . The priority entered is coffee > idea > pressure, So when you are idle, if you receive coffee and pressure at the same time , It will be transferred to the state of writing code .

Here is Scala Test code :

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {
    
  def states = Map("idle" -> 0, "coding" -> 1, "writing" -> 2, "grad" -> 3)

  def gradLife (state: Int, coffee: Boolean, idea: Boolean, pressure: Boolean): Int = {
    
    var nextState = states("idle")
    if (state == states("idle")) {
    
      if (coffee) {
    nextState = states("coding")}
      else if (idea) {
    nextState = states("idle")}
      else if (pressure) {
    nextState = states("writing")}
    } else if (state == states("coding")) {
    
      if (coffee) {
    nextState = states("coding")}
      else if (idea || pressure) {
    nextState = states("writing")}
    } else if (state == states("writing")) {
    
      if (coffee || idea) {
    nextState = states("writing")}
      else if (pressure) {
    nextState = states("grad")}
    }
    nextState
  }

  (0 until states.size).foreach{
     state => assert(gradLife(state, false, false, false) == states("idle")) }
  assert(gradLife(states("writing"), true, false, true) == states("writing"))
  assert(gradLife(states("idle"), true, true, true) == states("coding"))
  assert(gradLife(states("idle"), false, true, true) == states("idle"))
  assert(gradLife(states("grad"), false, false, false) == states("idle"))
}

I haven't learned temporal logic yet , So the current state here is represented by an input to the module , The next state is represented by an output , With the above gradLife Consistent function . Now use Chisel To achieve .

Chisel Provides a convenient state machine mapping function , be called Enum, In order to use these States , Think of them as UInt Use literal value .

Be careful , In hardware, three equal signs are used to judge equality ===

The implementation is as follows :

import chisel3._
import chisel3.util._

class GradLife extends Module {
    
  val io = IO(new Bundle {
    
    val state = Input(UInt(2.W))
    val coffee = Input(Bool())
    val idea = Input(Bool())
    val pressure = Input(Bool())
    val nextState = Output(UInt(2.W))
  })

  val idle :: coding :: writing :: grad :: Nil = Enum(4)

  io.nextState := idle
  when(io.state === idle) {
    
    when(io.coffee) {
    
      io.nextState := coding
    }.elsewhen(io.idea) {
    
      io.nextState := idle
    }.elsewhen(io.pressure) {
    
      io.nextState := writing
    }
  }.elsewhen(io.state === coding) {
    
    when(io.coffee) {
    
      io.nextState := coding
    }.elsewhen(io.idea || io.pressure) {
    
      io.nextState := writing
    }
  }.elsewhen(io.state === writing) {
    
    when(io.coffee || io.idea) {
    
      io.nextState := writing
    }.elsewhen(io.pressure) {
    
      io.nextState := grad
    }
  }
}

object GradLife extends App {
    
  println(getVerilogString(new GradLife))
}

Output Verilog The code is as follows :

module GradLife(
  input        clock,
  input        reset,
  input  [1:0] io_state,
  input        io_coffee,
  input        io_idea,
  input        io_pressure,
  output [1:0] io_nextState
);
  wire [1:0] _GEN_0 = io_pressure ? 2'h2 : 2'h0; // @[MyModule.scala 15:16 21:29 22:20]
  wire [1:0] _GEN_1 = io_idea ? 2'h0 : _GEN_0; // @[MyModule.scala 19:25 20:20]
  wire [1:0] _GEN_2 = io_coffee ? 2'h1 : _GEN_1; // @[MyModule.scala 17:21 18:20]
  wire [1:0] _GEN_3 = io_idea | io_pressure ? 2'h2 : 2'h0; // @[MyModule.scala 15:16 27:40 28:20]
  wire [1:0] _GEN_4 = io_coffee ? 2'h1 : _GEN_3; // @[MyModule.scala 25:21 26:20]
  wire [1:0] _GEN_5 = io_pressure ? 2'h3 : 2'h0; // @[MyModule.scala 15:16 33:29 34:20]
  wire [1:0] _GEN_6 = io_coffee | io_idea ? 2'h2 : _GEN_5; // @[MyModule.scala 31:32 32:20]
  wire [1:0] _GEN_7 = io_state == 2'h2 ? _GEN_6 : 2'h0; // @[MyModule.scala 15:16 30:36]
  wire [1:0] _GEN_8 = io_state == 2'h1 ? _GEN_4 : _GEN_7; // @[MyModule.scala 24:35]
  assign io_nextState = io_state == 2'h0 ? _GEN_2 : _GEN_8; // @[MyModule.scala 16:27]
endmodule

The test code is as follows :

import chisel3._
import chiseltest._
import org.scalatest.flatspec.AnyFlatSpec

class MyModuleTest extends AnyFlatSpec with ChiselScalatestTester {
    
  def states = Map("idle" -> 0, "coding" -> 1, "writing" -> 2, "grad" -> 3)

  def gradLife (state: Int, coffee: Boolean, idea: Boolean, pressure: Boolean): Int = {
    
    var nextState = states("idle")
    if (state == states("idle")) {
    
      if (coffee) {
    nextState = states("coding")}
      else if (idea) {
    nextState = states("idle")}
      else if (pressure) {
    nextState = states("writing")}
    } else if (state == states("coding")) {
    
      if (coffee) {
    nextState = states("coding")}
      else if (idea || pressure) {
    nextState = states("writing")}
    } else if (state == states("writing")) {
    
      if (coffee || idea) {
    nextState = states("writing")}
      else if (pressure) {
    nextState = states("grad")}
    }
    nextState
  }
  
  behavior of "MyOperators"
  it should "get right results" in {
    
    test(new GradLife) {
     c =>
      for (state <- 0 to 3) {
    
        for (coffee <- List(true, false)) {
    
          for (idea <- List(true, false)) {
    
            for (pressure <- List(true, false)) {
    
              c.io.state.poke(state.U)
              c.io.coffee.poke(coffee.B)
              c.io.idea.poke(idea.B)
              c.io.pressure.poke(pressure.B)
              c.io.nextState.expect(gradLife(state, coffee, idea, pressure).U)
            }
          }
        }
      }
    }
    println("SUCCESS!!")
  }
}

The test passed .

原网站

版权声明
本文为[github-3rr0r]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202130555354178.html