SyncReadMem generated verilog vs. Rocketchip emitted verilog - chisel

I am using SyncReadMem() for sram behavioral simulation. With the generated Verilog by verilator, I hope to replace it with a commercial sram compiler compiled verilog such that I can do synthesis for the whole design including sram.
However, I noticed that the verilog emitted by SyncReadMem() is not with uniform IOs just like the sram emitted in rocketchip. I wonder how do we generate some sram verilog just like the rocketchip one, using chisel mem API like SyncReadMem()?

You can use the Scala FIRRTL Compiler's "Replace Sequential Memories" pass to blackbox the memories. This is exactly what is happening with Rocket Chip.
Note that this is limited to only work if the memories have a single read port and a single write port and with read latency 1 and write latency 1.
As an example, consider the following 1r1w (one read, one write) SyncReadMem:
import chisel3._
class Foo extends MultiIOModule {
val read = IO(new Bundle {
val en = Input(Bool())
val addr = Input(UInt(8.W))
val data = Output(UInt(1.W))
})
val write = IO(new Bundle{
val en = Input(Bool())
val addr = Input(UInt(8.W))
val data = Input(UInt(1.W))
})
val bar = SyncReadMem(256, UInt(1.W))
read.data := bar.read(read.addr, read.en)
when (write.en) {
bar.write(write.addr, write.data)
}
}
If you compile this with a request to run the replace sequential memories pass:
(new ChiselStage)
.emitVerilog(new Foo, Array("--repl-seq-mem", "-c:Foo:-o:Foo.mem.conf"))
The arguments used there are -c:<circuit> where <circuit> is the name of the circuit you want to run on and -o:<mem-conf-file> is the name of a file to generate that will contain information (e.g., name, width, and depth) of the memories that were blackboxed.
You wind up with the memory blackboxed inside a new module bar called bar_ext:
module bar(
input [7:0] R0_addr,
input R0_en,
input R0_clk,
output R0_data,
input [7:0] W0_addr,
input W0_en,
input W0_clk,
input W0_data
);
wire [7:0] bar_ext_R0_addr;
wire bar_ext_R0_en;
wire bar_ext_R0_clk;
wire bar_ext_R0_data;
wire [7:0] bar_ext_W0_addr;
wire bar_ext_W0_en;
wire bar_ext_W0_clk;
wire bar_ext_W0_data;
bar_ext bar_ext (
.R0_addr(bar_ext_R0_addr),
.R0_en(bar_ext_R0_en),
.R0_clk(bar_ext_R0_clk),
.R0_data(bar_ext_R0_data),
.W0_addr(bar_ext_W0_addr),
.W0_en(bar_ext_W0_en),
.W0_clk(bar_ext_W0_clk),
.W0_data(bar_ext_W0_data)
);
assign bar_ext_R0_clk = R0_clk;
assign bar_ext_R0_en = R0_en;
assign bar_ext_R0_addr = R0_addr;
assign R0_data = bar_ext_R0_data;
assign bar_ext_W0_clk = W0_clk;
assign bar_ext_W0_en = W0_en;
assign bar_ext_W0_addr = W0_addr;
assign bar_ext_W0_data = W0_data;
endmodule
You can then run a memory compiler to consume the information in the memory configuration file and drop the output in place of bar_ext.

Related

How to link verilog blackbox to memory of rocketchip in chisel?

I am trying to attach a verilog module to rocketchip's memory. More precisely, I want to integrate a memory encryption engine as a blackbox. My idea is to link my verilog module to memAXI4Node of trait CanHaveMasterAXI4MemPort and io_axi4 node of SimAXIMem.
The verilog module has IOs for AXI ports, clock and reset.
My first try looks something like this:
SimAXIMem.scala
def connectMem(dut: CanHaveMasterAXI4MemPort)(implicit p: Parameters): Seq[SimAXIMem] = {
dut.mem_axi4.zip(dut.memAXI4Node.in).map { case (io, (_, edge)) =>
val mem = LazyModule(new SimAXIMem(edge, base = p(ExtMem).get.master.base, size = p(ExtMem).get.master.size))
Module(mem.module).suggestName("mem")
val blackbox = Module(new MyBlackBox())
blackbox.io.s_axi_awid := io.aw.bits.id
blackbox.io.s_axi_awaddr := io.aw.bits.addr
...
mem.io_axi4.head.aw.bits.id := blackbox.io.m_axi_awid
mem.io_axi4.head.aw.bits.addr := blackbox.io.m_axi_awaddr
...
//not working
val clock: Clock
blackbox.io.clock := clock
mem
}
}
Is there a proper way to put my verilog module between those two nodes?
How can I assign the clock to my blackbox, because this is only achievable inside a trait or module. But I assume, to connect the blackbox to memory the instantiation has to be done inside the method.
Jason

Chisel test - internal signals

I would like to test my code, so I'm doing a testbench. I wanted to know if it was possible to check the internal signals -like the value of the state register in this example- or if the peek was available only for the I/O
class MatrixMultiplier(matrixSize : UInt, cellSize : Int) extends Module {
val io = IO(new Bundle {
val writeEnable = Input(Bool())
val bufferSel = Input(Bool())
val writeAddress = Input(UInt(14.W)) //(matrixSize * matrixSize)
val writeData = Input(SInt(cellSize.W))
val readEnable = Input(Bool())
val readAddress = Input(UInt(14.W)) //(matrixSize * matrixSize)
val readReady = Output(Bool())
val readData = Output(SInt((2 * cellSize).W))
})
val s_idle :: s_writeMemA :: s_writeMemB :: s_multiplier :: s_ready :: s_readResult :: Nil = Enum(6)
val state = RegInit(s_idle)
...
and for the testbench :
class MatrixUnitTester(matrixMultiplier: MatrixMultiplier) extends PeekPokeTester(matrixMultiplier) { //(5.asUInt(), 32.asSInt())
println("State is: " + peek(matrixMultiplier.state).toString) // is it possible to have access to state ?
poke(matrixMultiplier.io.writeEnable, true.B)
poke(matrixMultiplier.io.bufferSel, false.B)
step(1)
...
EDIT : Ok, with VCD + GTKWave it is possible to graphically see these variables ;)
Good question. There's several parts to this answer
The Chisel supplied unit testing frameworks older chisel-testers and the newer chiseltest. Do not provide a mechanism to look into the wires directly.
Currently the chisel team is looking into ways of doing that.
Both provide indirect ways of doing it. Writing VCD output and using printf to see internal values
The Treadle firrtl simulator, which can directly simulate a firrtl (the direct output of the Chisel compiler) does allow for peek, and poking any signal directly. There are lots of examples of how its use in Treadle's unit tests. Treadle also provides a REPL shell which can be useful for exploring a circuit with manual peeks and pokes
The older chiseltesters (io-testers) and current chiseltest frameworks allow debugging the signal values with .peek() function that works well for the interface signals.
I haven't found a way to peek() an internal signal while debugging a testcase. However, Treadle simulator can dump the values of internal signals when it is running in verbose mode:
Add the annotation treadle.VerboseAnnotation to the test:
`test(new DecoupledGcd(16)).withAnnotations(Seq(WriteVcdAnnotation, treadle.VerboseAnnotation))`
When debugging in the IDEA and the test stops at breakpoint, the changes in the values of all internal signals up to this point are dumped to the Console.
This example will also generate the VCD wave file for further debugging.

Maintain connection order on FIRRTL using Cat operator

I want to ask for any idea for the following problem :
I want to connect the input port of a block named dut whose width is 787:0 bits, to a byte interface. I am doing like following :
val io = this.IO(new VerilatorHarnessIO(input_byte_count, output_byte_count*2))
val dut = Module(new DUT(dut_conf))
// inputs
val input_bytes = Cat(io.input_bytes)
val input_width = input_byte_count * 8
dut.io.inputs := input_bytes(input_width-1, input_width - dut_conf.inputBits)
I want that the order of the connection is preserved, ie:
Byte_0[7:0] ->input[7:0]
Byte_1[7:0] ->input[15:8]
But what I am getting is :
Byte_0[7:0] ->input[787:780]
Byte_1[7:0] ->input[779:772]
It will be much easier to debug if the ports are matched.
Is there a way to make this connection in the right order?
Thank you
Using the reverse method before you Cat should do what you want.
Consider the following Chisel:
import chisel3._
import chisel3.stage.{ChiselStage, ChiselGeneratorAnnotation}
import chisel3.util.Cat
class Foo extends RawModule {
val in = IO(Input(Vec(4, UInt(8.W))))
val out = IO(Output(UInt(32.W)))
out := Cat(in.reverse)
}
(new ChiselStage)
.execute(Array.empty, Seq(ChiselGeneratorAnnotation(() => new Foo)))
This produces the following Verilog with the bytes in the order you're looking for:
module Foo(
input [7:0] in_0,
input [7:0] in_1,
input [7:0] in_2,
input [7:0] in_3,
output [31:0] out
);
wire [15:0] _T; // #[Cat.scala 29:58]
wire [15:0] _T_1; // #[Cat.scala 29:58]
assign _T = {in_1,in_0}; // #[Cat.scala 29:58]
assign _T_1 = {in_3,in_2}; // #[Cat.scala 29:58]
assign out = {_T_1,_T}; // #[<pastie> 25:7]
endmodule
Cat appears backwards because it matches Verilog semantics and is thus backwards from the perspective of Scala semantics.
Consider:
val xs = List(8, 9, 10)
println(xs(0)) // 8
The left-most element is the lowest order index in Scala. However, in Verilog, you get the opposite:
assign x = {4'hde, 4'had};
The left-most part of that concatenation is actually the high-order nibble in the result. Chisel Cat was made to match Verilog semantics which makes it somewhat counter-intuitive in Scala.
As Schuyler mentioned, you can always reverse your Vec or Seq argument to Cat. Alternatively, you can cast to a UInt which will use the more intuitive Scala order:
import chisel3._
class Foo extends RawModule {
val in = IO(Input(Vec(4, UInt(8.W))))
val out = IO(Output(UInt(32.W)))
out := in.asUInt
}
.asUInt is defined on all Chisel Data, so you can use it to cast Bundles and other types to UInt as well. The only catch is that many methods defined on Vec return Seq, the Scala supertype of Vec which is not a chisel Data. This means you cannot do something like myVec.map(_ === 0.U).asUInt. You can always cast a Seq[T <: Data] (ie. a Seq containing Chisel Data elements) to a Vec via VecInit(mySeq), so you could do VecInit(myVec.map(_ === 0.U)).asUInt

Chisel/FIRRTL constant propagation & optimization across hierarchy

Consider a module that does some simple arithmetic and is controlled by a few parameters. One parameter controls the top level behavior: the module either reads its inputs from its module ports, or from other parameters. Therefore, the result will either be dynamically computed, or statically known at compile (cough, synthesis) time.
The Verilog generated by Chisel has different module names for the various flavors of this module, as expected. For the case where the result is statically known, there is a module with just one output port and a set of internal wires that are assigned constants and then implement the arithmetic to drive that output.
Is it possible to ask Chisel or FIRRTL to go further and completely optimize this away, i.e. in the next level of hierarchy up, just replace the instantiated module with its constant and statically known result? (granted that these constant values should by optimized away during synthesis, but maybe there are complicated use cases where this kind of elaboration time optimization could be useful).
For simple things that Firrtl currently knows how to constant propagate, it already actually does this. The issue is that it currently doesn't const prop arithmetic operators. I am planning to expand what operators can be constant propagated in the Chisel 3.1 release expected around New Years.
Below is an example of 3.0 behavior constant propagating a logical AND and a MUX.
import chisel3._
class OptChild extends Module {
val io = IO(new Bundle {
val a = Input(UInt(32.W))
val b = Input(UInt(32.W))
val s = Input(Bool())
val z = Output(UInt(32.W))
})
when (io.s) {
io.z := io.a & "hffff0000".U
} .otherwise {
io.z := io.b & "h0000ffff".U
}
}
class Optimize extends Module {
val io = IO(new Bundle {
val out = Output(UInt())
})
val child = Module(new OptChild)
child.io.a := "hdeadbeef".U
child.io.b := "hbadcad00".U
child.io.s := true.B
io.out := child.io.z
}
object OptimizeTop extends App {
chisel3.Driver.execute(args, () => new Optimize)
}
The emitted Verilog looks like:
module Optimize(
input clock,
input reset,
output [31:0] io_out
);
assign io_out = 32'hdead0000;
endmodule

Chisel: mapping separate input and output ports to inout pin

I'm generating Verilog from Chisel 3 source code and mapping the Verilog's top module ports to FPGA pins by using an UCF file.
I have a set of inout pins in my design (SDRAM data pins), which on the Chisel side have to be expressed as separate input and output ports. The problem is, I can't (AFAIK) then map the Verilog input ports and output ports to the same FPGA pin (if I were directly writing Verilog those would be a single inout signal, so that wouldn't be an issue) and I don't know of anyway to coerce Chisel 3 to produce a single Verilog inout port from two input/output Chisel ports.
How is this usually solved in Chisel (3)?
We are working on some level of support for Verilog inout in Chisel 3, but until that API is fully fleshed out you should write a Verilog wrapper that converts from from inout to an input, output, and some direction.
For example, say I have some Verilog with an inout pin that can be used to set or read from some register:
module Inout(
input clock,
input write,
inout [31:0] data
);
reg [31:0] value;
assign data = (write) ? 32'dz : value;
always #(posedge clock) begin
if (write)
value <= data;
else
value <= value;
end
endmodule
With a simple wrapper, I can expose a different interface that does not use inout:
module InoutWrapper(
input clock,
input write,
input [31:0] dataIn,
output [31:0] dataOut
);
wire [31:0] bus;
assign bus = (write)? dataIn : 32'dz;
assign dataOut = bus;
Inout mod (
.clock(clock),
.write(write),
.data(bus)
);
endmodule
This wrapper interface can be used in a Chisel design as a BlackBox:
class InoutWrapper extends BlackBox {
val io = IO(new Bundle {
val clock = Input(Clock())
val write = Input(Bool())
val dataIn = Input(UInt(32.W))
val dataOut = Output(UInt(32.W))
})
}
And here's a bonus simple sanity test to show that it works:
class InoutTester extends BasicTester {
val mod = Module(new InoutWrapper)
val (cycle, done) = Counter(true.B, 4)
when (done) { stop(); stop() }
mod.io.clock := this.clock // BlackBoxes require explicit clock assignment
mod.io.write := false.B // default assignments
mod.io.dataIn := "hdeadbeef".U
when (cycle === 1.U) {
mod.io.write := true.B
mod.io.dataIn := 123.U
}
when (cycle === 2.U) {
assert(mod.io.dataOut === 123.U)
}
}
If the inout port is at the top of the design, you can create a similar kind of wrapper for the top of your design.