From 634cad9e781b02a8fe0327957ffe5a335ef2c736 Mon Sep 17 00:00:00 2001 From: Howard Mao Date: Thu, 22 Jun 2017 11:52:43 -0700 Subject: [PATCH] update README --- README.md | 289 ++++++++++++++++++++++++------------------------------ 1 file changed, 129 insertions(+), 160 deletions(-) diff --git a/README.md b/README.md index 6fef0603..d6a5be1e 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,26 @@ The submodules and subdirectories for the project template are organized as follows. * rocket-chip - contains code for the RocketChip generator and Chisel HDL - * testchipip - contains the serial adapter and associated verilog and C++ code + * testchipip - contains the serial adapter, block device, and associated verilog and C++ code * verisim - directory in which Verilator simulations are compiled and run * vsim - directory in which Synopsys VCS simulations are compiled and run * bootrom - sources for the first-stage bootloader included in the Boot ROM * src/main/scala - scala source files for your project go here +## Using the block device + +The default example project just provides the Rocket coreplex, memory, and +serial line. But testchipip also provides a simulated block device that can +be used for non-volatile storage. You can build a simulator including the +block device using the blkdev package. + + make PROJECT=blkdev CONFIG=BlockDeviceConfig + ./simulator-blkdev-BlockDeviceConfig +blkdev=block-device.img ... + +By passing the +blkdev argument on the simulator command line, you can allow +the RTL simulation to read and write from a file. Take a look at tests/blkdev.c +for an example of how Rocket can program the block device controller. + ## Creating your own project To create your own project, you should create your own scala package. @@ -77,136 +91,123 @@ under src/main/scala that has the same name as your package. mkdir src/main/scala/pwm -Now let's add a peripheral device to the new SoC. First, add a Chisel module -for your device. +Now let's add a peripheral device to the new SoC. The easiest way to create a +TileLink peripheral is to use the TLRegisterRouter, which abstracts away the +details of handling the TileLink protocol and provides a convenient interface +for specifying memory-mapped registers. To create a RegisterRouter-based +peripheral, you will need to specify a parameter case class for the +configuration settings, a bundle trait with the extra top-level ports, and +a module implementation containing the actual RTL. - package pwm + case class PWMParams(address: BigInt, beatBytes: Int) - import chisel3._ - import cde.{Parameters, Field} - import uncore.tilelink._ - - class PWMTL(implicit p: Parameters) extends Module { - val io = new Bundle { - val tl = new ClientUncachedTileLinkIO().flip - val pwmout = Bool(OUTPUT) - } - - // ... + trait PWMTLBundle extends Bundle { + val pwmout = Output(Bool()) } -The `io` bundle holds the ports that this module exposes externally. It has -two parts, a uncached TileLink port (tl), and a single-bit output (pwmout). -The TL port is how the core communicates to the peripheral over MMIO. -The pwmout signal is what we will drive as our PWM output. + trait PWMTLModule { + val io: PWMTLBundle + implicit val p: Parameters + def params: PWMParams -Note that we have made our package `pwm` to match the subdirectory name. -Also note that we have imported the chisel3 package, which contains all the HDL -directives; cde.Parameters, an object which allows us to pass configurations -to different parts of the code (mainly used by TileLink in this case); and -the TileLink definitions from the uncore.tilelink package. + val w = params.beatBytes * 8 + val period = Reg(UInt(w.W)) + val duty = Reg(UInt(w.W)) + val enable = RegInit(false.B) -The full module code, with comments can be found in src/main/scala/pwm/PWM.scala. + // ... Use the registers to drive io.pwmout ... + + regmap( + 0x00 -> Seq( + RegField(w, period)), + 0x04 -> Seq( + RegField(w, duty)), + 0x08 -> Seq( + RegField(1, enable))) + } + +Once you have these classes, you can construct the final peripheral by +extending the TLRegisterRouter and passing the proper arguments. The first +set of arguments determines where the register router will be placed in the +global address map and what information will be put in its device tree entry. +The second set of arguments is the IO bundle constructor, which we create +by extending TLRegBundle with our bundle trait. The final set of arguments +is the module constructor, which we create by extends TLRegModule with our +module trait. + + class PWMTL(c: PWMParams)(implicit p: Parameters) + extends TLRegisterRouter( + c.address, "pwm", Seq("ucbbar,pwm"), + beatBytes = c.beatBytes)( + new TLRegBundle(c, _) with PWMTLBundle)( + new TLRegModule(c, _, _) with PWMTLModule) + +The full module code with comments can be found in src/main/scala/pwm/PWM.scala. After creating the module, we need to hook it up to our SoC. Rocketchip accomplishes this using the [cake pattern](http://www.cakesolutions.net/teamblogs/2011/12/19/cake-pattern-in-depth). -This basically involves placing code inside traits. In RocketChip, there are -three kinds of traits: a LazyModule trait, and IO bundle trait, and a module -implementation trait. +This basically involves placing code inside traits. In the RocketChip cake, +there are two kinds of traits: a LazyModule trait and a module implementation +trait. The LazyModule trait runs setup code that must execute before all the hardware gets elaborated. For a simple memory-mapped peripheral, this just involves -adding an entry to the address map. The SoC generator provides each leaf node -in the address map with a port from the MMIO interconnect. +connecting the peripheral's TileLink node to the MMIO crossbar. - import junctions._ - import diplomacy._ - import rocketchip._ + trait HasPeripheryPWM extends HasSystemNetworks { + implicit val p: Parameters - trait PeripheryPWM extends LazyModule { - val pDevices: ResourceManager[AddrMapEntry] + private val address = 0x2000 - pDevices.add(AddrMapEntry("pwm", MemSize(4096, MemAttr(AddrMapProt.RW)))) + val pwm = LazyModule(new PWMTL( + PWMParams(address, peripheryBusConfig.beatBytes))(p)) + + pwm.node := TLFragmenter( + peripheryBusConfig.beatBytes, cacheBlockBytes)(peripheryBus.node) } -This adds an entry called "pwm" and makes it 4096 bytes in size. This is more -than we really need, but to play nicely with the core's virtual memory system, -address map regions must be page-aligned. We also give this regions -read/write permissions. This device can be loaded from or stored to, -but CPU instructions cannot be fetched from this location. - -The IO bundle trait contains the extra IO ports that will be exported off-chip. -For the PWM peripheral, this will just be the `pwmout` pin. - - trait PeripheryPWMBundle { - val pwmout = Bool(OUTPUT) - } +Note that the PWMTL class we created from the register router is itself a +LazyModule. Register routers have a TileLike node simply named "node", which +we can hook up to the RocketChip peripheryBus. This will automatically add +address map and device tree entries for the peripheral. The module implementation trait is where we instantiate our PWM module and -connect it to the rest of the SoC. +connect it to the rest of the SoC. Since this module has an extra `pwmout` +output, we declare that in this trait, using Chisel's multi-IO +functionality. We then connect the PWMTL's pwmout to the pwmout we declared. - case object BuildPWM extends Field[(ClientUncachedTileLinkIO, Parameters) => Bool] + trait HasPeripheryPWMModuleImp extends LazyMultiIOModuleImp { + implicit val p: Parameters + val outer: HasPeripheryPWM - trait PeripheryPWMModule extends HasPeripheryParameters { - val pBus: TileLinkRecursiveInterconnect - val io: PeripheryPWMBundle + val pwmout = IO(Output(Bool())) - io.pwmout := p(BuildPWM)(pBus.port("pwm"), outerMMIOParams) + pwmout := outer.pwm.module.io.pwmout } -We just need to connect the MMIO TileLink port to the PWM module's TileLink port -and connect the PWM module's `pwmout` pin to the `pwmout` pin going off-chip. -We would like to do this in a configurable way so that we can swap out the -PWM module if need be. To do this, we create a new Field for the Parameters -object that produces a function taking in the Tilelink port and returning -the pwmout as a Bool. We will define this function later in the configuration -file. - -Note that we extend the HasPeripheryParameters trait. This provides us the -`outerMMIOParams` parameter object, which gets passed in as the `p` parameters -object to the PWM module. We have several parameters objects because different -TileLink interfaces need different configurations. For all MMIO ports -(i.e. those coming from pBus), you will want to use `outerMMIOParams`. -Peripheral devices can also connect TileLink client ports going into the -coreplex, which use the `innerParams` object. - Now we want to mix our traits into the system as a whole. This code is from src/main/scala/pwm/Top.scala. - package pwm - - import chisel3._ - import example._ - import cde.Parameters - class ExampleTopWithPWM(q: Parameters) extends ExampleTop(q) with PeripheryPWM { override lazy val module = Module( - new ExampleTopWithPWMModule(p, this, new ExampleTopWithPWMBundle(p))) + new ExampleTopWithPWMModule(p, this)) } - class ExampleTopWithPWMBundle(p: Parameters) extends ExampleTopBundle(p) - with PeripheryPWMBundle + class ExampleTopWithPWMModule(l: ExampleTopWithPWM) + extends ExampleTopModule(l) with HasPeripheryPWMModuleImp - class ExampleTopWithPWMModule(p: Parameters, l: ExampleTopWithPWM, b: => ExampleTopWithPWMBundle) - extends ExampleTopModule(p, l, b) with PeripheryPWMModule +Just as we need separate traits for LazyModule and module implementation, we +need two classes to build the system. The ExampleTop classes from the example +package already have the basic peripherals included for us, so we will just +extend those. -Just as we have three traits, we have three classes to build the system. -The ExampleTop classes from the example package already have the basic -peripherals included for us, so we will just extend those. - -The ExampleTop class includes the pre-elaboration code and also a lazy val -to produce the module implementation (hence LazyModule). The ExampleTopBundle -class becomes the top-level IO of the module implementation. And finally the -ExampleTopModule class is the actual RTL that gets synthesized. +The ExampleTop class includes the pre-elaboration code and also a lazy val to +produce the module implementation (hence LazyModule). The ExampleTopModule +class is the actual RTL that gets synthesized. Now we have the RTL for the chip, but we need a test harness to simulate it. - package pwm - - import cde.Parameters - import diplomacy.LazyModule - class TestHarness(q: Parameters) extends example.TestHarness()(q) { override def buildTop(p: Parameters) = LazyModule(new ExampleTopWithPWM(p)) @@ -221,33 +222,14 @@ We also need to create a Generator object, which gets called as the entry point for elaboration. object Generator extends GeneratorApp { - val longName = names.topModuleProject + "." + - names.topModuleClass + "." + - names.configs generateFirrtl } Finally, we need to add a configuration class in src/main/scala/pwm/Configs.scala. -This defines all the settings in the Parameters object. +This defines all the settings in the Parameters object. We aren't adding any +new parameters, so we can just extend the default configuration. - package pwm - - import cde.{Parameters, Config, CDEMatchError} - - class WithPWMTL extends Config( - (pname, site, here) => pname match { - case BuildPWM => (port: ClientUncachedTileLinkIO, p: Parameters) => { - val pwm = Module(new PWMTL()(p)) - pwm.io.tl <> port - pwm.io.pwmout - } - }) - - class PWMTLConfig extends Config(new WithPWMTL ++ new example.DefaultExampleConfig) - -The only thing we need to add to the DefaultExampleConfig is the definition -of the BuildPWM field. We just instantiate our PWMTL module, connect the -TileLink port and pass out the `pwmout` signal. + class PWMTLConfig extends Config(new example.DefaultExampleConfig) Now we can test that the PWM is working. The test program is in tests/pwm.c @@ -293,51 +275,41 @@ peripheral through MMIO. However, for IO devices (like a disk or network driver), we may want to have the device write directly to the coherent memory system instead. To add a device like that, you would do the following. - package dmadevice - - import chisel3._ - import cde.Parameters + class DMADevice(implicit p: Parameters) extends LazyModule { + val node = TLClientNode(TLClientParameters( + name = "dma-device", sourceId = IdRange(0, 1))) - class ExtBundle extends Bundle { - ... + lazy val module = new DMADeviceModule(this) } - class DMADevice(implicit p: Parameters) extends Module { - val io = new Bundle { - val tl = new ClientUncachedTileLinkIO + class DMADeviceModule(outer: DMADevice) extends LazyModuleImp(outer) { + val io = IO(new Bundle { + val mem = outer.node.bundleOut val ext = new ExtBundle - } + }) - ... + // ... rest of the code ... } - trait PeripheryDMA extends LazyModule { - val pBusMasters: RangeManager + trait HasPeripheryDMA extends HasSystemNetworks { + implicit val p: Parameters - pBusMasters.add("dma", 1) + val dma = LazyModule(new DMADevice) + + fsb.node := dma.node } - trait PeripheryDMABundle extends HasPeripheryParameters { - val ext = new ExtBundle - } - - trait PeripheryDMAModule { - val outer: PeripheryDMA - val io: PeripheryDMABundle - val coreplexIO: BaseCoreplexBundle - - val (r_start, r_end) = outer.pBusMasters.range("dma") - - val device = Module(new DMADevice()(innerParams)) - io.ext <> device.io.ext - coreplexIO.slave(r_start) <> device.io.tl + trait HasPeripheryDMAModuleImp extends LazyMultiIOModuleImp { + val ext = IO(new ExtBundle) + ext <> outer.dma.module.io.ext } The `ExtBundle` contains the signals we connect off-chip that we get data from. -The DMADevice also has a Tilelink port to communicate with the coherent memory -system (note that there's no .flip() call). Another thing to note is that -when we instantiate DMADevice in PeripheryDMAModule, the Parameters object -we give to it is `innerParams` instead of `outerMMIOParams`. +The DMADevice also has a Tilelink client port that we connect into the L1-L2 +crossbar through the front-side buffer (fsb). The sourceId variable given in +the TLClientNode instantiation determines the range of ids that can be used +in acquire messages from this device. Since we specified [0, 1) as our range, +only the ID 0 can be used. ## Adding a RoCC accelerator @@ -361,11 +333,6 @@ the accelerator can use to distinguish different instructions from each other. RoCC accelerators should extends the RoCC class. - package accel - - import chisel3._ - import rocket._ - class CustomAccelerator(implicit p: Parameters) extends RoCC()(p) { val cmd = Queue(io.cmd) // The parts of the command are as follows @@ -388,7 +355,7 @@ access to the L1 cache, `ptw` which provides access to the page-table walker, `autl` which provides shared access to the L2 alongside the ICache refill, and `utl` which provides dedicated access to the L2. -Look at the examples in rocket-chip/src/main/scala/rocket/rocc.scala for +Look at the examples in rocket-chip/src/main/scala/tile/LegacyRocc.scala for detailed information on the different IOs ### Adding RoCC accelerator to Config @@ -401,12 +368,14 @@ accelerator, and `generator` which specifies how to build the accelerator itself For instance, if we wanted to add the previously defined accelerator and route custom0 and custom1 instructions to it, we could do the following. - class WithCustomAccelerator extends Config( - (pname, site, here) => pname match { - case BuildRoCC => Seq( - opcodes = OpcodeSet.custom0 | OpcodeSet.custom1, - generator = (p: Parameters) => Module(new CustomAccelerator()(p))) - }) + class WithCustomAccelerator extends Config((site, here, up) => { + case RocketTilesKey => up(RocketTilesKey, site).map { r => + r.copy(rocc = Seq( + RoCCParams( + opcodes = OpcodeSet.custom0 | OpcodeSet.custom1, + generator = (p: Parameters) => Module(new CustomAccelerator()(p))))) + } + }) class CustomAcceleratorConfig extends Config( new WithCustomAccelerator ++ new BaseConfig)