Vivado can't recognize the double-port RAM while using SyncReadMem - chisel

I want to create a true double-port RAM in Chisel and synthesize the Verilog code in Vivado 2018.3. Here is my Chisel code:
class DoublePortsRAM extends Module {
val io = IO(new Bundle {
val addr1 = Input(UInt(10.W))
val dataIn1 = Input(UInt(32.W))
val en1 = Input(Bool())
val we1 = Input(Bool())
val dataOut1 = Output(UInt(32.W))
val clk2 = Input(Clock())
val reset2 = Input(Bool())
val addr2 = Input(UInt(10.W))
val dataIn2 = Input(UInt(32.W))
val en2 = Input(Bool())
val we2 = Input(Bool())
val dataOut2 = Output(UInt(32.W))
})
val syncRAM = SyncReadMem(1024, UInt(32.W))
when(io.en1) {
when(io.we1) {
syncRAM.write(io.addr1, io.dataIn1)
io.dataOut1 := DontCare
} .otherwise {
io.dataOut1 := syncRAM.read(io.addr1)
}
} .otherwise {
io.dataOut1 := DontCare
}
withClockAndReset(io.clk2, io.reset2) {
when(io.en2) {
when(io.we2) {
syncRAM.write(io.addr2, io.dataIn2)
io.dataOut2 := DontCare
} .otherwise {
io.dataOut2 := syncRAM.read(io.addr2)
}
} .otherwise {
io.dataOut2 := DontCare
}
}
}
After compiling it, some Verilog codes like these:
always #(posedge clock) begin
...
if (_GEN_36) begin
syncRAM__T_1_addr_pipe_0 <= io_addr1;
end
end
...
assign syncRAM__T_1_addr = syncRAM__T_1_addr_pipe_0;
assign syncRAM__T_1_data = syncRAM[syncRAM__T_1_addr];
assign io_dataOut1 = syncRAM__T_1_data;
...
However, Vivado reports that it can't infer the pattern of the RAM. I must modify the Verilog code manually to these:
always #(posedge clock) begin
...
if (_GEN_36) begin
syncRAM__T_1_addr_pipe_0 <= syncRAM[io_addr1];
end
end
...
assign io_dataOut1 = syncRAM__T_1_addr_pipe_0;
...
In other words, the Synchronous Read RAM shouldn't use a register to store the reading address. On the contrary, it is OK to store the reading data. In addition, Vivado can synthesize the single-port RAM without modifying the Verilog. What's wrong with it?

Related

IP block generation/testing when using diplomacy. Possible to give dummy node?

I've been studying rocket-chip for utilizing diplomacy and I have a decent grasp on the overall structure of how diplomacy works. (I don't understand it totally, but well enough to create some examples on my own). I would like to develop some IP in which the main objective is to have a regmap through the use of a *RegisterRouter.
If I use/modify one of the RegisterNodeExamples from rocket-chip, I get the following:
class MyDeviceController(implicit p: Parameters) extends LazyModule {
val device = new SimpleDevice("my-device", Seq("tutorial,my-device0"))
val node = APBRegisterNode(
//address = Seq(AddressSet(0x10028000, 0xfff)), (Modified since not in APBRegisterNode)
address = AddressSet(0x002000, 0xfff),
//device = device, (Removed since not in APBRegisterNode)
beatBytes = 8)
lazy val module = new LazyModuleImp(this) {
val bigReg = RegInit(0.U(64.W))
val mediumReg = RegInit(0.U(32.W))
val smallReg = RegInit(0.U(16.W))
val tinyReg0 = RegInit(0.U(4.W))
val tinyReg1 = RegInit(0.U(4.W))
node.regmap(
0x00 -> Seq(RegField(64, bigReg)),
0x08 -> Seq(RegField(32, mediumReg)),
0x0C -> Seq(RegField(16, smallReg)),
0x0E -> Seq(
RegField(4, tinyReg0),
RegField(4, tinyReg1)))
}
}
I'm using APB at the moment as I'm very familiar with AMBA protocols and it has the smallest code base under the diplomacy package. And I could make is so either of the AMBA or TL protocols are used later.
My Question
Is there a way to generate verilog just for MyDeviceController as a stand alone component?
I have not been able to figure this out if there is. Obviously if I just try to instantiate MyDeviceController I will get an error for the inward parameters of node not being connected. I'm not sure if you can give a "dummy" node connection? Or if there is some method that can handle that.
Why I want to do this
It is desirable to test the IP standalone in it's own test environment without a full SoC.
My Current Workaround/Solution
To work around this I essentially created a "wrapper" that creates an APBMasterNode and connects to the APBRegisterNode in MyDeviceController.
class APBMaster()(implicit p: Parameters) extends LazyModule {
val apbMasterParameters = APBMasterParameters(
name = "apbMaster"
)
val apbMasterPortParameters = APBMasterPortParameters(
masters = Seq(apbMasterParameters)
)
val node = APBMasterNode(
portParams = Seq(apbMasterPortParameters)
)
lazy val module = new LazyModuleImp(this) {
val io = IO(new Bundle {
val wtf = Output(Bool())
val start = Input(Bool())
})
val myreg = RegInit(0.U(16.W))
myreg := myreg + 1.U
val prdata = Wire(UInt(64.W))
prdata := node.out.head._1.prdata
//seems to need these things to generate the logic
io.wtf := node.out.head._1.pready && !(node.out.head._1.prdata === 0.U)
node.out.head._1.pstrb := 63.U
node.out.head._1.pprot := 0.U
when(myreg(3,0) === 8.U && io.start) {
node.out.head._1.paddr := myreg
node.out.head._1.psel := true.B
node.out.head._1.penable := false.B
node.out.head._1.pwrite := true.B
node.out.head._1.pwdata := myreg + 1.U
} .elsewhen(myreg(3,0) === 9.U) {
node.out.head._1.paddr := myreg
node.out.head._1.psel := true.B
node.out.head._1.penable := true.B
node.out.head._1.pwrite := true.B
node.out.head._1.pwdata := myreg
} otherwise {
node.out.head._1.paddr := 0.U
node.out.head._1.psel := false.B
node.out.head._1.penable := false.B
node.out.head._1.pwrite := false.B
node.out.head._1.pwdata := 0.U
}
}
}
One issue with this, was that I had to create some controls for each of the APB signals. If I did not, the Chisel/FIRRTL compiler/generator would not create any Verilog for MyDeviceController. This is what you see above with the myreg counter being used to do some basic APB transaction.
The wrapper would look like the following:
class APBTop()(implicit p: Parameters) extends LazyModule {
val master = LazyModule(new APBMaster)
val slave = LazyModule(new MyDeviceController()(Parameters.empty))
slave.node := master.node
lazy val module = new LazyModuleImp(this) {
val io = IO(new Bundle {
val busy = Output(Bool())
val wtf = Output(Bool())
val start = Input(Bool())
})
io.busy := true.B
io.wtf := master.module.io.wtf
master.module.io.start := io.start
}
}
I can create this wrapper/master as a typical testing component, then in my testenv just instatiate the MyDeviceController RTL, however I was wondering if there was another solution. It appears that diplomacy is fairly holistic (which I understand why), but was looking for suggestions on how IP level development is tackled for a Diplomatic infrastructure flow.
Thanks
Edit: Update March 2021
It's been a few months and I've spent more time with RocketChip/Chipyard/Diplomacy. This is a better solution, but leaving the old one below.
There is a makeIOs method for several Nodes. Using these we can actually punch out the respective AMBA/TL interface. This allows you to not have to use a wrapper that has nothing but connections.
Here is what it would look like compared to the previous version I suggested
class MyWrapper()(implicit p: Parameters) extends LazyModule {
//val master = LazyModule(new APBMaster)
val ApbPort = APBMasterNode(
portParams = Seq(APBMasterPortParameters(masters = Seq(APBMasterParameters(name = "ApbPort"))))
)
val apbport = InModuleBody {ApbPort.makeIOs()}
This reduces the need for the APBMaster dummy class as well.
Just to have an answer, I ended up using a combination of what myself and Jack Koenig went back and forth on.
If time permits I'll see if there is a way to make a "template" or LazyModule wrapper that does this for testing purposes (for each of the main protocols) and submit it to the Chisel repo.
class APBMaster()(implicit p: Parameters) extends LazyModule {
val apbMasterParameters = APBMasterParameters(
name = "apbMaster"
)
val apbMasterPortParameters = APBMasterPortParameters(
masters = Seq(apbMasterParameters)
)
val node = APBMasterNode(
portParams = Seq(apbMasterPortParameters)
)
lazy val module = new LazyModuleImp(this) {
//The dontTouch here preserves the interface so logic is generated
dontTouch(node.out.head._1)
}
}
class MyWrapper()(implicit p: Parameters) extends LazyModule {
val master = LazyModule(new APBMaster)
val slave = LazyModule(new MySlave()(Parameters.empty))
slave.node := master.node
lazy val module = new LazyModuleImp(this) {
//nothing???
}
}
object MyTestextends App {SSVPllFreqDetect)))
(new ChiselStage).execute(args, Seq(ChiselGeneratorAnnotation(() => LazyModule(new MyWrapper()(Parameters.empty)).module)))
}

Simple chisel dual port memory Read port issue

I am trying to do the basic tutorial on chisel for verilog generation, I am trying to build a dual port memory:
import chisel3._
import chisel3.stage.ChiselStage
class Memo extends Module {
val io = IO(new Bundle {
val wen = Input(Bool())
val wrAddr = Input(UInt(8.W))
val wrData = Input(UInt(8.W))
val ren = Input(Bool())
val rdAddr = Input(UInt(8.W))
val rdData = Output(UInt(8.W))
})
val mem = Mem(256, UInt(8.W))
when(io.wen) {
mem(io.wrAddr) := io.wrData
}
io.rdData := 0.U
when(io.ren) {
io.rdData := mem(io.rdAddr)
}
}
println((new ChiselStage).emitVerilog(new Memo))
The above code compiles without issues. But I want to hold the rdData value to the old value if ren = 0. For that I commented line io.rdData := 0.U and I got the error:
Errors: 1: in the following tutorials
Tutorial Memo: exception #[:#6.4] : [module Memo] Reference io is not fully initialized.
#[Memo.scala 31:15:#15.6] : io.rdData <= mux(io.ren, mem._T_20.data, VOID) #[Memo.scala 31:15:#15.6]
How do I fix this? How to hold the previous value on rdData? Also what does the Error message mean, that io is not initialized?
The uninitialized error means there are possible simulation paths where the wire io.rdData had never been assigned. If you need to hold onto some value I'd suggest adding a register, something like this.
val mem = Mem(256, UInt(8.W))
when(io.wen) {
mem(io.wrAddr) := io.wrData
}
val lastValue = RegInit(0.U(8.W))
io.rdData := 0.U
when(io.ren) {
io.rdData := mem(io.rdAddr)
lastValue := io.rdData
}.otherwise {
io.rdData := lastValue
}

how to suggest name inside bundle in chisel3.2?

I'm using the suggestName API for IO(), for example
class TestModule extends MultiIOModule{
val AXI = IO(new AXIWriteIO(32,32,4)).suggestName("axi")
val S_AXI = IO(Flipped(new AXIWriteIO(32,32,4)))
AXI.AW.suggestName("aw")
AXI <> S_AXI
}
It works with the "axi" part, but not the "aw" part.
The suggestName also does not work inside the bundle definition class:
class AXIAddress(val addrWidthBits: Int, val idBits: Int) extends Bundle {
val id = UInt(idBits.W).suggestName("ID")
//...
}
Any idea?
chisel version is 3.2.0
I chisel3 it is not possible to use suggestName inside Bundles. The names emitted into FIRRTL is the chisel variable name. To get the correct naming of the external ports while still being able to use nested bundles internally to represent the AXI interface I have done something like this:
class AXIAddress extends Bundle {
val addr = UInt(32.W)
val size = UInt(4.W)
val len = UInt(2.W)
}
class AXIMasterIF extends Bundle {
val readAddr = Decoupled(new AXIAddress)
val writeAddr = Decoupled(new AXIAddress)
}
object AXIMasterIF {
def apply(): AXIMasterIF = {
val axi = Wire(new AXIMasterIF)
axi
}
}
class AXIExternalIF extends Bundle {
val AWADDR = Output(UInt(32.W))
val AWVALID = Output(Bool())
val AWREADY = Input(Bool())
val AWSIZE = Output(UInt(4.W))
val AWLEN = Output(UInt(2.W))
val ARADDR = Output(UInt(32.W))
val ARVALID = Output(Bool())
val ARREADY = Input(Bool())
val ARSIZE = Output(UInt(4.W))
val ARLEN = Output(UInt(2.W))
def connect(int: AXIMasterIF): Unit = {
this.AWADDR := int.writeAddr.bits.addr
this.AWVALID := int.writeAddr.valid
int.writeAddr.ready := this.AWREADY
this.AWSIZE := int.writeAddr.bits.size
this.AWLEN := int.writeAddr.bits.len
this.ARADDR := int.readAddr.bits.addr
this.ARVALID := int.readAddr.valid
int.readAddr.ready := this.ARREADY
this.ARSIZE := int.readAddr.bits.size
this.ARLEN := int.readAddr.bits.len
}
}
class TopWrapper extends MultiIOModule {
// First instantive the "internal", nested, representation of the AXI Slave interface
val axiInt = AXIMasterIF()
// Then instantiate the external, correctly named, AXI Slave interace that we can connect in Vivado
val axiExt = IO(new AXIExternalIF).suggestName("MM_AXI_0")
// Make connections
axiExt.connect(axiInt)
}
The AXIExternalIF is the one that is exposed to the rest of the system. It must be in the top level module of your design. AXIMasterIF which uses AXIAddres is connected to that interface and is used internally to connect things to the AXI interface.
If you wanna play around with it and look at the emitted verilog you can do that at Scastie

chisel partial bulk connection with "<>" operator

I'm having trouble to do partial bulk connection with <>.
I saw in the book Digital Design with Chisel (4.3 Bulk Connections).
It is allowed to connect two bundles with partially matched signals.
I'm currently working on chisel3.2. and it seems not working, and during elatorating it
reports
chisel3.internal.ChiselException: Connection between left (AnonymousBundle(IO io in Fetch)) and source (AnonymousBundle(IO io in Decode)) failed #.regB: Left Record missing field (regB).
Is this changed at some version?
If it is changed, how do we do partial connection now?
This is the testing code(don't mind the module, it is only to keep the signal from optimization):
class Fetch extends Module {
val io = IO(new Bundle {
val instr = Output(UInt(32.W))
val pc = Output(UInt(32.W))
})
val r = RegInit(0.U(32.W))
r := r + 1.U
io.pc := r
io.instr := r+1.U
}
class Decode extends Module {
val io = IO(new Bundle {
val instr = Input(UInt(32.W))
val pc = Input(UInt(32.W))
val aluOp = Output(UInt(5.W))
val regA = Output(UInt(32.W))
val regB = Output(UInt(32.W))
})
io.aluOp := io.pc
io.regA := io.instr
io.regB := io.instr
}
class Execute extends Module {
val io = IO(new Bundle {
val aluOp = Input(UInt(5.W))
val regA = Input(UInt(32.W))
val regB = Input(UInt(32.W))
val result = Output(UInt(32.W))
})
io.result := io.regA
when(io.aluOp > 10.U){
io.result := io.regB
}
}
object MAIN{
def main(args:Array[String]):Unit = {
Driver.execute(Array(""),()=>new Module{
val io = IO(new Bundle{
val result = Output(UInt(32.W))
})
val fetch = Module(new Fetch())
val decode = Module(new Decode())
val execute = Module(new Execute)
fetch.io <> decode.io
decode.io <> execute.io
io <> execute.io
})
}
}
Link:chisel3-vs-chisel2
In Unsupported constructs Chapter:
In Chisel2, bulk-connects <> with unconnected source components do not update connections from the unconnected components.
In Chisel3, bulk-connects strictly adhere to last connection semantics and unconnected OUTPUTs will be connected to INPUTs resulting in the assignment of random values to those inputs.
I'm sorry that I have not fully understanded how to use <> in Chisel3.In my opinion, you should avoid to use <> in Chisel3.
Here is an example that is avaiable in Chisel3.
Code is from here.
import Chisel.Queue
import chisel3._
import chisel3.stage.{ChiselGeneratorAnnotation, ChiselStage}
import chisel3.util.Decoupled
import layered.stage.ElkStage
class MyQueue extends Module {
// Example circuit using a Queue
val io = IO(new Bundle {
val in = Flipped(Decoupled(UInt(8.W)))
val out = Decoupled(UInt(8.W))
})
val queue = Queue(io.in, 2) // 2-element queue
io.out <> queue
}
object MyQueue extends App{
(new ChiselStage).emitVerilog(new MyQueue,Array("-td", "vout"))
// gengerate graph file and it can be viewed by using IDEA plugin named EasySocHDL
(new ElkStage).execute(
Array("-td", "vout", "--lowFir"),
Seq(ChiselGeneratorAnnotation(() => new MyQueue))
)
}

Wiring Seq of chisel modules

I was following the answer from here: How to do a vector of modules?.
I have a question regrading how to address previus items in the Seq. here is my code:
val ccfLinks = for (j <- 0 until (len - 1)) yield {
val exe_unit = Module(new Ccflink(bitwidth))
if (j == 0) {
// connnect the first stage //
exe_unit.io.i_in := io.i_in
exe_unit.io.q_in := io.q_in
exe_unit.io.coeff_i := coeffs_i(0)
exe_unit.io.coeff_q := coeffs_q(0)
exe_unit.io.pre_i_product := SInt(0)
exe_unit.io.pre_q_product := SInt(0)
} else {
// connect the rest of the chain //
exe_unit.io.i_in := ccfLinks(j-1).io.i_out
exe_unit.io.q_in := ccfLinks(j-1).io.q_out
exe_unit.io.coeff_i := coeffs_i(j)
exe_unit.io.coeff_q := coeffs_q(j)
exe_unit.io.pre_i_product := ccfLinks(j-1).io.i_product
exe_unit.io.pre_q_product := ccfLinks(j-1).io.q_product
}
exe_unit
}
I am trying to connect the current module to the previous, how do I address the previous module ?
trying to compile the code above result in the next error:
"Ccf.scala:240: recursive value ccfLinks needs type [error] exe_unit.io.i_in := ccfLinks(j-1).io.i_out"
Here is fairly straight forward way of doing it. Note the use of foldLeft which takes an head of the list and calls the code thunk with successive pairs. Using more advanced list comprehensions can make this a little more succinct, but I think this is pretty clear what's going on when.
class Element extends Module {
val io = IO(new Bundle {
val in0 = Input(UInt(8.W))
val in1 = Input(UInt(8.W))
val out0 = Output(UInt(8.W))
val out1 = Output(UInt(8.W))
})
val reg0 = RegNext(io.in0, 0.U)
val reg1 = RegNext(io.in1, 0.U)
io.out0 := reg0
io.out1 := reg1
}
/**
* wire together a bunch of elements, into a basic queue
* #param elementCount how big is the queue
*/
class ElementQueue(val elementCount: Int) extends Module {
val io = IO(new Bundle {
val in0 = Input(UInt(8.W))
val in1 = Input(UInt(8.W))
val out0 = Output(UInt(8.W))
val out1 = Output(UInt(8.W))
})
// create a scala Seq of Elements
val elements = Seq.fill(elementCount)(Module(new Element))
// wire the head to the inputs
elements.head.io.in0 := io.in0
elements.head.io.in1 := io.in1
// wire the elements of the queue
val last = elements.tail.foldLeft(elements.head) { case (prior, next) =>
next.io.in0 := prior.io.out0
next.io.in1 := prior.io.out1
next
}
// wire the end of the queue to the outputs
io.out0 := last.io.out0
io.out1 := last.io.out1
}