chipyard clock

chipyard如何去分发时钟给每个模块,我该如何添加一个模块时钟?(仿真系统)

下面是大小核心校验的参数,可以看到每个核心都设置了时钟频率,这些时钟频率会组织为一个函数数组,送入config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyRocketConfig extends Config(
new chipyard.config.WithTileFrequency(100, Some(0)) ++
new chipyard.config.WithTileFrequency(50, Some(1)) ++
new chipyard.config.WithTileFrequency(50, Some(2)) ++
new chipyard.config.WithGCBusFrequency(50) ++
new chipyard.config.WithSystemBusFrequency(100) ++
new chipyard.config.WithSystemBusFrequencyAsDefault ++
new freechips.rocketchip.guardiancouncil.WithGHE ++
new freechips.rocketchip.subsystem.WithAsynchronousRocketTiles(
AsynchronousCrossing().depth,
AsynchronousCrossing().sourceSync) ++
// Crossing specifications
new boom.common.WithNMegaBooms(1, overrideIdOffset=Some(0)) ++
new freechips.rocketchip.subsystem.WithNGCCheckers(GH_GlobalParams.GH_NUM_CORES - 1, overrideIdOffset=Some(1)) ++
new chipyard.config.AbstractConfig
)

之后ClockFrequencyAssignersKey存放着函数对

1
2
3
4
class ClockNameContainsAssignment(name: String, fMHz: Double) extends Config((site, here, up) => {
case ClockFrequencyAssignersKey => up(ClockFrequencyAssignersKey, site) ++
Seq((cName: String) => if (cName.contains(name)) Some(fMHz) else None)
})

接下来看时钟是如何连接的:

首先看WithDividerOnlyClockGenerator,这个模块定义了ref时钟的node,然后时钟的分频在DividerOnlyClockGenerator模块中

这里可以理解为AXI xbar,但只有一个输入,多个输出,dividedClocks.getOrElse(div, instantiateDivider(div))承担了为每个tile分配相应的频率

1
2
3
4
5
6
for (((sinkBName, sinkB), sinkP) <- outClocks.member.elements.zip(outSinkParams.members)) {
val div = pllConfig.sinkDividerMap(sinkP)
sinkB.clock := dividedClocks.getOrElse(div, instantiateDivider(div))
// Reset handling and synchronization is expected to be handled by a downstream node
sinkB.reset := refClock.reset
}

然后这个模块调用了

1
2
val pllConfig = new SimplePllConfiguration(pllName, outSinkParams.members)

也就是pllConfig,这个模块主要是获得sink node的信息(频率等),然后根据ref时钟给出分频系数,也就是上面的div,然后dividedClocks就根据不同的kay或者对应的val(clock)分发给sink,至此完成了ref时钟到sink时钟的分发

那么如何得知ref clock的node从何处传入的呢?通过WithDividerOnlyClockGenerator可以得知他连接的输出IO clock

1
2
3
4
5
6
7
8
9
10
11
12
13
InModuleBody {
val clock_wire = Wire(Input(new ClockWithFreq(dividerOnlyClockGen.module.referenceFreq)))
val reset_wire = Wire(Input(AsyncReset()))
val (clock_io, clockIOCell) = IOCell.generateIOFromSignal(clock_wire, "clock", p(IOCellKey))
val (reset_io, resetIOCell) = IOCell.generateIOFromSignal(reset_wire, "reset", p(IOCellKey))

referenceClockSource.out.unzip._1.map { o =>
o.clock := clock_wire.clock
o.reset := reset_wire
}

(Seq(clock_io, reset_io), clockIOCell ++ resetIOCell)
}

但referenceFreq从何产生的呢?

似乎是tile中的最大值?

如何分发时钟呢?

WithDividerOnlyClockGenerator模块有node连接,这里就是将ref时钟分发给tile

1
2
3
(system.allClockGroupsNode
:= dividerOnlyClockGen.node
:= referenceClockSource)

所以找allClockGroupsNode,该node为trait HasChipyardPRCI的一个node,该node最后会连接到aggregator,而aggregator会分发时钟到两个部分:subsystem和asyncClockGroupsNode,然后chiptop会调用这个connectImplicitClockSinkNode函数从而完成subsystem分发

1
2
3
4
5
6
7
8
9
10
11
12
13
def connectImplicitClockSinkNode(sink: ClockSinkNode) = {
val implicitClockGrouper = this { ClockGroup() }
(sink
:= implicitClockGrouper
:= aggregator)
}
(aggregator
:= frequencySpecifier
:= clockGroupCombiner
:= resetSynchronizer
:= tileClockGater.map(_.clockNode).getOrElse(ClockGroupEphemeralNode()(ValName("temp")))
:= tileResetSetter.map(_.clockNode).getOrElse(ClockGroupEphemeralNode()(ValName("temp")))
:= allClockGroupsNode)

实际上ClockGroupFrequencySpecifier模块对之前的ClockFrequencyAssignersKey作出了处理

这里首先输入了函数数组,之后对ClockParameters进行模式匹配,如果子边没有设置ClockParameters(None),此时搜索这个表,匹配相应的name,给出对应的频率,如果子边匹配到了,那么直接返回子边的ClockParameters参数

每个clock必须由assign name,否则无法完成模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def apply(
assigners: Seq[(String) => Option[Double]],
defaultFreq: Double)(
implicit p: Parameters, valName: ValName): ClockGroupAdapterNode = {

def lookupFrequencyForName(clock: ClockSinkParameters): ClockSinkParameters = {
require(clock.name.nonEmpty, "All clocks in clock group must have an assigned name")
val clockFreq = assigners.foldLeft(defaultFreq)(
(currentFreq, candidateFunc) => candidateFunc(clock.name.get).getOrElse(currentFreq))

clock.copy(take = clock.take match {
case Some(cp) =>
println(s"Clock ${clock.name.get}: using diplomatically specified frequency of ${cp.freqMHz}.")
Some(cp)
case None => Some(ClockParameters(clockFreq))
})
}

这里的意思就是如果你你开始没有设置ClockParameters(node传入的ClockParameters为none),那么他会在函数数组找到特定的频率,然后分发给tile

然后查看connectImplicitClockSinkNode,最后又回到了WithDividerOnlyClockGenerator,这一小段代码将node连接到每个tile(只要是basesubsystem就可以分发)

1
2
3
4
5
6
7
8
9
10
11
implicit val p = GetSystemParameters(system)
val implicitClockSinkNode = ClockSinkNode(Seq(ClockSinkParameters(name = Some("implicit_clock"))))
system.connectImplicitClockSinkNode(implicitClockSinkNode)
InModuleBody {
val implicit_clock = implicitClockSinkNode.in.head._1.clock
val implicit_reset = implicitClockSinkNode.in.head._1.reset
system.asInstanceOf[BaseSubsystem].module match { case l: LazyModuleImp => {
l.clock := implicit_clock
l.reset := implicit_reset
}}
}

真的需要大小核心频率不同吗?大小核心如果频率不同,那么他们送入L2Cache的时钟也不同,可能出现问题