cisen / blog

Time waits for no one.
133 stars 20 forks source link

Chisel 相关 #1044

Open cisen opened 3 years ago

cisen commented 3 years ago

总结

问答

如何生成verilog?

怎么debug?

HasCoreParameters是什么?

tile是什么?

Flipped是什么意思?

<>是什么符号?

mux

Decoupled是什么? https://github.com/cisen/chisel-doc/blob/master/39.Rocket%20Chip%E9%87%8C%E9%9D%A2%E7%9A%84Decoupled%EF%BC%88%E8%A7%A3%E8%80%A6%EF%BC%89%E6%8E%A5%E5%8F%A3.md

edge是什么?

tile是什么?

Double data rate)触发器构成。该触发器既可以双沿捕获输入数据也可以拆分成普通单沿触发器。在HP BANK中,ILOGIC被称为ILOGICE2,在HR BANK中,ILOGIC被称为ILOGICE3

CLB是FPGA具有可编程能力的主要承担者。通过配置这些CLB可以让FPGA实现各种不同的逻辑功能。Input/Output Block分布在FPGA的周边,也具有可编程特性,可以配置支持各种不同的接口标准,如LVTTL、LVCMOS、PCI和LVDS等。BlockRAM是成块的RAM,可以在设计中用于存储数据,是设计的重要资源。在大规模设计选择FPGA时,RAM资源是否够用是重要的考虑因素。

除了CLB、Input/Output Block和BlockRAM以外,FPGA还有很多其他的功能单元,例如布线资源、DCM(Digital Clock Manager,数字时钟管理器)和Multiplier(乘法器)等。布线资源在FPGA内部占用硅片面积很大,为FPGA部件提供灵活可配的连接;DCM模块提供各种时钟资源,包括多种分频、移相后的时钟;Multiplier为18bit×18bit硬件乘法器,可以在一个时钟周期内完成乘法运算。

输入输出模块(Input/Output Block)

Input/Output Block 作用是为FPGA提供内部资源与外围电路之间的接口,提供输入缓冲、输出驱动、接口电平转换、阻抗匹配、延迟控制等功能。高端FPGA的输入/输出模块还提供了DDR输入/输出接口、高速串行接口(SERDES)(见注1)等功能。

Xilinx FPGA 的输入/输出模块采用SelectIO技术(SelectIO是Xilinx公司的注册商标),提供多达960个用户IO,支持20多个单端和差分电平I/O标准;还支持DDR、DDR-2、SDRAM、QDR-II和RLDRAM-II等Memory接口标准。SelectIO经验DCI(Digitally Controlled Imepedence,数字控制阻抗),提供有源I/O终端以实现阻抗匹配。

Virtex-5的Input/Output Block以Tile为单位,IO Tile的概念与CLB有相似之处,同样是一个较大的组成单元,内部包含多个相同单元。一个Tile包含两个IOB、两个ILOGIC/ISERDES单元和两个OLOGIC/OSERDES单元。

IOB内部的主要组成部分是输入/输出Buffer和PAD(焊盘,在集成电路版图上由金属焊点和静电防护二极管组成),提供输入信号缓冲、输出信号驱动等功能。

在Input/Output Block 中,每个ILOGIC/ISERDES 单元都可以配置为ILOGIC或ISERDES。配置为ILOGIC时,可以作为常见的输入逻辑单元,或作为DDR接口的输入端;配置为ISERDES时,可以完成1到6的串并转换,两个ISERDES单元相连可以完成1到10的串并转换。

类似地,每个OLOGIC/OSERDES 单元可以配置为OLOGIC,实现常见的输入逻辑单元,或作为DDR接口的输出端。也可以配置为OSERDES,完成6到1的并串转换,经过IOB和PAD驱动高速串行总线。两个OSERDES单元相连可以完成10到1的并串转换。

Input/Output Block中有IDELAY单元,可以提供精确的延迟,这个延迟不受工艺和温度的影响。延迟共有64个抽头,每个抽头提供75ps的延迟,因此延迟的数值可以在0~4800ps之间进行选择。

部分IO接口标准需要特定的Vcco和Vref电压,这些电压由FPGA外部电路提供,并连接到FPGA管脚,供多个I/O Tile共享。连接到同一组Vcco和Vref电压的I/O Tile组成一个Bank(中文意思是“组”,但是通常直接用Bank表示更方便)。

FPGA IO资源的基本单元架构为一个个 IO tile ,下图为 IO tile 的结构概略图: 20190606134221787

一个 IO tile 包含两个 IOB、两个 ILOGIC 和 两个 OLOGIC。本篇主要描述 IOB 的结构。

IOB的基本结构如下图所示,包含了输入缓冲、输出缓冲和三态控制三种驱动。 20210219215536151

一、FPGA的开发软件提供了 IOB 不同功能的原语(primitives): 对于单端信号:

IBUF (input buffer) IBUFG (clock input buffer) 20210219224857710

OBUF (output buffer) 20210219224913890

OBUFT (3-state output buffer) 20210219224923711

IOBUF (input/output buffer) 20210219224935268

对于差分信号: IBUFDS (input buffer) IBUFGDS (clock input buffer) 20210219224951584

OBUFDS (output buffer) 20210219225002690

OBUFTDS (3-state output buffer) 20210219225013583

IOBUFDS (input/output buffer) 20210219225023689

注意:一对差分信号作为输入输出时必须使用同一 tile 的 P/N 管脚,如下图的 L31P 和 L31N 为同一tile上的一对差分管脚。 20210219231309521

二、定义好 IOB 输入输出特性后,FPGA开发软件还提供对 IOB 的管脚约束、IO接口电气标准、输出压摆率、输出驱动能力、低容性IO、IO上下拉、差分100欧姆匹配电阻使能的设置。可以参考相关FPGA的数据手册,查看具体参数设置。

三、以上原语及设置如何实现?拿IOBUF举个例子:

2021021923280562


cisen commented 3 years ago

翻译

1

val io = IO(new Bundle {
    val out = Output(UInt(8.W))
  })
  io.out := 42.U
(
  input        clock,
  input        reset,
  output [7:0] io_out
);
  assign io_out = 8'h2a; // @[Hello.scala 11:10]

RegNext


```verilog
module ShiftRegister(
  input   clock,
  input   reset,
  input   io_in,
  output  io_out
);
`ifdef RANDOMIZE_REG_INIT
  reg [31:0] _RAND_0;
  reg [31:0] _RAND_1;
  reg [31:0] _RAND_2;
  reg [31:0] _RAND_3;
`endif // RANDOMIZE_REG_INIT
  reg  r0; // @[ShiftRegister.scala 11:19]
  reg  r1; // @[ShiftRegister.scala 12:19]
  reg  r2; // @[ShiftRegister.scala 13:19]
  reg  r3; // @[ShiftRegister.scala 14:19]
  assign io_out = r3; // @[ShiftRegister.scala 15:10]
  always @(posedge clock) begin
    r0 <= io_in; // @[ShiftRegister.scala 11:19]
    r1 <= r0; // @[ShiftRegister.scala 12:19]
    r2 <= r1; // @[ShiftRegister.scala 13:19]
    r3 <= r2; // @[ShiftRegister.scala 14:19]
  end
// Register and memory initialization
`ifdef RANDOMIZE_GARBAGE_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_INVALID_ASSIGN
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_REG_INIT
`define RANDOMIZE
`endif
`ifdef RANDOMIZE_MEM_INIT
`define RANDOMIZE
`endif
`ifndef RANDOM
`define RANDOM $random
`endif
`ifdef RANDOMIZE_MEM_INIT
  integer initvar;
`endif
`ifndef SYNTHESIS
`ifdef FIRRTL_BEFORE_INITIAL
`FIRRTL_BEFORE_INITIAL
`endif
initial begin
  `ifdef RANDOMIZE
    `ifdef INIT_RANDOM
      `INIT_RANDOM
    `endif
    `ifndef VERILATOR
      `ifdef RANDOMIZE_DELAY
        #`RANDOMIZE_DELAY begin end
      `else
        #0.002 begin end
      `endif
    `endif
`ifdef RANDOMIZE_REG_INIT
  _RAND_0 = {1{`RANDOM}};
  r0 = _RAND_0[0:0];
  _RAND_1 = {1{`RANDOM}};
  r1 = _RAND_1[0:0];
  _RAND_2 = {1{`RANDOM}};
  r2 = _RAND_2[0:0];
  _RAND_3 = {1{`RANDOM}};
  r3 = _RAND_3[0:0];
`endif // RANDOMIZE_REG_INIT
  `endif // RANDOMIZE
end // initial
`ifdef FIRRTL_AFTER_INITIAL
`FIRRTL_AFTER_INITIAL
`endif
`endif // SYNTHESIS
endmodule
cisen commented 3 years ago

语法和内置对象

trait with

tile

lazyModule

// Define your very own configuration object ...

case object BlankField extends Field[Boolean]

class BlankConfig extends Config((site, here, up) => {
  case BlankField => false
})

// Pay close attention to use of LazyModule and LazyModuleImp ...

object ChiselTopDriver extends App {
  implicit val p: Parameters = new BlankConfig
  chisel3.Driver.execute(args, () => LazyModule(new ChiselTop).module)
}

class ChiselTop()(implicit p: Parameters) extends LazyModule {
  val master = LazyModule(new AHBMaster)
  val slave = LazyModule(new AHBSlave)

  slave.node := master.node // does not work.. yet..

   lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val ddrClock = Input(Clock())
      val ddrReset = Input(UInt(1.W))
    }
  }
}

class AHBMaster()(implicit p: Parameters) extends LazyModule
{
  val node = AHBOutputNode() // does not work.. yet..

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val out = node.bundleOut
    }
  }
}

class AHBSlave()(implicit p: Parameters) extends LazyModule
{
  val node = AHBInputNode() // does not work.. yet..

  lazy val module = new LazyModuleImp(this) {
    val io = new Bundle {
      val in = node.bundleIn
    }
  }
}

https://mp.weixin.qq.com/s/FBU8fE4u9-UK6mRGQOlvbQ

  1. children LazyModule内部的LazyModule,说明LazyModule是可以嵌套的。 在如下位置添加:

  2. nodes LazyModule内部的Node。 相较之下,Node不能嵌套,内部没有其他Node。 在BaseNode中添加:

  3. parent 记录父LazyModule的信息。

  4. inModuleBody 一些在Module Body中执行的函数。

  5. GraphML 用于把LazyModule的结构信息,转化为GraphML的结构信息: GraphML是yworks公司(http://www.yworks.com/xml/graphml)定义的一种图标记语言(Graph Markable Language),类似于HTML/XML等。 所以生成的GraphML文本,可以使用yworks公司的GraphML工具来可视化。如果所猜测不错的话,应该是如下图所示:

  6. index 当前LazyModule实例的编号,代表该实例是被第几个创建的。

  7. module LazyModule的实现,多以lazy val的形式出现。这个lazy也是LazyModule中的Lazy的由来。 PS. 这种命名法不推荐。名称应当取决于其意义(见名知义),而不是来自于其实现。如果要改的话,不妨改成TileModule,体现其空心框架的意义,同时也与TileLink对应。或者叫DiplomaticModule,体现其协商参数的意义,同时与diplomacy的包名对应。

  8. 总结 a. LazyModule是Module吗? 如果Module是指Chisel3和Verilog中的硬件模块,那么LazyModule不是。LazyModule的实现LazyModuleImpLike对应着硬件模块。 如果把LazyModule和LazyModuleImp作为一个整体看的话,又可以把他们理解为一个硬件模块:包含了输入输出,包含了内部的子模块(LazyModule)和子节点(Node)。 b. LazyModule的作用 如同一块空心砖(tile),撑起一个框架结构,以供填充内部具体实现。

cisen commented 3 years ago

参数化

Site/Here/Up

import chisel3. import chisel3.Driver import rocketchip.config.

case object WIDTH extends Field[Int]

// 这里就是独立参数管理器 class MyConfig extends Config((site, here, up) => { case WIDTH => 8 })

class Helloworld (implicit p:Parameters) extends Module { // p传入的是一个参数变量,可以动态的获取输入的变量 val width:Int = p(WIDTH) val io = IO(new Bundle{ val port_a = Input(UInt(width.W)) val port_b = Input(UInt(width.W)) val port_c = Output(Uint(width.W)) }) IO.port_c := IO.port_a + io.port_b }

object Helloworld extend App { // 这里传入MyConfig管理的参数给helloworld implicit val parames: Config = (new MyConfig).toInstance Driver.emitVerilog(new Helloworld()(parames)) }


```scala
package generatorTests

import chisel3._
import chisel3.Driver
import rocketchip.config._

case object WIDTH extends Field[Int]

// 这里就是独立参数管理器
class MyConfig extends Config((site, here, up) => {
    case WIDTH => 8
    // here表示本偏函数的引用,这里的WIDTH是动态的,看重写的width值
    case DBWIDTH => 2 * here(WIDTH),
    // 注意这里的type也是config的值,只是TYPE是由实例化的时候重写的
    case SITEWIDTH => site(TYPE) match {
        case "module_a" => 6
        case "module_b" => 1
    },
    // 表示从更上一层节点(往根节点)方向查找参数
    case UPWIDTH => 3 * up(WIDTH)
})

class ModuleA (implicit p:Parameters) extends Module {
    // p传入的是一个参数变量,可以动态的获取输入的变量
    val width:Int = p(WIDTH)
    // 注意实例化的时候是灭有传入DBWIDTH
    val DBwidth:Int = p(DBWIDTH)
    val io = IO(new Bundle{
        val port_d = Input(UInt(width.W))
        val port_e = Input(UInt(width.W))
    })
    IO.port_e := IO.port_d + UInt(width)
}

class Helloworld (implicit p:Parameters) extends Module {
    val width: Int = p(WIDTH)
    val io = IO(new Bundle{
        val port_a = Input(UInt(width.W))
        val port_b = Input(UInt(width.W))
        val port_c = Output(Uint(width.W))
    })
    // 重写myconfig的WIDTH, 并传给ModuleA
    val m_module_a = Module(new ModuleA(width)(p.alterPartial({
        // 没有传入DBWIDTH,则会从参数连上找到DBWIDTH
        case WIDTH => 4
        case PA => 5,
        case TYPE => "module_a"
    })))

    m_module_a.io.port_d := io.port_b
    io.port_c := m_module_a.io.port_e + io.port_a
}

object Helloworld extend App {
    // 这里传入MyConfig管理的参数给helloworld
    implicit val parames: Config = (new MyConfig).toInstance
    Driver.emitVerilog(new Helloworld()(parames))
}

dipomacy

import chisel3. import chisel3.Driver import chipsalliance.rocketchip.config. import chisel3.internal.sourceinfo.SourceInfo import chisel3.util.random.FibonacciLFSR import freechips.rocketchip.diplomacy._

case class UpwardParam(width: Int) case class DownwardParam(width: Int) case class EdgeParam(width: Int)

class dipomacy {}

// 问题的关键加法器的参数的数量是不固定的,比如add(1,2)或者add(1,2,3),这种用verilog没办法实现。这里通过遍历生成多个lazynode // 然后将所有的node组成数组传给adder,再通过adder遍历所有的node并加起来。 // 能实现的关键是lazymodue在module上再盖了一层关系,可以实现动态控制module和参数,实现verilog不支持的功能

object AdderNodeImp extends SimpleNodeImp[DownwardParam, UpwardParam, EdgeParam, UInt] { // 生成edge的条件判断 def edge(pd: DownwardParam, pu: UpwardParam, p: Parameters, sourceInfo: SourceInfo): EdgeParam = { if (pd.width < pu.width) EdgeParam(pd.width) else EdgeParam(pu.width) } def bundle(e: EdgeParam) = UInt(e.width.W) def render(e: EdgeParam) = RenderedEdge("blue", "width = ${e.width}") } // SourceNode类型的结点,只有输出没有输入。 // SinkNode类型的结点,只有输入没有输出。 // NexusNode类型的结点,输入输出都有若干。

class AdderDriverNode(widths: Seq[DownwardParam])(implicit valName: ValName) extends SourceNode(AdderNodeImp)(widths) // class AdderMonitorNode(width: UpwardParam)(implicit valName: ValName) extends SinkNode(AdderNodeImp)(Seq(width))

class AdderNode(dFn: Seq[DownwardParam] => DownwardParam, uFn: Seq[UpwardParam] => UpwardParam)(implicit valName: ValName) extends NexusNode(AdderNodeImp)(dFn, uFn) // 有输入和输出,这里调动参数协商 // 可输入输出虽然对外的api都是node,但是node是不同的对象 class Adder(implicit p:Parameters) extends LazyModule { // 传入两个函数 // 在AdderTestHarness讲node连接到数据产生driver(SourceNode)和验证monitor(SinkNode) val node = new AdderNode ( // 每次添加node根据不同的node的类型加入到不同的head中 { // 这里的冒号是声明类别的意思,所以真正的函数只有dps => dps.head case dps: Seq[DownwardParam] => require(dps.forall(dp => dp.width == dps.head.width), "inward, downward widths not equivalent") // 真正返回的是这个 dps.head }, {

  case ups: Seq[UpwardParam] =>
    require(ups.forall(up => up.width == ups.head.width), "outward, upward widths not equivalent")
    // 真正返回的是这个
    ups.head
}

) lazy val module: LazyModuleImp = new LazyModuleImp(this) { require(node.in.size >= 2) // 执行加法,计算结果 // unzip函数的作用是把node.in中的元组拆开,node.in的元组实际上是bundle和edge的集合(Seq[(Bi,Ei)]),用unzip拆出bundle来。 node.out.head._1 := node.in.unzip.1.reduce( + _) }

// override lazy val desireName = "Adder" } // 单个产生随机数的节点 class AdderDriver(width: Int, numOutputs: Int)(implicit p: Parameters) extends LazyModule { // 创建源节点 val node = new AdderDriverNode(Seq.fill(numOutputs)(DownwardParam(width)))

lazy val module = new LazyModuleImp(this) { val negotiatedWidths = node.edges.out.map(.width) val finalWidth = negotiatedWidths.head // 产生随机数 val randomAddend = FibonacciLFSR.maxPeriod(finalWidth) // 输出随机数 node.out.foreach { case (addend, ) => addend := randomAddend} }

// override lazy val desiredName = "AdderDriver" }

class AdderMonitor(width: Int, numOperands: Int)(implicit p:Parameters) extends LazyModule { // 初始化两个monitornode val nodeSeq = Seq.fill(numOperands) { new AdderMonitorNode(UpwardParam(width)) } var nodeSum = new AdderMonitorNode(UpwardParam(width))(ValName("test"))

lazy val module = new LazyModuleImp(this) { val io = IO(new Bundle { val error = Output(Bool()) }) // 简单验证加法的结果 io.error := nodeSum.in.head.1 =/= nodeSeq.map(.in.head.1).reduce( + _) }

// override lazy val desireName = "AdderMonitor" }

class AdderTestHarness()(implicit p: Parameters) extends LazyModule { // 这里是连系代码,不会放到module里面 val numOperands = 2 // 获取module对象,此时还没执行adder的module,但是会执行mdule外部的东西 val adder = LazyModule(new Adder) // 关键!!!!!创建不确定数量的产生随机数的driver,这里是在module执行之外 // module里面是硬件,关系全部构建在软件层!!!!!!!!!!!!!!! val drivers = Seq.fill(numOperands) { LazyModule(new AdderDriver(width = 8, numOutputs = 2))} val monitor = LazyModule(new AdderMonitor(width = 4, numOperands = numOperands)) // 连接adder和driver计算结果,driver的node也只能是输出的 // node是自动+1的 drivers.foreach{ driver => adder.node := driver.node } // 连接driver和monitor验证结果,monitor只有输入的node drivers.zip(monitor.nodeSeq).foreach { case (driver, monitorNode) => monitorNode := driver.node} // adder只有输出的node monitor.nodeSum := adder.node // 这里面的module是真正的module,module外面的东西是能被调用而不用执行module的 lazy val module = new LazyModuleImp(this) { val io = IO(new Bundle{ val finished = Output(Bool()) }) when(monitor.module.io.error) { printf("something went wrong") } // 没有error就完成 io.finished := monitor.module.io.error }

override lazy val desiredName = "AdderTestHarness" }

class Helloworld()(implicit p: Parameters) extends Module{ val io = IO(new Bundle{val success = Output(Bool())}) val ldut = LazyModule(new AdderTestHarness()) // 真正创建所有module和执行module val dut = Module(ldut.module) io.success := dut.io.finished }

object Helloworld extends App { Driver.emitVerilog(new Helloworld()(Parameters.empty)) }