diff --git a/.gitmodules b/.gitmodules index 340826f..3dd7b40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,9 @@ [submodule "boom"] path = boom url = https://github.com/ucb-bar/riscv-boom.git +[submodule "firesim"] + path = firesim + url = https://github.com/firesim/firesim.git +[submodule "tools/firrtl"] + path = tools/firrtl + url = https://github.com/freechipsproject/firrtl.git diff --git a/Makefrag b/Makefrag index 786eb81..e7548fd 100644 --- a/Makefrag +++ b/Makefrag @@ -1,20 +1,26 @@ ROCKETCHIP_DIR=$(base_dir)/rocket-chip -SBT ?= java -Xmx2G -Xss8M -XX:MaxPermSize=256M -jar $(ROCKETCHIP_DIR)/sbt-launch.jar ++2.12.4 +SBT ?= java -Xmx16G -Xss8M -XX:MaxPermSize=256M -jar $(ROCKETCHIP_DIR)/sbt-launch.jar ++2.12.4 lookup_scala_srcs = $(shell find $(1)/ -iname "*.scala" 2> /dev/null) -PACKAGES=rocket-chip testchipip icenet sifive-blocks +PACKAGES=testchipip icenet sifive-blocks \ + $(addprefix firesim/sim/, . midas midas/targetutils) \ + $(addprefix $(ROCKETCHIP_DIR)/, . hardfloat) + SCALA_SOURCES=$(foreach pkg,$(PACKAGES),$(call lookup_scala_srcs,$(base_dir)/$(pkg)/src/main/scala)) $(call lookup_scala_srcs,$(base_dir)/src/main/scala) -ROCKET_CLASSES ?= "$(ROCKETCHIP_DIR)/target/scala-2.12/classes:$(ROCKETCHIP_DIR)/chisel3/target/scala-2.12/*" +# NB: The rocketchip hack below +ROCKET_CLASSES ?= $(ROCKETCHIP_DIR)/src/target/scala-2.12/classes:$(ROCKETCHIP_DIR)/chisel3/target/scala-2.12/* -FIRRTL_JAR ?= $(ROCKETCHIP_DIR)/lib/firrtl.jar +FIRRTL_DIR ?= $(base_dir)/tools/firrtl +FIRRTL_JAR ?= $(base_dir)/lib/firrtl.jar +# TODO: FIXME FIRRTL ?= java -Xmx2G -Xss8M -XX:MaxPermSize=256M -cp $(ROCKET_CLASSES):$(FIRRTL_JAR) firrtl.Driver -$(FIRRTL_JAR): $(call lookup_scala_srcs, $(ROCKETCHIP_DIR)/firrtl/src/main/scala) - $(MAKE) -C $(ROCKETCHIP_DIR)/firrtl SBT="$(SBT)" root_dir=$(ROCKETCHIP_DIR)/firrtl build-scala - mkdir -p $(ROCKETCHIP_DIR)/lib - cp $(ROCKETCHIP_DIR)/firrtl/utils/bin/firrtl.jar $(FIRRTL_JAR) +$(FIRRTL_JAR): $(call lookup_scala_srcs, $(FIRRTL_DIR)/src/main/scala) + $(MAKE) -C $(FIRRTL_DIR) SBT="$(SBT)" root_dir=$(FIRRTL_DIR) build-scala + mkdir -p $(@D) + cp $(FIRRTL_DIR)/utils/bin/firrtl.jar $(FIRRTL_JAR) build_dir=$(sim_dir)/generated-src testchip_dir = $(base_dir)/testchipip diff --git a/build.sbt b/build.sbt index 25d8f87..5c7a9bd 100644 --- a/build.sbt +++ b/build.sbt @@ -6,6 +6,7 @@ lazy val commonSettings = Seq( scalacOptions ++= Seq("-deprecation","-unchecked","-Xsource:2.11"), libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.1" % "test", libraryDependencies += "org.json4s" %% "json4s-native" % "3.5.3", + libraryDependencies += "org.json4s" %% "json4s-jackson" % "3.5.3", libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value, addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full), resolvers ++= Seq( @@ -13,7 +14,45 @@ lazy val commonSettings = Seq( Resolver.sonatypeRepo("releases"), Resolver.mavenLocal)) -lazy val rocketchip = RootProject(file("rocket-chip")) +val rocketChipDir = file("rocket-chip") + +lazy val firesimAsLibrary = sys.env.get("FIRESIM_IS_TOP") == None +lazy val firesimDir = if (firesimAsLibrary) { + file("firesim/sim/") +} else { + file("../../sim/") +} + +// Subproject definitions begin +// NB: FIRRTL dependency is unmanaged (and dropped in sim/lib) +lazy val chisel = (project in rocketChipDir / "chisel3") + +// Contains annotations & firrtl passes you may wish to use in rocket-chip without +// introducing a circular dependency between RC and MIDAS +lazy val midasTargetUtils = ProjectRef(firesimDir, "targetutils") + +// Rocket-chip dependencies (subsumes making RC a RootProject) +lazy val hardfloat = (project in rocketChipDir / "hardfloat") + .settings( + commonSettings, + crossScalaVersions := Seq("2.11.12", "2.12.4")) + .dependsOn(chisel, midasTargetUtils) + + +lazy val macros = (project in rocketChipDir / "macros") + .settings(commonSettings, + ) + +// HACK: I'm strugging to override settings in rocket-chip's build.sbt (i want +// the subproject to register a new library dependendency on midas's targetutils library) +// So instead, avoid the existing build.sbt altogether and specify the project's root at src/ + +lazy val rocketchip = (project in rocketChipDir / "src") + .settings( + commonSettings, + scalaSource in Compile := baseDirectory.value / "main" / "scala", + resourceDirectory in Compile := baseDirectory.value / "main" / "resources") + .dependsOn(chisel, hardfloat, macros) lazy val sifive_blocks = (project in file("sifive-blocks")).settings(commonSettings).dependsOn(rocketchip) @@ -23,4 +62,11 @@ lazy val icenet = project.settings(commonSettings).dependsOn(rocketchip, testchi lazy val boom = project.settings(commonSettings).dependsOn(rocketchip) -lazy val example = (project in file(".")).settings(commonSettings).dependsOn(boom, icenet, testchipip, sifive_blocks) +// The library components of FireSim +lazy val midas = ProjectRef(firesimDir, "midas") +lazy val firesim = ProjectRef(firesimDir, "common") + +lazy val firechip = (project in file(".")) + .settings(commonSettings) + .dependsOn(boom, icenet, testchipip, sifive_blocks, midasTargetUtils, midas, + firesim % "test->test;compile->compile") diff --git a/firesim b/firesim new file mode 160000 index 0000000..d5e7dee --- /dev/null +++ b/firesim @@ -0,0 +1 @@ +Subproject commit d5e7deedc0fe9f270f2da5c36d73337542b153c7 diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..31334bb --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.1.1 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..61fa965 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,19 @@ +resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven" + +resolvers += "simplytyped" at "http://simplytyped.github.io/repo/releases" + +addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2") + +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.3.1") + +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") + +addSbtPlugin("com.simplytyped" % "sbt-antlr4" % "0.8.1") + +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") + +addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.1") + +addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.9.3") + +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") diff --git a/src/main/scala/firesim/Generator.scala b/src/main/scala/firesim/Generator.scala new file mode 100755 index 0000000..2c30cd6 --- /dev/null +++ b/src/main/scala/firesim/Generator.scala @@ -0,0 +1,122 @@ +package firesim.firesim + +import java.io.{File} + +import chisel3.experimental.RawModule +import chisel3.internal.firrtl.{Circuit, Port} + +import freechips.rocketchip.diplomacy.{ValName, AutoBundle} +import freechips.rocketchip.devices.debug.DebugIO +import freechips.rocketchip.util.{HasGeneratorUtilities, ParsedInputNames, ElaborationArtefacts} +import freechips.rocketchip.system.DefaultTestSuites._ +import freechips.rocketchip.system.{TestGeneration, RegressionTestSuite} +import freechips.rocketchip.config.Parameters +import freechips.rocketchip.subsystem.RocketTilesKey +import freechips.rocketchip.tile.XLen + +import boom.system.{BoomTilesKey, BoomTestSuites} + +import firesim.util.{GeneratorArgs, HasTargetAgnosticUtilites, HasFireSimGeneratorUtilities} + +trait HasTestSuites { + val rv64RegrTestNames = collection.mutable.LinkedHashSet( + "rv64ud-v-fcvt", + "rv64ud-p-fdiv", + "rv64ud-v-fadd", + "rv64uf-v-fadd", + "rv64um-v-mul", + // "rv64mi-p-breakpoint", // Not implemented in BOOM + // "rv64uc-v-rvc", // Not implemented in BOOM + "rv64ud-v-structural", + "rv64si-p-wfi", + "rv64um-v-divw", + "rv64ua-v-lrsc", + "rv64ui-v-fence_i", + "rv64ud-v-fcvt_w", + "rv64uf-v-fmin", + "rv64ui-v-sb", + "rv64ua-v-amomax_d", + "rv64ud-v-move", + "rv64ud-v-fclass", + "rv64ua-v-amoand_d", + "rv64ua-v-amoxor_d", + "rv64si-p-sbreak", + "rv64ud-v-fmadd", + "rv64uf-v-ldst", + "rv64um-v-mulh", + "rv64si-p-dirty") + + val rv32RegrTestNames = collection.mutable.LinkedHashSet( + "rv32mi-p-ma_addr", + "rv32mi-p-csr", + "rv32ui-p-sh", + "rv32ui-p-lh", + "rv32uc-p-rvc", + "rv32mi-p-sbreak", + "rv32ui-p-sll") + + def addTestSuites(targetName: String, params: Parameters) { + val coreParams = + if (params(RocketTilesKey).nonEmpty) { + params(RocketTilesKey).head.core + } else { + params(BoomTilesKey).head.core + } + val xlen = params(XLen) + val vm = coreParams.useVM + val env = if (vm) List("p","v") else List("p") + coreParams.fpu foreach { case cfg => + if (xlen == 32) { + TestGeneration.addSuites(env.map(rv32uf)) + if (cfg.fLen >= 64) + TestGeneration.addSuites(env.map(rv32ud)) + } else { + TestGeneration.addSuite(rv32udBenchmarks) + TestGeneration.addSuites(env.map(rv64uf)) + if (cfg.fLen >= 64) + TestGeneration.addSuites(env.map(rv64ud)) + } + } + if (coreParams.useAtomics) TestGeneration.addSuites(env.map(if (xlen == 64) rv64ua else rv32ua)) + if (coreParams.useCompressed) TestGeneration.addSuites(env.map(if (xlen == 64) rv64uc else rv32uc)) + val (rvi, rvu) = + if (params(BoomTilesKey).nonEmpty) ((if (vm) BoomTestSuites.rv64i else BoomTestSuites.rv64pi), rv64u) + else if (xlen == 64) ((if (vm) rv64i else rv64pi), rv64u) + else ((if (vm) rv32i else rv32pi), rv32u) + + TestGeneration.addSuites(rvi.map(_("p"))) + TestGeneration.addSuites((if (vm) List("v") else List()).flatMap(env => rvu.map(_(env)))) + TestGeneration.addSuite(benchmarks) + TestGeneration.addSuite(new RegressionTestSuite(if (xlen == 64) rv64RegrTestNames else rv32RegrTestNames)) + TestGeneration.addSuite(FastBlockdevTests) + TestGeneration.addSuite(SlowBlockdevTests) + if (!targetName.contains("NoNIC")) + TestGeneration.addSuite(NICLoopbackTests) + } +} + +// Mixed into an App or into a TestSuite +trait IsFireSimGeneratorLike extends HasFireSimGeneratorUtilities with HasTestSuites { + /** Output software test Makefrags, which provide targets for integration testing. */ + def generateTestSuiteMakefrags { + addTestSuites(names.topModuleClass, targetParams) + writeOutputFile(s"$longName.d", TestGeneration.generateMakefrag) // Subsystem-specific test suites + } + + // Output miscellaneous files produced as a side-effect of elaboration + def generateArtefacts { + ElaborationArtefacts.files.foreach { case (extension, contents) => + writeOutputFile(s"${longName}.${extension}", contents ()) + } + } +} + +object FireSimGenerator extends App with IsFireSimGeneratorLike { + lazy val generatorArgs = GeneratorArgs(args) + lazy val genDir = new File(names.targetDir) + + elaborateAndCompileWithMidas + generateTestSuiteMakefrags + generateHostVerilogHeader + generateArtefacts +} diff --git a/src/main/scala/firesim/SimConfigs.scala b/src/main/scala/firesim/SimConfigs.scala new file mode 100644 index 0000000..6d0a398 --- /dev/null +++ b/src/main/scala/firesim/SimConfigs.scala @@ -0,0 +1,131 @@ +package firesim.firesim + +import freechips.rocketchip.config.{Parameters, Config, Field} + +import midas.{EndpointKey} +import midas.widgets.{EndpointMap} +import midas.models._ + +import testchipip.{WithBlockDevice} + +import firesim.endpoints._ +import firesim.configs._ + +class WithSerialWidget extends Config((site, here, up) => { + case EndpointKey => up(EndpointKey) ++ EndpointMap(Seq(new SimSerialIO)) +}) + +class WithUARTWidget extends Config((site, here, up) => { + case EndpointKey => up(EndpointKey) ++ EndpointMap(Seq(new SimUART)) +}) + +class WithSimpleNICWidget extends Config((site, here, up) => { + case EndpointKey => up(EndpointKey) ++ EndpointMap(Seq(new SimSimpleNIC)) + case LoopbackNIC => false +}) + +class WithBlockDevWidget extends Config((site, here, up) => { + case EndpointKey => up(EndpointKey) ++ EndpointMap(Seq(new SimBlockDev)) +}) + +class WithTracerVWidget extends Config((site, here, up) => { + case midas.EndpointKey => up(midas.EndpointKey) ++ + EndpointMap(Seq(new SimTracerV)) +}) + +/******************************************************************************* +* Full PLATFORM_CONFIG Configurations. These set simulator parameters. +* +* In general, if you're adding or removing features from any of these, you +* should CREATE A NEW ONE, WITH A NEW NAME. This is because the manager +* will store this name as part of the tags for the AGFI, so that later you can +* reconstruct what is in a particular AGFI. These tags are also used to +* determine which driver to build. +*******************************************************************************/ +class FireSimConfig extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new WithDefaultMemModel ++ + new WithTracerVWidget ++ + new BasePlatformConfig) + +class FireSimConfig160MHz extends Config( + new WithDesiredHostFrequency(160) ++ + new FireSimConfig) + +class FireSimConfig90MHz extends Config( + new WithDesiredHostFrequency(90) ++ + new FireSimConfig) + +class FireSimConfig75MHz extends Config( + new WithDesiredHostFrequency(75) ++ + new FireSimConfig) + +class FireSimClockDivConfig extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new WithDefaultMemModel(clockDivision = 2) ++ + new BasePlatformConfig) + +class FireSimDDR3Config extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new FCFS16GBQuadRank ++ + new BasePlatformConfig) + +class FireSimDDR3LLC4MBConfig extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new FCFS16GBQuadRankLLC4MB ++ + new BasePlatformConfig) + +class FireSimDDR3FRFCFSConfig extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new FRFCFS16GBQuadRank ++ + new BasePlatformConfig) + +class FireSimDDR3FRFCFSLLC4MBConfig extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new FRFCFS16GBQuadRankLLC4MB ++ + new BasePlatformConfig) + +class FireSimDDR3FRFCFSLLC4MBConfig160MHz extends Config( + new WithDesiredHostFrequency(160) ++ + new FireSimDDR3FRFCFSLLC4MBConfig) + +class FireSimDDR3FRFCFSLLC4MBConfig90MHz extends Config( + new WithDesiredHostFrequency(90) ++ + new FireSimDDR3FRFCFSLLC4MBConfig) + +class FireSimDDR3FRFCFSLLC4MBConfig75MHz extends Config( + new WithDesiredHostFrequency(75) ++ + new FireSimDDR3FRFCFSLLC4MBConfig) + +class FireSimDDR3FRFCFSLLC4MB3ClockDivConfig extends Config( + new WithDesiredHostFrequency(90) ++ + new WithSerialWidget ++ + new WithUARTWidget ++ + new WithSimpleNICWidget ++ + new WithBlockDevWidget ++ + new FRFCFS16GBQuadRankLLC4MB3Div ++ + new BasePlatformConfig) diff --git a/src/main/scala/firesim/TargetConfigs.scala b/src/main/scala/firesim/TargetConfigs.scala new file mode 100644 index 0000000..2c38050 --- /dev/null +++ b/src/main/scala/firesim/TargetConfigs.scala @@ -0,0 +1,168 @@ +package firesim.firesim + +import java.io.File + +import freechips.rocketchip.config.{Parameters, Config} +import freechips.rocketchip.tile._ +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.subsystem._ +import freechips.rocketchip.devices.tilelink.BootROMParams +import boom.system.BoomTilesKey +import testchipip.{WithBlockDevice, BlockDeviceKey, BlockDeviceConfig} +import sifive.blocks.devices.uart.{PeripheryUARTKey, UARTParams} +import icenet._ + +class WithBootROM extends Config((site, here, up) => { + case BootROMParams => { + val rebarBootROM = new File(s"./testchipip/bootrom/bootrom.rv${site(XLen)}.img") + val firesimBootROM = new File(s"./target-rtl/testchipip/bootrom/bootrom.rv${site(XLen)}.img") + + val bootROMPath = if (rebarBootROM.exists()) { + rebarBootROM.getAbsolutePath() + } else { + firesimBootROM.getAbsolutePath() + } + BootROMParams(contentFileName = bootROMPath) + } +}) + +class WithPeripheryBusFrequency(freq: BigInt) extends Config((site, here, up) => { + case PeripheryBusKey => up(PeripheryBusKey).copy(frequency=freq) +}) + +class WithUARTKey extends Config((site, here, up) => { + case PeripheryUARTKey => List(UARTParams( + address = BigInt(0x54000000L), + nTxEntries = 256, + nRxEntries = 256)) +}) + +class WithNICKey extends Config((site, here, up) => { + case NICKey => NICConfig( + inBufPackets = 64, + ctrlQueueDepth = 64) +}) + +class WithRocketL2TLBs(entries: Int) extends Config((site, here, up) => { + case RocketTilesKey => up(RocketTilesKey) map (tile => tile.copy( + core = tile.core.copy( + nL2TLBEntries = entries + ) + )) +}) + +class WithPerfCounters extends Config((site, here, up) => { + case RocketTilesKey => up(RocketTilesKey) map (tile => tile.copy( + core = tile.core.copy(nPerfCounters = 29) + )) +}) + +class WithBoomL2TLBs(entries: Int) extends Config((site, here, up) => { + case BoomTilesKey => up(BoomTilesKey) map (tile => tile.copy( + core = tile.core.copy(nL2TLBEntries = entries) + )) +}) + +class WithTraceRocket extends Config((site, here, up) => { + case RocketTilesKey => up(RocketTilesKey, site) map { r => r.copy(trace = true) } +}) + +class WithTraceBoom extends Config((site, here, up) => { + case BoomTilesKey => up(BoomTilesKey, site) map { r => r.copy(trace = true) } +}) + + +/******************************************************************************* +* Full TARGET_CONFIG configurations. These set parameters of the target being +* simulated. +* +* In general, if you're adding or removing features from any of these, you +* should CREATE A NEW ONE, WITH A NEW NAME. This is because the manager +* will store this name as part of the tags for the AGFI, so that later you can +* reconstruct what is in a particular AGFI. These tags are also used to +* determine which driver to build. +*******************************************************************************/ +class FireSimRocketChipConfig extends Config( + new WithBootROM ++ + new WithPeripheryBusFrequency(BigInt(3200000000L)) ++ + new WithExtMemSize(0x400000000L) ++ // 16GB + new WithoutTLMonitors ++ + new WithUARTKey ++ + new WithNICKey ++ + new WithBlockDevice ++ + new WithRocketL2TLBs(1024) ++ + new WithPerfCounters ++ + new freechips.rocketchip.system.DefaultConfig) + +class WithNDuplicatedRocketCores(n: Int) extends Config((site, here, up) => { + case RocketTilesKey => List.tabulate(n)(i => up(RocketTilesKey).head.copy(hartId = i)) +}) + +class FireSimRocketChipTracedConfig extends Config( + new WithTraceRocket ++ new FireSimRocketChipConfig) + +// single core config +class FireSimRocketChipSingleCoreConfig extends Config(new FireSimRocketChipConfig) + +class FireSimRocketChipSingleCoreTracedConfig extends Config( + new WithTraceRocket ++ new FireSimRocketChipSingleCoreConfig) + +// dual core config +class FireSimRocketChipDualCoreConfig extends Config( + new WithNDuplicatedRocketCores(2) ++ + new FireSimRocketChipSingleCoreConfig) + +class FireSimRocketChipDualCoreTracedConfig extends Config( + new WithTraceRocket ++ new FireSimRocketChipDualCoreConfig) + +// quad core config +class FireSimRocketChipQuadCoreConfig extends Config( + new WithNDuplicatedRocketCores(4) ++ + new FireSimRocketChipSingleCoreConfig) + +class FireSimRocketChipQuadCoreTracedConfig extends Config( + new WithTraceRocket ++ new FireSimRocketChipQuadCoreConfig) + +// hexa core config +class FireSimRocketChipHexaCoreConfig extends Config( + new WithNDuplicatedRocketCores(6) ++ + new FireSimRocketChipSingleCoreConfig) + +class FireSimRocketChipHexaCoreTracedConfig extends Config( + new WithTraceRocket ++ new FireSimRocketChipHexaCoreConfig) + +// octa core config +class FireSimRocketChipOctaCoreConfig extends Config( + new WithNDuplicatedRocketCores(8) ++ + new FireSimRocketChipSingleCoreConfig) + +class FireSimRocketChipOctaCoreTracedConfig extends Config( + new WithTraceRocket ++ new FireSimRocketChipOctaCoreConfig) + +class FireSimBoomConfig extends Config( + new WithBootROM ++ + new WithPeripheryBusFrequency(BigInt(3200000000L)) ++ + new WithExtMemSize(0x400000000L) ++ // 16GB + new WithoutTLMonitors ++ + new WithUARTKey ++ + new WithNICKey ++ + new WithBlockDevice ++ + new WithBoomL2TLBs(1024) ++ + //new WithBoomSynthAssertExcludes ++ // Will do nothing unless assertion synth is enabled + // Using a small config because it has 64-bit system bus, and compiles quickly + new boom.system.SmallBoomConfig) + +// A safer implementation than the one in BOOM in that it +// duplicates whatever BOOMTileKey.head is present N times. This prevents +// accidentally (and silently) blowing away configurations that may change the +// tile in the "up" view +class WithNDuplicatedBoomCores(n: Int) extends Config((site, here, up) => { + case BoomTilesKey => List.tabulate(n)(i => up(BoomTilesKey).head.copy(hartId = i)) +}) + +class FireSimBoomDualCoreConfig extends Config( + new WithNDuplicatedBoomCores(2) ++ + new FireSimBoomConfig) + +class FireSimBoomTracedConfig extends Config( + new WithTraceBoom ++ new FireSimBoomConfig) diff --git a/src/main/scala/firesim/TargetLandTestSuites.scala b/src/main/scala/firesim/TargetLandTestSuites.scala new file mode 100644 index 0000000..b20fa8d --- /dev/null +++ b/src/main/scala/firesim/TargetLandTestSuites.scala @@ -0,0 +1,45 @@ +//See LICENSE for license details. +package firesim.firesim + +import scala.collection.mutable.LinkedHashSet + +import freechips.rocketchip.system.{TestGeneration, RocketTestSuite} + +/* This imports tests from FireChip to test devices that aren't natively + * tested by the riscv assembly tests. + * Firesim's target-specific makefrag gives the recipes for building the + * binaries. + */ + +class BlockdevTestSuite(prefix: String, val names: LinkedHashSet[String]) extends RocketTestSuite { + val envName = "" + // fc_test_dir is is defined in firesim's Makefrag + val dir = "$(fc_test_dir)" + val makeTargetName = prefix + "-blkdev-tests" + def kind = "blockdev" + // Blockdev tests need an image, which complicates this + def additionalArgs = "+blkdev-in-mem0=128 +nic-loopback0" + override def toString = s"$makeTargetName = \\\n" + + // Make variable with the binaries of the suite + names.map(n => s"\t$n.riscv").mkString(" \\\n") + "\n\n" + + // Variables with binary specific arguments + names.map(n => s"$n.riscv_ARGS=$additionalArgs").mkString(" \n") + + postScript +} + +object FastBlockdevTests extends BlockdevTestSuite("fast", LinkedHashSet("blkdev")) +object SlowBlockdevTests extends BlockdevTestSuite("slow", LinkedHashSet("big-blkdev")) + +class NICTestSuite(prefix: String, val names: LinkedHashSet[String]) extends RocketTestSuite { + val envName = "" + val dir = "$(fc_test_dir)" + val makeTargetName = prefix + "-nic-tests" + def kind = "nic" + def additionalArgs = "+netbw0=100 +linklatency0=6405 +netburst0=8 +slotid=0 +nic-loopback0" + override def toString = s"$makeTargetName = \\\n" + + names.map(n => s"\t$n.riscv").mkString(" \\\n") + "\n\n" + + names.map(n => s"$n.riscv_ARGS=$additionalArgs").mkString(" \n") + + postScript +} + +object NICLoopbackTests extends NICTestSuite("loopback", LinkedHashSet("nic-loopback")) diff --git a/src/main/scala/firesim/TargetMixins.scala b/src/main/scala/firesim/TargetMixins.scala new file mode 100644 index 0000000..8495be9 --- /dev/null +++ b/src/main/scala/firesim/TargetMixins.scala @@ -0,0 +1,95 @@ +package firesim.firesim + +import chisel3._ +import freechips.rocketchip.config.{Field, Parameters} +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.subsystem._ +import freechips.rocketchip.amba.axi4._ +import freechips.rocketchip.util._ +import freechips.rocketchip.rocket.TracedInstruction +import boom.system.BoomSubsystem +import midas.targetutils.ExcludeInstanceAsserts +import midas.models.AXI4BundleWithEdge + + +class TraceOutputTop(val numTraces: Int)(implicit val p: Parameters) extends Bundle { + val traces = Vec(numTraces, new TracedInstruction) +} + +/** Adds a port to the system intended to master an AXI4 DRAM controller. */ +trait CanHaveMisalignedMasterAXI4MemPort { this: BaseSubsystem => + val module: CanHaveMisalignedMasterAXI4MemPortModuleImp + + private val memPortParamsOpt = p(ExtMem) + private val portName = "axi4" + private val device = new MemoryDevice + val nMemoryChannels: Int + + val memAXI4Node = AXI4SlaveNode(Seq.tabulate(nMemoryChannels) { channel => + val params = memPortParamsOpt.get + val base = AddressSet.misaligned(params.base, params.size) + + AXI4SlavePortParameters( + slaves = Seq(AXI4SlaveParameters( + address = base, + resources = device.reg, + regionType = RegionType.UNCACHED, // cacheable + executable = true, + supportsWrite = TransferSizes(1, cacheBlockBytes), + supportsRead = TransferSizes(1, cacheBlockBytes), + interleavedId = Some(0))), // slave does not interleave read responses + beatBytes = params.beatBytes) + }) + + memPortParamsOpt.foreach { params => + memBuses.map { m => + memAXI4Node := m.toDRAMController(Some(portName)) { + (AXI4UserYanker() := AXI4IdIndexer(params.idBits) := TLToAXI4()) + } + } + } +} + +/** Actually generates the corresponding IO in the concrete Module */ +trait CanHaveMisalignedMasterAXI4MemPortModuleImp extends LazyModuleImp { + val outer: CanHaveMisalignedMasterAXI4MemPort + + val mem_axi4 = IO(new HeterogeneousBag(outer.memAXI4Node.in map AXI4BundleWithEdge.apply)) + (mem_axi4 zip outer.memAXI4Node.in).foreach { case (io, (bundle, _)) => io <> bundle } + + def connectSimAXIMem() { + (mem_axi4 zip outer.memAXI4Node.in).foreach { case (io, (_, edge)) => + val mem = LazyModule(new SimAXIMem(edge, size = p(ExtMem).get.size)) + Module(mem.module).io.axi4.head <> io + } + } +} + +trait CanHaveRocketTraceIO extends LazyModuleImp { + val outer: RocketSubsystem + + val traced_params = outer.rocketTiles(0).p + val tile_traces = outer.rocketTiles flatMap (tile => tile.module.trace.getOrElse(Nil)) + val traceIO = IO(Output(new TraceOutputTop(tile_traces.length)(traced_params))) + traceIO.traces zip tile_traces foreach ({ case (ioconnect, trace) => ioconnect := trace }) + + println(s"N tile traces: ${tile_traces.size}") +} + +trait CanHaveBoomTraceIO extends LazyModuleImp { + val outer: BoomSubsystem + + val traced_params = outer.boomTiles(0).p + val tile_traces = outer.boomTiles flatMap (tile => tile.module.trace.getOrElse(Nil)) + val traceIO = IO(Output(new TraceOutputTop(tile_traces.length)(traced_params))) + traceIO.traces zip tile_traces foreach ({ case (ioconnect, trace) => ioconnect := trace }) + + println(s"N tile traces: ${tile_traces.size}") +} + +// Prevent MIDAS from synthesizing assertions in the dummy TLB included in BOOM +trait ExcludeInvalidBoomAssertions extends LazyModuleImp { + ExcludeInstanceAsserts(("NonBlockingDCache", "dtlb")) +} + diff --git a/src/main/scala/firesim/Targets.scala b/src/main/scala/firesim/Targets.scala new file mode 100755 index 0000000..0ebb60e --- /dev/null +++ b/src/main/scala/firesim/Targets.scala @@ -0,0 +1,224 @@ +package firesim.firesim + +import chisel3._ +import freechips.rocketchip._ +import freechips.rocketchip.subsystem._ +import freechips.rocketchip.diplomacy.LazyModule +import freechips.rocketchip.tilelink._ +import freechips.rocketchip.devices.tilelink._ +import freechips.rocketchip.config.Parameters +import freechips.rocketchip.util.HeterogeneousBag +import freechips.rocketchip.amba.axi4.AXI4Bundle +import freechips.rocketchip.config.{Field, Parameters} +import freechips.rocketchip.diplomacy.LazyModule +import boom.system.{BoomSubsystem, BoomSubsystemModule} +import icenet._ +import testchipip._ +import testchipip.SerialAdapter.SERIAL_IF_WIDTH +import sifive.blocks.devices.uart._ +import midas.targetutils.ExcludeInstanceAssertsAnnotation +import midas.models.AXI4BundleWithEdge +import java.io.File + +case object NumNodes extends Field[Int] + +/******************************************************************************* +* Top level DESIGN configurations. These describe the basic instantiations of +* the designs being simulated. +* +* In general, if you're adding or removing features from any of these, you +* should CREATE A NEW ONE, WITH A NEW NAME. This is because the manager +* will store this name as part of the tags for the AGFI, so that later you can +* reconstruct what is in a particular AGFI. These tags are also used to +* determine which driver to build. +*******************************************************************************/ + +class FireSim(implicit p: Parameters) extends RocketSubsystem + with CanHaveMisalignedMasterAXI4MemPort + with HasPeripheryBootROM +// with HasSystemErrorSlave + // with HasSyncExtInterrupts + with HasNoDebug + with HasPeripherySerial + with HasPeripheryUART + with HasPeripheryIceNIC + with HasPeripheryBlockDevice +{ + val hasTraces = rocketTiles.map(_.rocketParams.trace).reduce(_ || _) + + override lazy val module = new FireSimModuleImp(this) +// if (hasTraces) new FireSimModuleImpTraced(this) +// else new FireSimModuleImp(this) + + // Error device used for testing and to NACK invalid front port transactions + val error = LazyModule(new TLError(p(ErrorDeviceKey), sbus.beatBytes)) + // always buffer the error device because no one cares about its latency + sbus.coupleTo("slave_named_error"){ error.node := TLBuffer() := _ } +} + +class FireSimModuleImp[+L <: FireSim](l: L) extends RocketSubsystemModuleImp(l) + with HasRTCModuleImp + with CanHaveMisalignedMasterAXI4MemPortModuleImp + with HasPeripheryBootROMModuleImp + // with HasExtInterruptsModuleImp + with HasNoDebugModuleImp + with HasPeripherySerialModuleImp + with HasPeripheryUARTModuleImp + with HasPeripheryIceNICModuleImpValidOnly + with HasPeripheryBlockDeviceModuleImp + +class FireSimModuleImpTraced[+L <: FireSim](l: L) extends FireSimModuleImp(l) + with CanHaveRocketTraceIO + +class FireSimNoNIC(implicit p: Parameters) extends RocketSubsystem + with CanHaveMisalignedMasterAXI4MemPort + with HasPeripheryBootROM +// with HasSystemErrorSlave + // with HasSyncExtInterrupts + with HasNoDebug + with HasPeripherySerial + with HasPeripheryUART + with HasPeripheryBlockDevice +{ + val hasTraces = rocketTiles.map(_.rocketParams.trace).reduce(_ || _) + + override lazy val module = new FireSimNoNICModuleImp(this) +// if (hasTraces) new FireSimNoNICModuleImpTraced(this) +// else new FireSimNoNICModuleImp(this) + + // Error device used for testing and to NACK invalid front port transactions + val error = LazyModule(new TLError(p(ErrorDeviceKey), sbus.beatBytes)) + // always buffer the error device because no one cares about its latency + sbus.coupleTo("slave_named_error"){ error.node := TLBuffer() := _ } +} + +class FireSimNoNICModuleImp[+L <: FireSimNoNIC](l: L) extends RocketSubsystemModuleImp(l) + with HasRTCModuleImp + with CanHaveMisalignedMasterAXI4MemPortModuleImp + with HasPeripheryBootROMModuleImp + // with HasExtInterruptsModuleImp + with HasNoDebugModuleImp + with HasPeripherySerialModuleImp + with HasPeripheryUARTModuleImp + with HasPeripheryBlockDeviceModuleImp + +class FireSimNoNICModuleImpTraced[+L <: FireSimNoNIC](l: L) extends FireSimNoNICModuleImp(l) + with CanHaveRocketTraceIO + +class FireBoom(implicit p: Parameters) extends BoomSubsystem + with CanHaveMisalignedMasterAXI4MemPort + with HasPeripheryBootROM +// with HasSystemErrorSlave + // with HasSyncExtInterrupts + with HasNoDebug + with HasPeripherySerial + with HasPeripheryUART + with HasPeripheryIceNIC + with HasPeripheryBlockDevice +{ + val hasTraces = boomTiles.map(_.boomParams.trace).reduce(_ || _) + + override lazy val module = new FireBoomModuleImp(this) +// if (hasTraces) new FireBoomModuleImpTraced(this) +// else new FireBoomModuleImp(this) + + //ExcludeInstanceAssertsAnnotation(("NonBlockingDCache", "dtlb")) + + // Error device used for testing and to NACK invalid front port transactions + val error = LazyModule(new TLError(p(ErrorDeviceKey), sbus.beatBytes)) + // always buffer the error device because no one cares about its latency + sbus.coupleTo("slave_named_error"){ error.node := TLBuffer() := _ } +} + +class FireBoomModuleImp[+L <: FireBoom](l: L) extends BoomSubsystemModule(l) + with HasRTCModuleImp + with CanHaveMisalignedMasterAXI4MemPortModuleImp + with HasPeripheryBootROMModuleImp + // with HasExtInterruptsModuleImp + with HasNoDebugModuleImp + with HasPeripherySerialModuleImp + with HasPeripheryUARTModuleImp + with HasPeripheryIceNICModuleImpValidOnly + with HasPeripheryBlockDeviceModuleImp + with ExcludeInvalidBoomAssertions + +class FireBoomModuleImpTraced[+L <: FireBoom](l: L) extends FireBoomModuleImp(l) + with CanHaveBoomTraceIO + +class FireBoomNoNIC(implicit p: Parameters) extends BoomSubsystem + with CanHaveMisalignedMasterAXI4MemPort + with HasPeripheryBootROM +// with HasSystemErrorSlave + // with HasSyncExtInterrupts + with HasNoDebug + with HasPeripherySerial + with HasPeripheryUART + with HasPeripheryBlockDevice +{ + val hasTraces = boomTiles.map(_.boomParams.trace).reduce(_ || _) + + override lazy val module = new FireBoomNoNICModuleImp(this) +// if (hasTraces) new FireBoomNoNICModuleImpTraced(this) +// else new FireBoomNoNICModuleImp(this) + + // Error device used for testing and to NACK invalid front port transactions + val error = LazyModule(new TLError(p(ErrorDeviceKey), sbus.beatBytes)) + // always buffer the error device because no one cares about its latency + sbus.coupleTo("slave_named_error"){ error.node := TLBuffer() := _ } +} + +class FireBoomNoNICModuleImp[+L <: FireBoomNoNIC](l: L) extends BoomSubsystemModule(l) + with HasRTCModuleImp + with CanHaveMisalignedMasterAXI4MemPortModuleImp + with HasPeripheryBootROMModuleImp + // with HasExtInterruptsModuleImp + with HasNoDebugModuleImp + with HasPeripherySerialModuleImp + with HasPeripheryUARTModuleImp + with HasPeripheryBlockDeviceModuleImp + with ExcludeInvalidBoomAssertions + +class FireBoomNoNICModuleImpTraced[+L <: FireBoomNoNIC](l: L) extends FireBoomNoNICModuleImp(l) + with CanHaveBoomTraceIO + +class SupernodeIO( + nNodes: Int, + serialWidth: Int, + bagPrototype: HeterogeneousBag[AXI4BundleWithEdge])(implicit p: Parameters) + extends Bundle { + + val serial = Vec(nNodes, new SerialIO(serialWidth)) + val mem_axi = Vec(nNodes, bagPrototype.cloneType) + val bdev = Vec(nNodes, new BlockDeviceIO) + val net = Vec(nNodes, new NICIOvonly) + val uart = Vec(nNodes, new UARTPortIO) + + override def cloneType = new SupernodeIO(nNodes, serialWidth, bagPrototype).asInstanceOf[this.type] +} + + +class FireSimSupernode(implicit p: Parameters) extends Module { + val nNodes = p(NumNodes) + val nodes = Seq.fill(nNodes) { + Module(LazyModule(new FireSim).module) + } + + val io = IO(new SupernodeIO(nNodes, SERIAL_IF_WIDTH, nodes(0).mem_axi4)) + + io.mem_axi.zip(nodes.map(_.mem_axi4)).foreach { + case (out, mem_axi4) => out <> mem_axi4 + } + io.serial <> nodes.map(_.serial) + io.bdev <> nodes.map(_.bdev) + io.net <> nodes.map(_.net) + io.uart <> nodes.map(_.uart(0)) + nodes.foreach{ case n => { + n.debug.clockeddmi.get.dmi.req.valid := false.B + n.debug.clockeddmi.get.dmi.resp.ready := false.B + n.debug.clockeddmi.get.dmiClock := clock + n.debug.clockeddmi.get.dmiReset := reset.toBool + n.debug.clockeddmi.get.dmi.req.bits.data := DontCare + n.debug.clockeddmi.get.dmi.req.bits.addr := DontCare + n.debug.clockeddmi.get.dmi.req.bits.op := DontCare + } } +} diff --git a/src/main/scala/firesim/endpoints/BlockDevWidget.scala b/src/main/scala/firesim/endpoints/BlockDevWidget.scala new file mode 100644 index 0000000..3c9f1fa --- /dev/null +++ b/src/main/scala/firesim/endpoints/BlockDevWidget.scala @@ -0,0 +1,255 @@ +package firesim +package endpoints + +import chisel3._ +import chisel3.util._ +import chisel3.experimental.{DataMirror, Direction} +import freechips.rocketchip.config.Parameters +import freechips.rocketchip.util.DecoupledHelper + +import midas.core.{HostPort, IsRationalClockRatio, UnityClockRatio} +import midas.widgets._ +import midas.models.DynamicLatencyPipe +import testchipip.{BlockDeviceIO, BlockDeviceRequest, BlockDeviceData, BlockDeviceInfo, HasBlockDeviceParameters, BlockDeviceKey} + +class SimBlockDev( + override val clockRatio: IsRationalClockRatio = UnityClockRatio) + extends Endpoint { + def matchType(data: Data) = data match { + case channel: BlockDeviceIO => + DataMirror.directionOf(channel.req.valid) == Direction.Output + case _ => false + } + def widget(p: Parameters) = new BlockDevWidget()(p) + override def widgetName = "BlockDevWidget" +} + +class BlockDevWidgetIO(implicit val p: Parameters) extends EndpointWidgetIO()(p) { + val hPort = Flipped(HostPort(new BlockDeviceIO)) +} + +class BlockDevWidget(implicit p: Parameters) extends EndpointWidget()(p) { + // TODO use HasBlockDeviceParameters + val blockDevExternal = p(BlockDeviceKey) + val dataBytes = 512 + val sectorBits = 32 + val nTrackers = blockDevExternal.nTrackers + val tagBits = log2Up(nTrackers) + val nTrackerBits = log2Up(nTrackers+1) + val dataBitsPerBeat = 64 + val dataBeats = (dataBytes * 8) / dataBitsPerBeat // A transaction is thus dataBeats * len beats long + val sectorSize = log2Ceil(sectorBits/8) + val beatIdxBits = log2Ceil(dataBeats) + val pAddrBits = 32 // TODO: Make configurable somehow + // Timing parameters + val latencyBits = 24 + val defaultReadLatency = (1 << 8).U(latencyBits.W) + val defaultWriteLatency = (1 << 8).U(latencyBits.W) + + val io = IO(new BlockDevWidgetIO) + + val reqBuf = Module(new Queue(new BlockDeviceRequest, 10)) + val dataBuf = Module(new Queue(new BlockDeviceData, 32)) + + val rRespBuf = Module(new Queue(new BlockDeviceData, 32)) + val wAckBuf = Module(new Queue(UInt(tagBits.W), 4)) + + val target = io.hPort.hBits + val channelCtrlSignals = Seq(io.hPort.toHost.hValid, + io.hPort.fromHost.hReady, + io.tReset.valid) + val rRespStallN = Wire(Bool()) // Unset if the SW model hasn't returned the response data in time + val wAckStallN = Wire(Bool()) // As above, but with a write acknowledgement + val fixMeOnNextRocketBump = true.B + val tFireHelper = DecoupledHelper((channelCtrlSignals ++ Seq( + reqBuf.io.enq.ready, + dataBuf.io.enq.ready, + fixMeOnNextRocketBump, + rRespStallN, + wAckStallN)):_*) + + val tFire = tFireHelper.fire(fixMeOnNextRocketBump) // Dummy argument to get conjunction of all signals + // Decoupled helper can't exclude two bools unfortunately... + val targetReset = channelCtrlSignals.reduce(_ && _) && io.tReset.bits + + reqBuf.reset := reset.toBool || targetReset + dataBuf.reset := reset.toBool || targetReset + rRespBuf.reset := reset.toBool || targetReset + wAckBuf.reset := reset.toBool || targetReset + + io.hPort.toHost.hReady := tFireHelper.fire(io.hPort.toHost.hValid) + io.hPort.fromHost.hValid := tFireHelper.fire(io.hPort.fromHost.hReady) + io.tReset.ready := tFireHelper.fire(io.tReset.valid) + + reqBuf.io.enq.bits := target.req.bits + reqBuf.io.enq.valid := target.req.valid && tFireHelper.fire(reqBuf.io.enq.ready) + target.req.ready := true.B + + dataBuf.io.enq.bits := target.data.bits + dataBuf.io.enq.valid := target.data.valid && tFireHelper.fire(dataBuf.io.enq.ready) + target.data.ready := true.B + + // Begin Timing model + val tCycle = RegInit(0.U(latencyBits.W)) + when (tFire) { + tCycle := tCycle + 1.U + } + + // Timing model programmable settings + val readLatency = genWORegInit(Wire(UInt(latencyBits.W)), "read_latency", defaultReadLatency) + val writeLatency = genWORegInit(Wire(UInt(latencyBits.W)), "write_latency", defaultWriteLatency) + + chisel3.experimental.withReset(reset.toBool || targetReset) { + when (tFire) { + assert(!target.req.fire || ((dataBeats.U * target.req.bits.len) < ((BigInt(1) << sectorBits) - 1).U), + "Transaction length exceeds timing model maximum supported length") + } + + // Timing Model -- Write Latency Pipe + // Write latency = write-ack cycle - cycle to receive last write beat + // Count the beats received for each tracker; they can be interleaved + + val wBeatCounters = Reg(Vec(nTrackers, UInt(sectorBits.W))) + val wValid = RegInit(VecInit(Seq.fill(nTrackers)(false.B))) + + val writeLatencyPipe = Module(new DynamicLatencyPipe(UInt(1.W), nTrackers, latencyBits)) + writeLatencyPipe.io.enq.valid := false.B + writeLatencyPipe.io.enq.bits := DontCare + writeLatencyPipe.io.latency := writeLatency + writeLatencyPipe.io.tCycle := tCycle + + val tagMatch = target.data.bits.tag === target.req.bits.tag + + wValid.zip(wBeatCounters).zipWithIndex.foreach { case ((valid, count), idx) => + val wReqFire = target.req.fire && target.req.bits.write && target.req.bits.tag === idx.U + val wDataFire = target.data.fire && target.data.bits.tag === idx.U + val wDone = (wDataFire && count === 1.U) + + when (tFire) { + // New write request received + when(wDone) { + assert(valid, "Write data received for unallocated tracker: %d\n", idx.U) + writeLatencyPipe.io.enq.valid := true.B + valid := false.B + }.elsewhen (wReqFire) { + valid := true.B + count := Mux(wDataFire, dataBeats.U * target.req.bits.len - 1.U, dataBeats.U * target.req.bits.len) + // We don't honestly expect len > 2^29 do we.. + // New data beat received for our tracker + }.elsewhen (wDataFire) { + count := count - 1.U + assert(valid, "Write data received for unallocated tracker: %d\n", idx.U) + } + } + } + + // Timing Model -- Read Latency Pipe + // Read latency is simply the number of cycles between read-req and first resp beat + val readLatencyPipe = Module(new DynamicLatencyPipe(UInt(sectorBits.W), nTrackers, latencyBits)) + + readLatencyPipe.io.enq.valid := tFire && target.req.fire && !target.req.bits.write + readLatencyPipe.io.enq.bits := target.req.bits.len + readLatencyPipe.io.tCycle := tCycle + readLatencyPipe.io.latency := readLatency + + // Scheduler. Prioritize returning write acknowledgements over returning read resps + // as they are only a single cycle long + val readRespBeatsLeft = RegInit(0.U(sectorBits.W)) + val returnWrite = RegInit(false.B) + val readRespBusy = readRespBeatsLeft =/= 0.U + val done = (returnWrite || readRespBeatsLeft === 1.U) && target.resp.fire + val idle = !returnWrite && !readRespBusy + writeLatencyPipe.io.deq.ready := false.B + readLatencyPipe.io.deq.ready := false.B + + when (tFire) { + when (done || idle) { + // If a write-response is waiting, return it first + when(writeLatencyPipe.io.deq.valid) { + returnWrite := true.B + writeLatencyPipe.io.deq.ready := true.B + }.elsewhen(readLatencyPipe.io.deq.valid) { + readRespBeatsLeft := readLatencyPipe.io.deq.bits * dataBeats.U + readLatencyPipe.io.deq.ready := true.B + }.otherwise { + readRespBeatsLeft := 0.U + returnWrite := false.B + } + }.elsewhen(readRespBusy && target.resp.fire) { + readRespBeatsLeft := readRespBeatsLeft - 1.U + } + } + + // Tie functional queues to output, gated with timing model control + target.resp.valid := !idle + target.resp.bits.data := 0.U + target.resp.bits.tag := 0.U + // This shouldn't be necessary for a well behaved target, but only drive bits + // through when there is valid data in the queue, closing a potential + // determinism hole + when (rRespBuf.io.deq.valid && readRespBusy) { + target.resp.bits.data := rRespBuf.io.deq.bits.data + target.resp.bits.tag := rRespBuf.io.deq.bits.tag + }.elsewhen (wAckBuf.io.deq.valid && returnWrite) { + target.resp.bits.tag := wAckBuf.io.deq.bits + } + + wAckStallN := !returnWrite || wAckBuf.io.deq.valid + rRespStallN := !readRespBusy || rRespBuf.io.deq.valid + + wAckBuf.io.deq.ready := tFireHelper.fire(wAckStallN) && returnWrite && target.resp.ready + rRespBuf.io.deq.ready := tFireHelper.fire(rRespStallN) && readRespBusy && target.resp.ready + } // withReset{} + + // Memory mapped registers + val nsectorReg = Reg(UInt(sectorBits.W)) + val max_req_lenReg = Reg(UInt(sectorBits.W)) + attach(nsectorReg, "bdev_nsectors", WriteOnly) + attach(max_req_lenReg, "bdev_max_req_len", WriteOnly) + target.info.nsectors := nsectorReg + target.info.max_req_len := max_req_lenReg + + // Functional request queue (to CPU) + genROReg(reqBuf.io.deq.valid, "bdev_req_valid") + genROReg(reqBuf.io.deq.bits.write, "bdev_req_write") + genROReg(reqBuf.io.deq.bits.offset, "bdev_req_offset") + genROReg(reqBuf.io.deq.bits.len, "bdev_req_len") + genROReg(reqBuf.io.deq.bits.tag, "bdev_req_tag") + Pulsify(genWORegInit(reqBuf.io.deq.ready, "bdev_req_ready", false.B), pulseLength = 1) + + // Functional data queue (to CPU) + genROReg(dataBuf.io.deq.valid, "bdev_data_valid") + genROReg(dataBuf.io.deq.bits.data(63, 32), "bdev_data_data_upper") + genROReg(dataBuf.io.deq.bits.data(31, 0), "bdev_data_data_lower") + genROReg(dataBuf.io.deq.bits.tag, "bdev_data_tag") + Pulsify(genWORegInit(dataBuf.io.deq.ready, "bdev_data_ready", false.B), pulseLength = 1) + + // Read reponse buffer MMIO IF (from CPU) + val rRespDataRegUpper = genWOReg(Wire(UInt((dataBitsPerBeat/2).W)),"bdev_rresp_data_upper") + val rRespDataRegLower = genWOReg(Wire(UInt((dataBitsPerBeat/2).W)),"bdev_rresp_data_lower") + val rRespTag = genWOReg(Wire(UInt(tagBits.W) ),"bdev_rresp_tag") + Pulsify( genWORegInit(rRespBuf.io.enq.valid ,"bdev_rresp_valid", false.B), pulseLength = 1) + genROReg(rRespBuf.io.enq.ready, "bdev_rresp_ready") + + rRespBuf.io.enq.bits.data := Cat(rRespDataRegUpper, rRespDataRegLower) + rRespBuf.io.enq.bits.tag := rRespTag + + // Write acknowledgement buffer MMIO IF (from CPU) -- we only need the tag from SW + val wAckTag = genWOReg(Wire(UInt(tagBits.W)) ,"bdev_wack_tag") + Pulsify( genWORegInit(wAckBuf.io.enq.valid ,"bdev_wack_valid", false.B), pulseLength = 1) + genROReg(wAckBuf.io.enq.ready, "bdev_wack_ready") + wAckBuf.io.enq.bits := wAckTag + + // Indicates to the CPU-hosted component that we need to be serviced + genROReg(reqBuf.io.deq.valid || dataBuf.io.deq.valid, "bdev_reqs_pending") + genROReg(~wAckStallN, "bdev_wack_stalled") + genROReg(~rRespStallN, "bdev_rresp_stalled") + + genCRFile() + + override def genHeader(base: BigInt, sb: StringBuilder) { + super.genHeader(base, sb) + sb.append(CppGenerationUtils.genMacro(s"${getWName.toUpperCase}_latency_bits", UInt32(latencyBits))) + sb.append(CppGenerationUtils.genMacro(s"${getWName.toUpperCase}_num_trackers", UInt32(nTrackers))) + } +} diff --git a/src/main/scala/firesim/endpoints/SerialWidget.scala b/src/main/scala/firesim/endpoints/SerialWidget.scala new file mode 100644 index 0000000..f76e1f6 --- /dev/null +++ b/src/main/scala/firesim/endpoints/SerialWidget.scala @@ -0,0 +1,72 @@ +package firesim +package endpoints + +import midas.core.{HostPort} +import midas.widgets._ + +import chisel3._ +import chisel3.util._ +import chisel3.experimental.{DataMirror, Direction} +import freechips.rocketchip.config.Parameters + +import testchipip.SerialIO + +class SimSerialIO extends Endpoint { + def matchType(data: Data) = data match { + case channel: SerialIO => + DataMirror.directionOf(channel.out.valid) == Direction.Output + case _ => false + } + def widget(p: Parameters) = new SerialWidget()(p) + override def widgetName = "SerialWidget" +} + +class SerialWidgetIO(implicit val p: Parameters) extends EndpointWidgetIO()(p) { + val w = testchipip.SerialAdapter.SERIAL_IF_WIDTH + val hPort = Flipped(HostPort(new SerialIO(w))) +} + +class SerialWidget(implicit p: Parameters) extends EndpointWidget()(p) { + val io = IO(new SerialWidgetIO) + + val inBuf = Module(new Queue(UInt(io.w.W), 16)) + val outBuf = Module(new Queue(UInt(io.w.W), 16)) + val tokensToEnqueue = RegInit(0.U(32.W)) + + val target = io.hPort.hBits + val tFire = io.hPort.toHost.hValid && io.hPort.fromHost.hReady && io.tReset.valid && tokensToEnqueue =/= 0.U + val targetReset = tFire & io.tReset.bits + inBuf.reset := reset.toBool || targetReset + outBuf.reset := reset.toBool || targetReset + + io.hPort.toHost.hReady := tFire + io.hPort.fromHost.hValid := tFire + io.tReset.ready := tFire + + target.in <> inBuf.io.deq + inBuf.io.deq.ready := target.in.ready && tFire + + outBuf.io.enq <> target.out + outBuf.io.enq.valid := target.out.valid && tFire + + genWOReg(inBuf.io.enq.bits, "in_bits") + Pulsify(genWORegInit(inBuf.io.enq.valid, "in_valid", false.B), pulseLength = 1) + genROReg(inBuf.io.enq.ready, "in_ready") + genROReg(outBuf.io.deq.bits, "out_bits") + genROReg(outBuf.io.deq.valid, "out_valid") + Pulsify(genWORegInit(outBuf.io.deq.ready, "out_ready", false.B), pulseLength = 1) + + val stepSize = Wire(UInt(32.W)) + val start = Wire(Bool()) + when (start) { + tokensToEnqueue := stepSize + }.elsewhen (tFire) { + tokensToEnqueue := tokensToEnqueue - 1.U + } + + genWOReg(stepSize, "step_size") + genROReg(tokensToEnqueue === 0.U, "done") + Pulsify(genWORegInit(start, "start", false.B), pulseLength = 1) + + genCRFile() +} diff --git a/src/main/scala/firesim/endpoints/SimpleNICWidget.scala b/src/main/scala/firesim/endpoints/SimpleNICWidget.scala new file mode 100644 index 0000000..870d71a --- /dev/null +++ b/src/main/scala/firesim/endpoints/SimpleNICWidget.scala @@ -0,0 +1,249 @@ +package firesim +package endpoints + +import chisel3._ +import chisel3.util._ +import chisel3.experimental.{DataMirror, Direction} +import freechips.rocketchip.config.{Parameters, Field} +import freechips.rocketchip.diplomacy.AddressSet +import freechips.rocketchip.util._ + +import midas.core.{HostPort} +import midas.widgets._ +import testchipip.{StreamIO, StreamChannel} +import icenet.{NICIOvonly, RateLimiterSettings} +import icenet.IceNIC._ +import junctions.{NastiIO, NastiKey} + +object TokenQueueConsts { + val TOKENS_PER_BIG_TOKEN = 7 + val BIG_TOKEN_WIDTH = (TOKENS_PER_BIG_TOKEN + 1) * 64 + val TOKEN_QUEUE_DEPTH = 6144 +} +import TokenQueueConsts._ + +case object LoopbackNIC extends Field[Boolean] + +/* on a NIC token transaction: + * 1) simulation driver feeds an empty token to start: + * data_in is garbage or real value (if exists) + * data_in_valid is 0 or 1 respectively + * data_out_ready is true (say host can always accept) + * + * 2) target responds: + * data_out garbage or real value (if exists) + * data_out_valid 0 or 1 respectively + * data_in_ready would be 1, so driver knows how to construct the next token if there was data to send + * + * repeat + */ + +class ReadyValidLast extends Bundle { + val data_last = Bool() + val ready = Bool() + val valid = Bool() +} + +class BIGToken extends Bundle { + val data = Vec(7, UInt(64.W)) + val rvls = Vec(7, new ReadyValidLast()) + val pad = UInt(43.W) +} + +class HostToNICToken extends Bundle { + val data_in = new StreamChannel(64) + val data_in_valid = Bool() + val data_out_ready = Bool() +} + +class NICToHostToken extends Bundle { + val data_out = new StreamChannel(64) + val data_out_valid = Bool() + val data_in_ready = Bool() +} + +class SimSimpleNIC extends Endpoint { + def matchType(data: Data) = data match { + case channel: NICIOvonly => + DataMirror.directionOf(channel.out.valid) == Direction.Output + case _ => false + } + def widget(p: Parameters) = new SimpleNICWidget()(p) + override def widgetName = "SimpleNICWidget" +} + +class SimpleNICWidgetIO(implicit val p: Parameters) extends EndpointWidgetIO()(p) { + val hPort = Flipped(HostPort(new NICIOvonly)) +} + +class BigTokenToNICTokenAdapter extends Module { + val io = IO(new Bundle { + val htnt = DecoupledIO(new HostToNICToken) + val pcie_in = Flipped(DecoupledIO(UInt(512.W))) + }) + + val pcieBundled = (new BIGToken).fromBits(io.pcie_in.bits) + + val xactHelper = DecoupledHelper(io.htnt.ready, io.pcie_in.valid) + + val loopIter = RegInit(0.U(32.W)) + when (io.htnt.fire()) { + loopIter := Mux(loopIter === 6.U, 0.U, loopIter + 1.U) + } + + io.htnt.bits.data_in.data := pcieBundled.data(loopIter) + io.htnt.bits.data_in.keep := 0xFF.U + io.htnt.bits.data_in.last := pcieBundled.rvls(loopIter).data_last + io.htnt.bits.data_in_valid := pcieBundled.rvls(loopIter).valid + io.htnt.bits.data_out_ready := pcieBundled.rvls(loopIter).ready + io.htnt.valid := xactHelper.fire(io.htnt.ready) + io.pcie_in.ready := xactHelper.fire(io.pcie_in.valid, loopIter === 6.U) +} + +class NICTokenToBigTokenAdapter extends Module { + val io = IO(new Bundle { + val ntht = Flipped(DecoupledIO(new NICToHostToken)) + val pcie_out = DecoupledIO(UInt(512.W)) + }) + + // step one, buffer 7 elems into registers. note that the 7th element is here + // just for convenience. in reality, it is not used since we're bypassing to + // remove a cycle of latency + val NTHT_BUF = Reg(Vec(7, new NICToHostToken)) + val specialCounter = RegInit(0.U(32.W)) + + when (io.ntht.valid) { + NTHT_BUF(specialCounter) := io.ntht.bits + } + + io.ntht.ready := (specialCounter === 6.U && io.pcie_out.ready) || (specialCounter =/= 6.U) + io.pcie_out.valid := specialCounter === 6.U && io.ntht.valid + when ((specialCounter =/= 6.U) && io.ntht.valid) { + specialCounter := specialCounter + 1.U + } .elsewhen ((specialCounter === 6.U) && io.ntht.valid && io.pcie_out.ready) { + specialCounter := 0.U + } .otherwise { + specialCounter := specialCounter + } + // step two, connect 6 elems + latest one to output (7 items) + // TODO: attach pcie_out to data + + // debug check to help check we're not losing tokens somewhere + val token_trace_counter = RegInit(0.U(43.W)) + when (io.pcie_out.fire()) { + token_trace_counter := token_trace_counter + 1.U + } .otherwise { + token_trace_counter := token_trace_counter + } + + val out = Wire(new BIGToken) + for (i <- 0 until 6) { + out.data(i) := NTHT_BUF(i).data_out.data + out.rvls(i).data_last := NTHT_BUF(i).data_out.last + out.rvls(i).ready := NTHT_BUF(i).data_in_ready + out.rvls(i).valid := NTHT_BUF(i).data_out_valid + } + out.data(6) := io.ntht.bits.data_out.data + out.rvls(6).data_last := io.ntht.bits.data_out.last + out.rvls(6).ready := io.ntht.bits.data_in_ready + out.rvls(6).valid := io.ntht.bits.data_out_valid + out.pad := token_trace_counter + + io.pcie_out.bits := out.asUInt +} + +class HostToNICTokenGenerator(nTokens: Int)(implicit p: Parameters) extends Module { + val io = IO(new Bundle { + val out = Decoupled(new HostToNICToken) + val in = Flipped(Decoupled(new NICToHostToken)) + }) + + val s_init :: s_seed :: s_forward :: Nil = Enum(3) + val state = RegInit(s_init) + + val (_, seedDone) = Counter(state === s_seed && io.out.fire(), nTokens) + + io.out.valid := state === s_seed || (state === s_forward && io.in.valid) + io.out.bits.data_in_valid := state === s_forward && io.in.bits.data_out_valid + io.out.bits.data_in := io.in.bits.data_out + io.out.bits.data_out_ready := state === s_seed || io.in.bits.data_in_ready + io.in.ready := state === s_forward && io.out.ready + + when (state === s_init) { state := s_seed } + when (seedDone) { state := s_forward } +} + +class SimpleNICWidget(implicit p: Parameters) extends EndpointWidget()(p) + with BidirectionalDMA { + val io = IO(new SimpleNICWidgetIO) + + // DMA mixin parameters + lazy val fromHostCPUQueueDepth = TOKEN_QUEUE_DEPTH + lazy val toHostCPUQueueDepth = TOKEN_QUEUE_DEPTH + // Biancolin: Need to look into this + lazy val dmaSize = BigInt((BIG_TOKEN_WIDTH / 8) * TOKEN_QUEUE_DEPTH) + + val htnt_queue = Module(new Queue(new HostToNICToken, 10)) + val ntht_queue = Module(new Queue(new NICToHostToken, 10)) + + val bigtokenToNIC = Module(new BigTokenToNICTokenAdapter) + val NICtokenToBig = Module(new NICTokenToBigTokenAdapter) + + val target = io.hPort.hBits + val tFire = io.hPort.toHost.hValid && io.hPort.fromHost.hReady && io.tReset.valid + val targetReset = tFire & io.tReset.bits + io.tReset.ready := tFire + +// htnt_queue.reset := reset //|| targetReset +// ntht_queue.reset := reset //|| targetReset + + if (p(LoopbackNIC)) { + val tokenGen = Module(new HostToNICTokenGenerator(10)) + htnt_queue.io.enq <> tokenGen.io.out + tokenGen.io.in <> ntht_queue.io.deq + NICtokenToBig.io.ntht.valid := false.B + NICtokenToBig.io.ntht.bits := DontCare + bigtokenToNIC.io.htnt.ready := false.B + } else { + NICtokenToBig.io.ntht <> ntht_queue.io.deq + htnt_queue.io.enq <> bigtokenToNIC.io.htnt + } + + io.hPort.toHost.hReady := ntht_queue.io.enq.ready + ntht_queue.io.enq.valid := io.hPort.toHost.hValid + ntht_queue.io.enq.bits.data_out := target.out.bits + ntht_queue.io.enq.bits.data_out_valid := target.out.valid + ntht_queue.io.enq.bits.data_in_ready := true.B //target.in.ready + + io.hPort.fromHost.hValid := htnt_queue.io.deq.valid + htnt_queue.io.deq.ready := io.hPort.fromHost.hReady + target.in.bits := htnt_queue.io.deq.bits.data_in + target.in.valid := htnt_queue.io.deq.bits.data_in_valid + //target.out.ready := htnt_queue.io.deq.bits.data_out_ready + + bigtokenToNIC.io.pcie_in <> incomingPCISdat.io.deq + outgoingPCISdat.io.enq <> NICtokenToBig.io.pcie_out + + + if (p(LoopbackNIC)) { + target.rlimit.size := 8.U + target.rlimit.period := 0.U + target.rlimit.inc := 1.U + target.macAddr := 0.U + } else { + val macAddrRegUpper = Reg(UInt(32.W)) + val macAddrRegLower = Reg(UInt(32.W)) + val rlimitSettings = Reg(UInt(32.W)) + + target.rlimit := (new RateLimiterSettings).fromBits(rlimitSettings) + target.macAddr := Cat(macAddrRegUpper, macAddrRegLower) + + attach(macAddrRegUpper, "macaddr_upper", WriteOnly) + attach(macAddrRegLower, "macaddr_lower", WriteOnly) + attach(rlimitSettings, "rlimit_settings", WriteOnly) + } + + genROReg(!tFire, "done") + + genCRFile() +} diff --git a/src/main/scala/firesim/endpoints/TracerVWidget.scala b/src/main/scala/firesim/endpoints/TracerVWidget.scala new file mode 100644 index 0000000..1ded90a --- /dev/null +++ b/src/main/scala/firesim/endpoints/TracerVWidget.scala @@ -0,0 +1,75 @@ +package firesim.endpoints + +import chisel3._ +import chisel3.util._ +import freechips.rocketchip.config.{Parameters, Field} +import freechips.rocketchip.diplomacy.AddressSet +import freechips.rocketchip.util._ +import freechips.rocketchip.rocket.TracedInstruction +import freechips.rocketchip.subsystem.RocketTilesKey +import freechips.rocketchip.tile.TileKey +import firesim.firesim.TraceOutputTop + +import midas.core.{HostPort} +import midas.widgets._ +import testchipip.{StreamIO, StreamChannel} +import TokenQueueConsts._ + +class SimTracerV extends Endpoint { + + // this is questionable ... + // but I can't see a better way to do this for now. getting sharedMemoryTLEdge is the problem. + var tracer_param = Parameters.empty + var num_traces = 0 + def matchType(data: Data) = data match { + case channel: TraceOutputTop => { + // this is questionable ... + tracer_param = channel.traces(0).p + num_traces = channel.traces.length + true + } + case _ => false + } + def widget(p: Parameters) = new TracerVWidget(tracer_param, num_traces)(p) + override def widgetName = "TracerVWidget" +} + +class TracerVWidgetIO(val tracerParams: Parameters, val num_traces: Int)(implicit p: Parameters) extends EndpointWidgetIO()(p) { + val hPort = Flipped(HostPort(new TraceOutputTop(num_traces)(tracerParams))) +} + +class TracerVWidget(tracerParams: Parameters, num_traces: Int)(implicit p: Parameters) extends EndpointWidget()(p) + with UnidirectionalDMAToHostCPU { + val io = IO(new TracerVWidgetIO(tracerParams, num_traces)) + + // DMA mixin parameters + lazy val toHostCPUQueueDepth = TOKEN_QUEUE_DEPTH + lazy val dmaSize = BigInt((BIG_TOKEN_WIDTH / 8) * TOKEN_QUEUE_DEPTH) + + val uint_traces = io.hPort.hBits.traces map (trace => trace.asUInt) + outgoingPCISdat.io.enq.bits := Cat(uint_traces) //io.hPort.hBits.traces(0).asUInt + + val tFireHelper = DecoupledHelper(outgoingPCISdat.io.enq.ready, + io.hPort.toHost.hValid, io.hPort.fromHost.hReady, io.tReset.valid) + + io.tReset.ready := tFireHelper.fire(io.tReset.valid) + io.hPort.fromHost.hValid := tFireHelper.fire(io.hPort.fromHost.hReady) + io.hPort.toHost.hReady := tFireHelper.fire(io.hPort.toHost.hValid) + + outgoingPCISdat.io.enq.valid := tFireHelper.fire(outgoingPCISdat.io.enq.ready) + + when (outgoingPCISdat.io.enq.fire()) { + for (i <- 0 until io.hPort.hBits.traces.length) { + printf("trace %d, valid: %x\n", i.U, io.hPort.hBits.traces(i).valid) + printf("trace %d, iaddr: %x\n", i.U, io.hPort.hBits.traces(i).iaddr) + printf("trace %d, insn: %x\n", i.U, io.hPort.hBits.traces(i).insn) + printf("trace %d, priv: %x\n", i.U, io.hPort.hBits.traces(i).priv) + printf("trace %d, exception: %x\n", i.U, io.hPort.hBits.traces(i).exception) + printf("trace %d, interrupt: %x\n", i.U, io.hPort.hBits.traces(i).interrupt) + printf("trace %d, cause: %x\n", i.U, io.hPort.hBits.traces(i).cause) + printf("trace %d, tval: %x\n", i.U, io.hPort.hBits.traces(i).tval) + } + } + attach(outgoingPCISdat.io.deq.valid && !outgoingPCISdat.io.enq.ready, "tracequeuefull", ReadOnly) + genCRFile() +} diff --git a/src/main/scala/firesim/endpoints/UARTWidget.scala b/src/main/scala/firesim/endpoints/UARTWidget.scala new file mode 100644 index 0000000..89be005 --- /dev/null +++ b/src/main/scala/firesim/endpoints/UARTWidget.scala @@ -0,0 +1,129 @@ +package firesim +package endpoints + +import midas.core.{HostPort} +import midas.widgets._ + +import chisel3._ +import chisel3.util._ +import chisel3.experimental.{DataMirror, Direction} +import freechips.rocketchip.config.Parameters +import freechips.rocketchip.subsystem.PeripheryBusKey +import sifive.blocks.devices.uart.{UARTPortIO, PeripheryUARTKey} + +class SimUART extends Endpoint { + def matchType(data: Data) = data match { + case channel: UARTPortIO => + DataMirror.directionOf(channel.txd) == Direction.Output + case _ => false + } + def widget(p: Parameters) = { + val frequency = p(PeripheryBusKey).frequency + val baudrate = 3686400L + val div = (p(PeripheryBusKey).frequency / baudrate).toInt + new UARTWidget(div)(p) + } + override def widgetName = "UARTWidget" +} + +class UARTWidgetIO(implicit p: Parameters) extends EndpointWidgetIO()(p) { + val hPort = Flipped(HostPort(new UARTPortIO)) +} + +class UARTWidget(div: Int)(implicit p: Parameters) extends EndpointWidget()(p) { + val io = IO(new UARTWidgetIO) + + val txfifo = Module(new Queue(UInt(8.W), 128)) + val rxfifo = Module(new Queue(UInt(8.W), 128)) + + val target = io.hPort.hBits + val fire = io.hPort.toHost.hValid && io.hPort.fromHost.hReady && io.tReset.valid & txfifo.io.enq.ready + val targetReset = fire & io.tReset.bits + rxfifo.reset := reset.toBool || targetReset + txfifo.reset := reset.toBool || targetReset + + io.hPort.toHost.hReady := fire + io.hPort.fromHost.hValid := fire + io.tReset.ready := fire + + val sTxIdle :: sTxWait :: sTxData :: sTxBreak :: Nil = Enum(UInt(), 4) + val txState = RegInit(sTxIdle) + val txData = Reg(UInt(8.W)) + // iterate through bits in byte to deserialize + val (txDataIdx, txDataWrap) = Counter(txState === sTxData && fire, 8) + // iterate using div to convert clock rate to baud + val (txBaudCount, txBaudWrap) = Counter(txState === sTxWait && fire, div) + val (txSlackCount, txSlackWrap) = Counter(txState === sTxIdle && target.txd === 0.U && fire, 4) + + switch(txState) { + is(sTxIdle) { + when(txSlackWrap) { + txData := 0.U + txState := sTxWait + } + } + is(sTxWait) { + when(txBaudWrap) { + txState := sTxData + } + } + is(sTxData) { + when(fire) { + txData := txData | (target.txd << txDataIdx) + } + when(txDataWrap) { + txState := Mux(target.txd === 1.U, sTxIdle, sTxBreak) + }.elsewhen(fire) { + txState := sTxWait + } + } + is(sTxBreak) { + when(target.txd === 1.U && fire) { + txState := sTxIdle + } + } + } + + txfifo.io.enq.bits := txData + txfifo.io.enq.valid := txDataWrap + + val sRxIdle :: sRxStart :: sRxData :: Nil = Enum(UInt(), 3) + val rxState = RegInit(sRxIdle) + // iterate using div to convert clock rate to baud + val (rxBaudCount, rxBaudWrap) = Counter(fire, div) + // iterate through bits in byte to deserialize + val (rxDataIdx, rxDataWrap) = Counter(rxState === sRxData && fire && rxBaudWrap, 8) + + target.rxd := 1.U + switch(rxState) { + is(sRxIdle) { + target.rxd := 1.U + when (rxBaudWrap && rxfifo.io.deq.valid) { + rxState := sRxStart + } + } + is(sRxStart) { + target.rxd := 0.U + when(rxBaudWrap) { + rxState := sRxData + } + } + is(sRxData) { + target.rxd := (rxfifo.io.deq.bits >> rxDataIdx)(0) + when(rxDataWrap && rxBaudWrap) { + rxState := sRxIdle + } + } + } + rxfifo.io.deq.ready := (rxState === sRxData) && rxDataWrap && rxBaudWrap && fire + + genROReg(txfifo.io.deq.bits, "out_bits") + genROReg(txfifo.io.deq.valid, "out_valid") + Pulsify(genWORegInit(txfifo.io.deq.ready, "out_ready", false.B), pulseLength = 1) + + genWOReg(rxfifo.io.enq.bits, "in_bits") + Pulsify(genWORegInit(rxfifo.io.enq.valid, "in_valid", false.B), pulseLength = 1) + genROReg(rxfifo.io.enq.ready, "in_ready") + + genCRFile() +} diff --git a/src/test/scala/firesim/ScalaTestSuite.scala b/src/test/scala/firesim/ScalaTestSuite.scala new file mode 100644 index 0000000..8297376 --- /dev/null +++ b/src/test/scala/firesim/ScalaTestSuite.scala @@ -0,0 +1,118 @@ +//See LICENSE for license details. +package firesim.firesim + +import java.io.File + +import scala.concurrent.{Future, Await, ExecutionContext} +import scala.sys.process.{stringSeqToProcess, ProcessLogger} + +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.system.{RocketTestSuite, BenchmarkTestSuite} +import freechips.rocketchip.system.TestGeneration._ +import freechips.rocketchip.system.DefaultTestSuites._ + +import firesim.util.{GeneratorArgs, HasFireSimGeneratorUtilities} +import firesim._ + +abstract class FireSimTestSuite( + topModuleClass: String, + targetConfigs: String, + platformConfigs: String, + N: Int = 8 + ) extends TestSuiteCommon with IsFireSimGeneratorLike { + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + + lazy val generatorArgs = GeneratorArgs( + midasFlowKind = "midas", + targetDir = "generated-src", + topModuleProject = "firesim.firesim", + topModuleClass = topModuleClass, + targetConfigProject = "firesim.firesim", + targetConfigs = targetConfigs, + platformConfigProject = "firesim.firesim", + platformConfigs = platformConfigs) + + // From HasFireSimGeneratorUtilities + // For the firesim utilities to use the same directory as the test suite + override lazy val testDir = genDir + + // From TestSuiteCommon + val targetTuple = generatorArgs.tupleName + val commonMakeArgs = Seq(s"DESIGN=${generatorArgs.topModuleClass}", + s"TARGET_CONFIG=${generatorArgs.targetConfigs}", + s"PLATFORM_CONFIG=${generatorArgs.platformConfigs}") + override lazy val platform = hostParams(midas.Platform) + + def invokeMlSimulator(backend: String, name: String, debug: Boolean) = { + make(s"${outDir.getAbsolutePath}/${name}.%s".format(if (debug) "vpd" else "out"), + s"EMUL=${backend}" + ) + } + + def runTest(backend: String, name: String, debug: Boolean) = { + behavior of s"${name} running on ${backend} in MIDAS-level simulation" + compileMlSimulator(backend, debug) + if (isCmdAvailable(backend)) { + it should s"pass" in { + assert(invokeMlSimulator(backend, name, debug) == 0) + } + } + } + + //def runReplay(backend: String, replayBackend: String, name: String) = { + // val dir = (new File(outDir, backend)).getAbsolutePath + // (Seq("make", s"replay-$replayBackend", + // s"SAMPLE=${dir}/${name}.sample", s"output_dir=$dir") ++ makeArgs).! + //} + + def runSuite(backend: String, debug: Boolean = false)(suite: RocketTestSuite) { + // compile emulators + behavior of s"${suite.makeTargetName} running on $backend" + if (isCmdAvailable(backend)) { + val postfix = suite match { + case _: BenchmarkTestSuite | _: BlockdevTestSuite | _: NICTestSuite => ".riscv" + case _ => "" + } + val results = suite.names.toSeq sliding (N, N) map { t => + val subresults = t map (name => + Future(name -> invokeMlSimulator(backend, s"$name$postfix", debug))) + Await result (Future sequence subresults, Duration.Inf) + } + results.flatten foreach { case (name, exitcode) => + it should s"pass $name" in { assert(exitcode == 0) } + } + //replayBackends foreach { replayBackend => + // if (platformParams(midas.EnableSnapshot) && isCmdAvailable("vcs")) { + // assert((Seq("make", s"vcs-$replayBackend") ++ makeArgs).! == 0) // compile vcs + // suite.names foreach { name => + // it should s"replay $name in $replayBackend" in { + // assert(runReplay(backend, replayBackend, s"$name$postfix") == 0) + // } + // } + // } else { + // suite.names foreach { name => + // ignore should s"replay $name in $backend" + // } + // } + //} + } else { + ignore should s"pass $backend" + } + } + + clean + mkdirs + elaborateAndCompileWithMidas + generateTestSuiteMakefrags + runTest("verilator", "rv64ui-p-simple", false) + runSuite("verilator")(benchmarks) + runSuite("verilator")(FastBlockdevTests) +} + +class RocketF1Tests extends FireSimTestSuite("FireSimNoNIC", "FireSimRocketChipConfig", "FireSimConfig") +class RocketF1ClockDivTests extends FireSimTestSuite("FireSimNoNIC", "FireSimRocketChipConfig", "FireSimClockDivConfig") +class BoomF1Tests extends FireSimTestSuite("FireBoomNoNIC", "FireSimBoomConfig", "FireSimConfig") +class RocketNICF1Tests extends FireSimTestSuite("FireSim", "FireSimRocketChipConfig", "FireSimConfig") { + runSuite("verilator")(NICLoopbackTests) +} diff --git a/tools/firrtl b/tools/firrtl new file mode 160000 index 0000000..380c233 --- /dev/null +++ b/tools/firrtl @@ -0,0 +1 @@ +Subproject commit 380c233b43c2de53b0ee15a39e9364d438066b9f