Simulating a CPU design written in Chisel - simulation

I've written a single-cycled CPU in Chisel3 which implements most of the RV32I instructions (except CSR, Fence, ECALL/BREAK, LB/SB, which may be included later). The instructions are currently hard coded in the instruction memory, but I will change this so it reads instructions from a file instead. I've run into trouble with how to actually simulate my design. Here is the code where I've "glued" all components together.
class Core extends Module {
val io = IO(new Bundle {
val dc = Input(Bool())
})
io := DontCare
val pc = RegInit(0.U)
val pcSelect = Module(new PcSelect())
val pcPlusFour = Module(new Adder())
val alu = Module(new ALU())
val aluControl = Module(new AluControl())
val control = Module(new Control())
val immGen = Module(new ImmGen())
val branchLogic = Module(new BranchLogic())
val branchUnit = Module(new Adder())
val jumpReg = Module(new JumpReg())
val regFile = Module(new RegFile())
val jumpAdder = Module(new Adder())
val dataMem = Module(new DataMemory())
val instrMem = Module(new InstructionMemory())
// Mux from data memory
val dataMux = Mux(control.io.memToReg, dataMem.io.readDataOutput, alu.io.result)
// Mux to register file
val regFileMux = Mux(control.io.writeSrc, pcPlusFour.io.result, dataMux)
// PC + 4
pcPlusFour.io.in1 := pc
pcPlusFour.io.in2 := 4.U
// Instruction memory
instrMem.io.address := pc
val instruction = instrMem.io.instruction
val opcode = instruction(6, 0)
// Control
control.io.opcode := opcode
// Register file
regFile.io.readReg1 := instruction(19, 15) // rs1
regFile.io.readReg2 := instruction(24, 20) // rs2
regFile.io.writeReg := instruction(11, 7) // rd
regFile.io.regWrite := control.io.regWrite
regFile.io.writeData := regFileMux
// ALU
val aluMux1 = Mux(control.io.aluSrc1, immGen.io.extendedU, regFile.io.readData1)
alu.io.in1 := aluMux1
val src = control.io.aluSrc2
val aluMux2 = Mux(src === 1.U, immGen.io.extendedI, Mux(src === 2.U, immGen.io.extendedS, Mux(src === 3.U, pc, regFile.io.readData2)))
alu.io.in2 := aluMux2
alu.io.aluOp := aluControl.io.output
// ALU control
aluControl.io.aluOp := control.io.aluOp
aluControl.io.funct7 := instruction(31, 25)
aluControl.io.funct3 := instruction(14, 12)
// Data Memory
dataMem.io.readAddress := alu.io.result
dataMem.io.writeData := regFile.io.readData2
dataMem.io.memWrite := control.io.memWrite
dataMem.io.memRead := control.io.memRead
// Immediate generator
immGen.io.instr := instruction
// Branch logic
branchLogic.io.reg1 := regFile.io.readData1
branchLogic.io.reg2 := regFile.io.readData2
branchLogic.io.branch := control.io.branch
branchLogic.io.funct3 := instruction(14, 12)
// Jump reg
jumpReg.io.reg1 := regFile.io.readData1
jumpReg.io.imm := immGen.io.extendedI
// Jump
jumpAdder.io.in1 := pc
jumpAdder.io.in2 := immGen.io.extendedJ
// Branch
branchUnit.io.in1 := pc
branchUnit.io.in2 := immGen.io.extendedB
// PC-select
pcSelect.io.pcPlus4 := pcPlusFour.io.result
pcSelect.io.branch := branchUnit.io.result
pcSelect.io.jump := jumpAdder.io.result
pcSelect.io.jalr := jumpReg.io.output
pcSelect.io.branchSignal := branchLogic.io.result
pcSelect.io.jumpSignal := control.io.jump
pcSelect.io.jalrSignal := control.io.jumpReg
pc := pcSelect.io.output
}
So my questions are:
How can I simulate this design to see that it executes all instructions properly?
I would like to run the benchmark "dhrystone" on it to measure performance. How can I do that (is it possible?)? I'm not sure how to handle syscalls if that is needed.
Thanks in advance!

Great question: there are a number of ways to approach this.
A common approach is to take the generated Verilog from Chisel and write your own test-harness to instantiate your design. This test-harness can be written in C++, Verilog, SystemVerilog, or your other favorite test-harness/glue language.
This approach is used by Sodor (https://github.com/ucb-bar/riscv-sodor) and Rocket-Chip (https://github.com/freechipsproject/rocket-chip), with the outermost test-harness code written in C++, but able to interface with Verilog simulators like Verilator and VCS. The C++ test logic allows the user to pass in the test binary through the command line and to then load the binary -- through some sort of "magic" -- into the test memory. This magic may be an External Debug Interface, a Tether Serial Interface, or it may be a provided external RAM model that can be loaded by the test-harness (either something simple you write yourself or something as complex as dramsim2).
That stuff is fairly complex, so I recommend starting simple; one option is to create a blackbox memory in Chisel that is backed by a simple memory that uses readmemh to initialize itself. The nice feature here is you don't need to recompile your code to run new binaries, you just swap out the file you want to load into the test memory.
Chisel also provides its own self-contained testers, so perhaps you could do your test-harness entirely within Scala, but I have not seen this done for something as complex as a core which is very reliant on external stimuli and a need to communicate with the outside world.

Chisel design Risc-V cpu
https://fatalfeel.blogspot.com/2013/12/chisel-design-ic-for-risc-v.html
using the rocket-chip be cpu core, you can find ~/XiangShan/rocket-chip there
simulator run c++ compiler and you can using Vivado run simulator
also can debug in intellij IDE

Related

I want to make adder tree with chisel 3

First of all, I implemented adder which is shown below.
class AdderSwitch(InputBandwidth: Int, OutputBandwidth: Int, NumberOfOutputBuffer: Int) extends Module {
val io = IO(new Bundle {
val InputLeft = Input(SInt(InputBandwidth.W))
val InputRight = Input(SInt(InputBandwidth.W))
val OutputLeft = Output(SInt(InputBandwidth.W))
val OutputSum = Output(SInt(OutputBandwidth.W))
val OutputRight = Output(SInt(InputBandwidth.W))
val PassingOrSum = Input(Bool())
val OutputLeftReady = Input(Bool())
val OutputSumReady = Input(Bool())
val OutputRightReady = Input(Bool())
})
val Demux0 = Module(new Demux(InputBandwidth = InputBandwidth))
Demux0.io.Sel := io.PassingOrSum
Demux0.io.DemuxInput := io.InputLeft
val Demux1 = Module(new Demux(InputBandwidth = InputBandwidth))
Demux1.io.Sel := io.PassingOrSum
Demux1.io.DemuxInput := io.InputRight
val OutputSumWire = Wire(SInt(OutputBandwidth.W))
OutputSumWire := Demux0.io.Output1 + Demux1.io.Output1// + Mux(io.ForwardingMux, io.ForwardRightInput, io.ForwardLeftInput)
val OutputLeftBuffer = Module (new Queue(SInt(), NumberOfOutputBuffer))
val OutputSumBuffer = Module (new Queue(SInt(), NumberOfOutputBuffer))
val OutputRightBuffer = Module (new Queue(SInt(), NumberOfOutputBuffer))
OutputLeftBuffer.io.enq.bits := Mux(io.PassingOrSum, OutputSumWire, Demux0.io.Output0)
OutputLeftBuffer.io.enq.valid := true.B
OutputSumBuffer.io.enq.bits := OutputSumWire
OutputSumBuffer.io.enq.valid := true.B
OutputRightBuffer.io.enq.bits := Mux(io.PassingOrSum, OutputSumWire, Demux1.io.Output0)
OutputRightBuffer.io.enq.valid := true.B
io.OutputLeft := OutputLeftBuffer.io.deq.bits
io.OutputSum := OutputSumBuffer.io.deq.bits
io.OutputRight := OutputRightBuffer.io.deq.bits
OutputLeftBuffer.io.deq.ready := io.OutputLeftReady
OutputSumBuffer.io.deq.ready := io.OutputSumReady
OutputRightBuffer.io.deq.ready := io.OutputRightReady
}
I checked my code works well when it is tested.
I want to make adder tree which looks like below with chisel 3.
I was trying to use my adder code as a node of this tree.
So, I decided to use scala library, scala.immutable.collection.
However, there are so many object and class. And, I am very new to this definition.
To implement scala tree data structure for my chisel code, what do I have to do first?

Scala Chisel Ripple Carry Adder Syntax

im trying to design the following Ripple Carry Adder made of Fulladers. I tried a lot so far, but I'm struggling with Chisel Syntax. Could someone help me out and point out what I'm doing wrong? This is my Code below:
class RcaAdder(val n:Int) extends Module {
val io = IO(new Bundle {
val a = Input(UInt(n.W))
val b = Input(UInt(n.W))
val cin = Input(UInt(1.W))
val sum = Output(UInt(n.W))
val cout = Output(UInt(1.W))
})
//For loop
for(i <- 0 to n){
val fulladder = Module(new FullAdder())
fulladder.io.a := io.a(i)
fulladder.io.b := io.b(i)
if(i == 0){
fulladder.io.cin := io.cin
}else{
fulladder.io.cin := io.cout
}
io.cout := fulladder.io.cout
io.sum(i) := fulladder.io.sum
}
}
Which gets me the following error:
Exception in thread "main" chisel3.internal.ChiselException: Cannot reassign to read-only Bool(OpResult in RcaAdder)
I assume it has something to do with the " io.sum(i) := .. "
Please help me out! Thank you so much!
You are very close to getting it working. One problem you are having is that you cannot assign to a bit subset on the left hand side of :=. One way of getting around this is to create a Vec of UInt(1.W) and then use that as the RHS as a single as a single assignment. I think you have a problem with your ifs, I'd recommend using foldLeft instead of for because it provides a mechanism of accessing the previous elements. Put that all together and I think what you want is something like this.
class RcaAdder(n: Int) extends Module {
val io = IO(new Bundle {
val a = Input(UInt(n.W))
val b = Input(UInt(n.W))
val cin = Input(UInt(1.W))
val sum = Output(UInt(n.W))
val cout = Output(UInt(1.W))
})
val outBits = Wire(Vec(n, UInt(1.W)))
io.cout := (0 until n).foldLeft(io.cin) { case (carry, index) =>
val fullAdder = Module(new FullAdder)
fullAdder.io.a := io.a(index)
fullAdder.io.b := io.b(index)
fullAdder.io.cin := carry
outBits(index) := fullAdder.io.sum
fullAdder.io.cout. // This will be passed as carry to the next interation
}
io.sum := outBits.asUInt()
}
I've added a working test example here on scastie.
Good luck and welcome to Chisel

Embedding resources in scala.js

I have a localization resource file I need access from scala.js. It needs to be local to the script execution environment (i.e., not loaded asynchronously from a server, as recommended at How to read a resource file in Scala.js?).
Is there any mechanism for embedding the contents of a small resource file directly into the generated javascript compiled from a scala.js file? Basically, I want something like:
object MyResource {
#EmbeddedResource(URL("/my/package/localized.txt"))
val resourceString: String = ???
}
This would obviously bloat the generated .js file somewhat, but that is an acceptable tradeoff for my application. It seems like this wouldn't be an uncommon need and that this macro ought to already exist somewhere, but my initial searches haven't turned anything up.
If you are using sbt, you can use a source generator that reads your resource file and serializes it in a val inside an object:
sourceGenerators in Compile += Def.task {
val baseDir = baseDirectory.value / "custom-resources" // or whatever
val resourceFile = baseDir / "my/package/localized.txt"
val sourceDir = (sourceManaged in Compile).value
val sourceFile = sourceDir / "Localized.scala"
if (!sourceFile.exists() ||
sourceFile.lastModified() < resourceFile.lastModified()) {
val content = IO.read(resourceFile).replaceAllLiterally("$", "$$")
val scalaCode =
s"""
package my.package.localized
object Localized {
final val content = raw\"\"\"$content\"\"\"
}
"""
IO.write(sourceFile, scalaCode)
}
Seq(sourceFile)
}.taskValue
If you are using another build tool, I am sure there is a similar concept of source generators that you can use.

Large file download with Play framework

I have a sample download code that works fine if the file is not zipped because I know the length and when I provide, it I think while streaming play does not have to bring the whole file in memory and it works. The below code works
def downloadLocalBackup() = Action {
var pathOfFile = "/opt/mydir/backups/big/backup"
val file = new java.io.File(pathOfFile)
val path: java.nio.file.Path = file.toPath
val source: Source[ByteString, _] = FileIO.fromPath(path)
logger.info("from local backup set the length in header as "+file.length())
Ok.sendEntity(HttpEntity.Streamed(source, Some(file.length()), Some("application/zip"))).withHeaders("Content-Disposition" -> s"attachment; filename=backup")
}
I don't know how the streaming in above case takes care of the difference in speed between disk reads(Which are faster than network). This never runs out of memory even for large files. But when I use the below code, which has zipOutput stream I am not sure of the reason to run out of memory. Somehow the same 3GB file when I try to use with zip stream, is not working.
def downloadLocalBackup2() = Action {
var pathOfFile = "/opt/mydir/backups/big/backup"
val file = new java.io.File(pathOfFile)
val path: java.nio.file.Path = file.toPath
val enumerator = Enumerator.outputStream { os =>
val zipStream = new ZipOutputStream(os)
zipStream.putNextEntry(new ZipEntry("backup2"))
val is = new BufferedInputStream(new FileInputStream(pathOfFile))
val buf = new Array[Byte](1024)
var len = is.read(buf)
var totalLength = 0L;
var logged = false;
while (len >= 0) {
zipStream.write(buf, 0, len)
len = is.read(buf)
if (!logged) {
logged = true;
logger.info("logging the while loop just one time")
}
}
is.close
zipStream.close()
}
logger.info("log right before sendEntity")
val kk = Ok.sendEntity(HttpEntity.Streamed(Source.fromPublisher(Streams.enumeratorToPublisher(enumerator)).map(x => {
val kk = Writeable.wByteArray.transform(x); kk
}),
None, Some("application/zip"))
).withHeaders("Content-Disposition" -> s"attachment; filename=backupfile.zip")
kk
}
In the first example, Akka Streams handles all details for you. It knows how to read the input stream without loading the complete file in memory. That is the advantage of using Akka Streams as explained in the docs:
The way we consume services from the Internet today includes many instances of streaming data, both downloading from a service as well as uploading to it or peer-to-peer data transfers. Regarding data as a stream of elements instead of in its entirety is very useful because it matches the way computers send and receive them (for example via TCP), but it is often also a necessity because data sets frequently become too large to be handled as a whole. We spread computations or analyses over large clusters and call it “big data”, where the whole principle of processing them is by feeding those data sequentially—as a stream—through some CPUs.
...
The purpose [of Akka Streams] is to offer an intuitive and safe way to formulate stream processing setups such that we can then execute them efficiently and with bounded resource usage—no more OutOfMemoryErrors. In order to achieve this our streams need to be able to limit the buffering that they employ, they need to be able to slow down producers if the consumers cannot keep up. This feature is called back-pressure and is at the core of the Reactive Streams initiative of which Akka is a founding member.
At the second example, you are handling the input/output streams by yourself, using the standard blocking API. I'm not 100% sure about how writing to a ZipOutputStream works here, but it is possible that it is not flushing the writes and accumulating everything before close.
Good thing is that you don't need to handle this manually since Akka Streams provides a way to gzip a Source of ByteStrings:
import javax.inject.Inject
import akka.util.ByteString
import akka.stream.scaladsl.{Compression, FileIO, Source}
import play.api.http.HttpEntity
import play.api.mvc.{BaseController, ControllerComponents}
class FooController #Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def download = Action {
val pathOfFile = "/opt/mydir/backups/big/backup"
val file = new java.io.File(pathOfFile)
val path: java.nio.file.Path = file.toPath
val source: Source[ByteString, _] = FileIO.fromPath(path)
val gzipped = source.via(Compression.gzip)
Ok.sendEntity(HttpEntity.Streamed(gzipped, Some(file.length()), Some("application/zip"))).withHeaders("Content-Disposition" -> s"attachment; filename=backup")
}
}

Scopes in Chisel and scala

I am new to chisel and I have designed basic circuits and a simple risc-v processor with it but I still can't figure out how the scope works in Scala/ Chisel. Considering the following simple counter:
package example
import chisel3._
class GenericCounter(n: Int) extends Module {
val io = IO(new Bundle {
val ld = Input(UInt(1.W))
val cld = Input(UInt(log2Ceil(n).W))
val cout = Output(UInt(log2Ceil(n).W))
})
val cnt = RegInit(0.asUInt(n.W))
when(io.ld === 1.U){
cnt := io.cld
} .otherwise{
cnt := Mux(cnt===100.U, 0.U, cnt + 1.U)
}
io.cout := cnt
}
While trying to compile the above code, the compiler give an error that log2ceil is not defined. But if I use util.log2ceil it works fine. This is true for all util functions such as Cat, isPow2 etc. I know that the import chisel3._ should have imported all the necessary functions but it seems that I am missing something here. Can someone help me out?
In Scala, importing all of the contents of a package does not import the contents of any subpackages. Thus if you wish to import the contents of chisel.util you should also write import chisel3.util._