开云体育

ctrl + shift + ? for shortcuts
© 2025 开云体育

memory module with 2 read interfaces & 1 write interface


Sergey Smolov
 

Hello, I'm trying to implement a specific memory module (M). The problem is that it should have three "communication channels": two for read requests/responses and one for write requests. I mean that there are two modules (suppose, A and B) that send read requests to the memory module M and receive read responses from it, and also there is another module C that sends write requests only to the memory module. Here is how it is implemented in Chisel:
class VarWord extends Bundle {
var fixed = UInt(Config.VAR_MEM_WIDTH.W)
var value = UInt(Config.VAR_MEM_WIDTH.W)
}

class VarReadIface extends Bundle {
val en = Input(Bool())
val addr = Input(UInt(Config.VAR_ADDR_WIDTH.W))
val data = Output(new VarWord)
}

class VarWriteIface extends Bundle {
val en = Input(Bool())
val addr = Input(UInt(Config.VAR_ADDR_WIDTH.W))
val data = Input(new VarWord)
}

class VarMemory extends Module {
var io = IO(new Bundle {
// 2 read ports (for the reader and the assigner).
var read_in = Vec(2, new VarReadIface)
// 1 write port.
val write_in = new VarWriteIface
})

val invalid_word = Reg(new VarWord)

val mem = Mem(Config.VAR_MEM_LENGTH, new VarWord)
loadMemoryFromFile(mem, "var_mem.txt")

when(io.write_in.en) {
mem(io.write_in.addr) := io.write_in.data
}

for (i <- 0 to 1) {
io.read_in(i).data := Mux(io.read_in(i).en, mem(io.read_in(i).addr), invalid_word)
}
}
Could you give some recommendations on how to implement this structure on BSV? I think I can use two Server interfaces for communication with A and B modules, but what interface should I take for communication with C?

P.S. Dear colleagues, thank for your replies on my previous questions. I really appreciate your help!


 

It looks like what you want is a RegFile, but you want to be?explicit about which ports you are using.?Regfile have?a number of read ports and will give each use of "sub" a new port. This wrapping give you a physical boundary which (when synthesized separately) will give you the the number of read ports you want and let you explicitly multiplex each.

The server would makes sense for Reads if you want the request and response to be in different atomic actions. If so, you'd likely want a FIFO to hold values in between, but you should be able to find example of Bluespec BRAM modules that do this.

I would do this as something like this. I'm a bit rusty on the details of lighter weight function syntax so it's a more verbose?than needed. Also there are some obvious type name / matching issues, but those should?be straightforward to resolve.

interface ReadIfc;
? ? method ReadResp sub(ReadReq r);
endinterface

interface WriteIfc; // This could be Put#(Write)
? ? method Action upd(Write w);
endinterface

interface ExplicitlyPortedRegFile;
? ? ?interface Vector#(2, ReadIfc) reads;
? ? ?interface WriteIfc write;
endinterface

module mkReadIfc(RegFIle#(Address, Value) rf);
? ? ?method ReadResp sub(ReadReq r) = rf.sub(r);
endmodule

module mkWriteIfc(RegFIle#(Address, Value) rf);
? ? ?method Action upd(Write w) = rf.upd(w.addr, w.value);
endmodule

(* synthesize *)
module mkExplicitlyPortedRegFile(ExplicitlyPortedRegFile);
? ? ? RegFile#(Address, Value) rf <- mkRegFileLoad("var_mem.txt");

? ? ? interface reads = replicateM(mkReadIfc(rf));
? ? ? interface writes = mkWriteIfc(rf);

endmodule


Hope this helps,?

-Nirav




On Thu, Apr 22, 2021 at 11:50 AM Sergey Smolov <ssedai@...> wrote:
Hello, I'm trying to implement a specific memory module (M). The problem is that it should have three "communication channels": two for read requests/responses and one for write requests. I mean that there are two modules (suppose, A and B) that send read requests to the memory module M and receive read responses from it, and also there is another module C that sends write requests only to the memory module. Here is how it is implemented in Chisel:
class VarWord extends Bundle {
var fixed = UInt(Config.VAR_MEM_WIDTH.W)
var value = UInt(Config.VAR_MEM_WIDTH.W)
}

class VarReadIface extends Bundle {
val en = Input(Bool())
val addr = Input(UInt(Config.VAR_ADDR_WIDTH.W))
val data = Output(new VarWord)
}

class VarWriteIface extends Bundle {
val en = Input(Bool())
val addr = Input(UInt(Config.VAR_ADDR_WIDTH.W))
val data = Input(new VarWord)
}

class VarMemory extends Module {
var io = IO(new Bundle {
// 2 read ports (for the reader and the assigner).
var read_in = Vec(2, new VarReadIface)
// 1 write port.
val write_in = new VarWriteIface
})

val invalid_word = Reg(new VarWord)

val mem = Mem(Config.VAR_MEM_LENGTH, new VarWord)
loadMemoryFromFile(mem, "var_mem.txt")

when(io.write_in.en) {
mem(io.write_in.addr) := io.write_in.data
}

for (i <- 0 to 1) {
io.read_in(i).data := Mux(io.read_in(i).en, mem(io.read_in(i).addr), invalid_word)
}
}
Could you give some recommendations on how to implement this structure on BSV? I think I can use two Server interfaces for communication with A and B modules, but what interface should I take for communication with C?

P.S. Dear colleagues, thank for your replies on my previous questions. I really appreciate your help!


Sergey Smolov
 

Dear Nirav, thanks for your answer. I've tried your solution and here is my code:

typedef UInt#(VarMemWidth) VarMemUInt;

typedef struct { VarMemUInt fixed; VarMemUInt value; } VarWord deriving(Eq, Bits, FShow);
typedef struct { Bool enable; VarAddrBit address; } VarMemReadReq deriving (Eq,Bits);
typedef struct { Bool enable; VarAddrBit address; VarWord data; } VarMemWriteReq deriving(Eq, Bits, FShow);

typedef RegFile#(VarAddrBit, VarWord) VarMem;

interface ReadMem_IFC;
? method VarWord read(VarMemReadReq r_req);
endinterface

interface WriteMem_IFC;
? method Action write(VarMemWriteReq w_req);
endinterface

interface VarMemory_IFC;
? // 2 read ports (for the reader and the assigner).
? interface Vector#(2, ReadMem_IFC) read_in;
? // 1 write port.
? interface WriteMem_IFC write_in;
endinterface

module mkReadIFC#(VarMem file)(ReadMem_IFC);
? method VarWord read(VarMemReadReq r_req);
??? VarWord resp = r_req.enable ? file.sub(r_req.address) : ?;
??? return resp;
? endmethod
endmodule

module mkWriteIFC#(VarMem file)(WriteMem_IFC);
? method Action write(VarMemWriteReq w_req);
??? if (w_req.enable)
????? file.upd(w_req.address, w_req.data);
? endmethod
endmodule

(* synthesize *)
module mkVarMemory(VarMemory_IFC);

? Integer varNum = valueOf(MaxVarNum);

? VarWord invalid_word <- ?;
? VarMem reg_file????? <- mkRegFileLoad("var_mem.txt", 0, fromInteger(varNum));

? interface read_in? = replicateM(mkReadIFC(reg_file));
? interface write_in = mkWriteIFC(reg_file);

endmodule

Unfortunately, BSV compiler reports the following error:

Error: "VarMemory.bsv", line 67, column 24: (T0080)
? Type error at the use of the following function:
??? replicateM

? The expected return type of the function:
??? Vector::Vector#(2, VarMemory::ReadMem_IFC)

? The return type according to the use:
??? a__#(Vector::Vector#(c__, b__))

make: *** [Makefile:44: compile] Error 1

What am I doing wrong? The error line is highlighted with bold letters.


 

Oops. My mistake. all M suffixed operations are monadic and need the "<-" syntax.?

You'll need to break that into two lines:

let readIfcs?<- replicateM(mkReadIFC(read_file));
interface read_in = readIfcs;



On Fri, Apr 23, 2021 at 9:48 AM Sergey Smolov <ssedai@...> wrote:
Dear Nirav, thanks for your answer. I've tried your solution and here is my code:

typedef UInt#(VarMemWidth) VarMemUInt;

typedef struct { VarMemUInt fixed; VarMemUInt value; } VarWord deriving(Eq, Bits, FShow);
typedef struct { Bool enable; VarAddrBit address; } VarMemReadReq deriving (Eq,Bits);
typedef struct { Bool enable; VarAddrBit address; VarWord data; } VarMemWriteReq deriving(Eq, Bits, FShow);

typedef RegFile#(VarAddrBit, VarWord) VarMem;

interface ReadMem_IFC;
? method VarWord read(VarMemReadReq r_req);
endinterface

interface WriteMem_IFC;
? method Action write(VarMemWriteReq w_req);
endinterface

interface VarMemory_IFC;
? // 2 read ports (for the reader and the assigner).
? interface Vector#(2, ReadMem_IFC) read_in;
? // 1 write port.
? interface WriteMem_IFC write_in;
endinterface

module mkReadIFC#(VarMem file)(ReadMem_IFC);
? method VarWord read(VarMemReadReq r_req);
??? VarWord resp = r_req.enable ? file.sub(r_req.address) : ?;
??? return resp;
? endmethod
endmodule

module mkWriteIFC#(VarMem file)(WriteMem_IFC);
? method Action write(VarMemWriteReq w_req);
??? if (w_req.enable)
????? file.upd(w_req.address, w_req.data);
? endmethod
endmodule

(* synthesize *)
module mkVarMemory(VarMemory_IFC);

? Integer varNum = valueOf(MaxVarNum);

? VarWord invalid_word <- ?;
? VarMem reg_file????? <- mkRegFileLoad("var_mem.txt", 0, fromInteger(varNum));

? interface read_in? = replicateM(mkReadIFC(reg_file));
? interface write_in = mkWriteIFC(reg_file);

endmodule

Unfortunately, BSV compiler reports the following error:

Error: "VarMemory.bsv", line 67, column 24: (T0080)
? Type error at the use of the following function:
??? replicateM

? The expected return type of the function:
??? Vector::Vector#(2, VarMemory::ReadMem_IFC)

? The return type according to the use:
??? a__#(Vector::Vector#(c__, b__))

make: *** [Makefile:44: compile] Error 1

What am I doing wrong? The error line is highlighted with bold letters.


Sergey Smolov
 

It helps, thank you!


Sergey Smolov
 

Hello,
I would like to continue this topic a bit. I have another module, called VarReader, that accesses to VarMemory through ReadMem_IFC. The VarMemory module is described as it is shown above and it isn't instantiated inside the reader module.

Here is the VarMemory code:
typedef UInt#(VarMemWidth) VarMemUInt;
typedef Reg#(Bit#(VarMemWidth)) VarMemReg;

typedef struct { VarMemUInt fixed; VarMemUInt value; } VarWord deriving(Eq, Bits, FShow);
typedef struct { Bool enable; VarAddrBit address; } VarMemReadReq deriving (Eq,Bits);
typedef struct { Bool enable; VarAddrBit address; VarWord data; } VarMemWriteReq deriving(Eq, Bits, FShow);

typedef RegFile#(VarAddrBit, VarWord) VarMem;

interface ReadMem_IFC;
? method VarWord read(VarMemReadReq r_req);
endinterface

interface WriteMem_IFC;
? method Action write(VarMemWriteReq w_req);
endinterface

interface VarMemory_IFC;
? // 2 read ports (for the reader and the assigner).
? interface Vector#(2, ReadMem_IFC) read_in;
? // 1 write port.
? interface WriteMem_IFC write_in;
endinterface

module mkReadIFC#(VarMem file)(ReadMem_IFC);
? method VarWord read(VarMemReadReq r_req);
??? VarWord resp = r_req.enable ? file.sub(r_req.address) : ?;
??? return resp;
? endmethod
endmodule

module mkWriteIFC#(VarMem file)(WriteMem_IFC);
? method Action write(VarMemWriteReq w_req);
??? if (w_req.enable)
????? file.upd(w_req.address, w_req.data);
? endmethod
endmodule

(* synthesize *)
module mkVarMemory(VarMemory_IFC);

? let varMemWidth = valueOf(VarMemWidth);
? VarMem reg_file <- mkRegFileFullLoad("var_mem.txt");

? let reads <- replicateM(mkReadIFC(reg_file));
? let write <- mkWriteIFC(reg_file);

? interface read_in? = reads;
? interface write_in = write;

endmodule
I have an "interface ReadMem_IFC read_out;" interface in VarReader. How should I connect these modules at the top level and how could this interface be implemented at VarReader?


 

It sounds like you read_out is supposed to be the dual to the read_in interface. There's a minor complication here because the read interface is purely combinational and as such there's not a great way to represent this caller interface.?
As such it's isolating the caller into a module which provides the address and reads in the input as a single function is a bit hard to represent as we cannot provide as strong guarantees of atomicity when referencing module is externally?as we may have
to multiplex that interface with another potential user.

There are a few ways to approach this:

1. If you really just expect the VarMemory to be used by one block in isolation, you can just keep it as a submodule to the reader. If you do need to expose another interface?externally you can pipe it through as a subinterface of VarReader.

2. You can pass the VarMemory interface as a parameter to the VarReader module (e.g. module mkVarReader#(VarMemory memory)(VarReader_IFC)). This will give you the access as the submodule while leaving the memory logically external. The down side of this is that because the scheduling of rules/methods of VarReader that is? needed for it's translation to RTL depends on the usage of VarMemory, we need to inline the mkVarReader module into it's parent and cannot compile it separately. This isn't a big deal but can increase compile time and make debugging / perf tuning harder.?

3. If you want the VarMemory to really be multiplexed across multiple users it may make sense to split the read operation into two parts and add some sort of internal state to VarMemory. In that case you'll likely end up with something that's equivalent to a Server interface for read_in and sometihng that's functionally a Client interface for read_out and you can connect them via 2 simple rules or just use mkConnectable.

4, If you really want to keep it modular and as a RegFile you can use RWires to make a wire-level shim ReadIn and ReadOut and then write shim modules which translate between the wire-level interface and the normal one. You can then pipe out the wire-level interface to each of these modules and stich them together at the mutual top-level. This is generally only needed for dealing with actual wire interfaces like PCI,

I think 1 or 2 likely should work for you you.

HTH,?

-Nirav










1. If you are okay with the?


On Mon, Apr 26, 2021 at 5:08 AM Sergey Smolov <ssedai@...> wrote:
Hello,
I would like to continue this topic a bit. I have another module, called VarReader, that accesses to VarMemory through ReadMem_IFC. The VarMemory module is described as it is shown above and it isn't instantiated inside the reader module.

Here is the VarMemory code:
typedef UInt#(VarMemWidth) VarMemUInt;
typedef Reg#(Bit#(VarMemWidth)) VarMemReg;

typedef struct { VarMemUInt fixed; VarMemUInt value; } VarWord deriving(Eq, Bits, FShow);
typedef struct { Bool enable; VarAddrBit address; } VarMemReadReq deriving (Eq,Bits);
typedef struct { Bool enable; VarAddrBit address; VarWord data; } VarMemWriteReq deriving(Eq, Bits, FShow);

typedef RegFile#(VarAddrBit, VarWord) VarMem;

interface ReadMem_IFC;
? method VarWord read(VarMemReadReq r_req);
endinterface

interface WriteMem_IFC;
? method Action write(VarMemWriteReq w_req);
endinterface

interface VarMemory_IFC;
? // 2 read ports (for the reader and the assigner).
? interface Vector#(2, ReadMem_IFC) read_in;
? // 1 write port.
? interface WriteMem_IFC write_in;
endinterface

module mkReadIFC#(VarMem file)(ReadMem_IFC);
? method VarWord read(VarMemReadReq r_req);
??? VarWord resp = r_req.enable ? file.sub(r_req.address) : ?;
??? return resp;
? endmethod
endmodule

module mkWriteIFC#(VarMem file)(WriteMem_IFC);
? method Action write(VarMemWriteReq w_req);
??? if (w_req.enable)
????? file.upd(w_req.address, w_req.data);
? endmethod
endmodule

(* synthesize *)
module mkVarMemory(VarMemory_IFC);

? let varMemWidth = valueOf(VarMemWidth);
? VarMem reg_file <- mkRegFileFullLoad("var_mem.txt");

? let reads <- replicateM(mkReadIFC(reg_file));
? let write <- mkWriteIFC(reg_file);

? interface read_in? = reads;
? interface write_in = write;

endmodule
I have an "interface ReadMem_IFC read_out;" interface in VarReader. How should I connect these modules at the top level and how could this interface be implemented at VarReader?


Sergey Smolov
 

My case is 3, I think, so I'd like to use BSV Client\Server primitives. But here is the problem I've started from in this thread. I have two different modules (and VarReader is one of them), that send "read requests" to VarMemory and receive "read responses". Also I have another module (called "Foo", for example), that sends "write requests" to VarMemory (and I've supposed to use WriteMem_IFC interface for this module) and there are no responses in such interaction. I understand how to implement "read interactions" here. Is it possible to use Client/Server for one-directional communication (sending requests only)? How could I implement WriteMem_IFC in "Foo"?


 

You could use just half of the client/server there, but you should use Get/Put interfaces for the unidirectional cases. A client/server interface is just a Get in one direction and a Put in the other.?

-Nirav


On Mon, Apr 26, 2021 at 9:34 AM Sergey Smolov <ssedai@...> wrote:
My case is 3, I think, so I'd like to use BSV Client\Server primitives. But here is the problem I've started from in this thread. I have two different modules (and VarReader is one of them), that send "read requests" to VarMemory and receive "read responses". Also I have another module (called "Foo", for example), that sends "write requests" to VarMemory (and I've supposed to use WriteMem_IFC interface for this module) and there are no responses in such interaction. I understand how to implement "read interactions" here. Is it possible to use Client/Server for one-directional communication (sending requests only)? How could I implement WriteMem_IFC in "Foo"?


Sergey Smolov
 

Ah, yes, thank you.
I've tried to use FIFOF modules bacuase of "notEmpty" methods that are suitable for guards of rules that process requests. Here is the code:
typedef UInt#(VarMemWidth) VarMemUInt;
typedef Reg#(Bit#(VarMemWidth)) VarMemReg;

typedef struct { VarMemUInt fixed; VarMemUInt value; } VarWord deriving(Eq, Bits, FShow);
typedef struct { Bool enable; VarAddrBit address; } VarMemReadReq deriving (Eq,Bits);
typedef struct { Bool enable; VarAddrBit address; VarWord data; } VarMemWriteReq deriving(Eq, Bits, FShow);

typedef RegFile#(VarAddrBit, VarWord) VarMem;

interface VarMemory_IFC;
? // 2 read ports (for the reader and the assigner).
? interface Vector#(2, Server#(VarMemReadReq, VarWord)) read_in;
? // 1 write port.
? interface Put#(VarMemWriteReq) write_in;
endinterface

(* synthesize *)
module mkVarMemory(VarMemory_IFC);

? Vector#(2, FIFOF#(VarMemReadReq)) readReqQs <- replicateM(mkFIFOF);
? Vector#(2, FIFOF#(VarWord)) readRespQs <- replicateM(mkFIFOF);
? FIFOF#(VarMemWriteReq) writeReqQ <- mkFIFOF;

? VarMem reg_file <- mkRegFileFullLoad("var_mem.txt");

? for (Integer i = 0; i < 2; i = i + 1) begin
??? let req_q = readReqQs[i];
??? let resp_q = readRespQs[i];

??? rule read_request (req_q.notEmpty());
????? let r_req = req_q.first;
????? VarWord response = r_req.enable ? reg_file.sub(r_req.address) : ?;
????? req_q.deq();
????? resp_q.enq(response);
??? endrule
? end

? rule write_request (writeReqQ.notEmpty());
??? VarMemWriteReq write_req = writeReqQ.first();
????? if (write_req.enable)
??????? reg_file.upd(write_req.address, write_req.data);
??? writeReqQ.deq();
? endrule

? Vector#(2, Server#(VarMemReadReq, VarWord)) bank_vector = newVector;

? for (Integer i = 0; i < 2 ; i = i + 1)

??? bank_vector[i] = (
??????? interface Server#(VarMemReadReq, VarWord);
????????? interface Put request = fifoToPut(readReqQs[i]);
????????? interface Get response = fifoToGet(readRespQs[i]);
??????? endinterface
??? );

? interface read_in = bank_vector;
? interface Put write_in = toPut(writeReqQ);

endmodule

The compiler says that it is impossible to use FIFOF with "fifoToPut/fifoToGet" functions.
Type error at:
??? readReqQs[i]

? Expected type:
??? FIFO::FIFO#(VarMemory::VarMemReadReq)

? Inferred type:
??? FIFOF::FIFOF#(VarMemory::VarMemReadReq)
How can I avoid this problem?


 

There should be a function "fifofToFifo" which trim off the irrelevant interface stuff, but you can always just call the method fifof.enq yourself.?

interface Put;
? ?method Action put(Type x) = fifo.enq(x);
endinterface?


On Mon, Apr 26, 2021 at 10:52 AM Sergey Smolov <ssedai@...> wrote:
Ah, yes, thank you.
I've tried to use FIFOF modules bacuase of "notEmpty" methods that are suitable for guards of rules that process requests. Here is the code:
typedef UInt#(VarMemWidth) VarMemUInt;
typedef Reg#(Bit#(VarMemWidth)) VarMemReg;

typedef struct { VarMemUInt fixed; VarMemUInt value; } VarWord deriving(Eq, Bits, FShow);
typedef struct { Bool enable; VarAddrBit address; } VarMemReadReq deriving (Eq,Bits);
typedef struct { Bool enable; VarAddrBit address; VarWord data; } VarMemWriteReq deriving(Eq, Bits, FShow);

typedef RegFile#(VarAddrBit, VarWord) VarMem;

interface VarMemory_IFC;
? // 2 read ports (for the reader and the assigner).
? interface Vector#(2, Server#(VarMemReadReq, VarWord)) read_in;
? // 1 write port.
? interface Put#(VarMemWriteReq) write_in;
endinterface

(* synthesize *)
module mkVarMemory(VarMemory_IFC);

? Vector#(2, FIFOF#(VarMemReadReq)) readReqQs <- replicateM(mkFIFOF);
? Vector#(2, FIFOF#(VarWord)) readRespQs <- replicateM(mkFIFOF);
? FIFOF#(VarMemWriteReq) writeReqQ <- mkFIFOF;

? VarMem reg_file <- mkRegFileFullLoad("var_mem.txt");

? for (Integer i = 0; i < 2; i = i + 1) begin
??? let req_q = readReqQs[i];
??? let resp_q = readRespQs[i];

??? rule read_request (req_q.notEmpty());
????? let r_req = req_q.first;
????? VarWord response = r_req.enable ? reg_file.sub(r_req.address) : ?;
????? req_q.deq();
????? resp_q.enq(response);
??? endrule
? end

? rule write_request (writeReqQ.notEmpty());
??? VarMemWriteReq write_req = writeReqQ.first();
????? if (write_req.enable)
??????? reg_file.upd(write_req.address, write_req.data);
??? writeReqQ.deq();
? endrule

? Vector#(2, Server#(VarMemReadReq, VarWord)) bank_vector = newVector;

? for (Integer i = 0; i < 2 ; i = i + 1)

??? bank_vector[i] = (
??????? interface Server#(VarMemReadReq, VarWord);
????????? interface Put request = fifoToPut(readReqQs[i]);
????????? interface Get response = fifoToGet(readRespQs[i]);
??????? endinterface
??? );

? interface read_in = bank_vector;
? interface Put write_in = toPut(writeReqQ);

endmodule

The compiler says that it is impossible to use FIFOF with "fifoToPut/fifoToGet" functions.
Type error at:
??? readReqQs[i]

? Expected type:
??? FIFO::FIFO#(VarMemory::VarMemReadReq)

? Inferred type:
??? FIFOF::FIFOF#(VarMemory::VarMemReadReq)
How can I avoid this problem?


 

The compiler says that it is impossible to use FIFOF with "fifoToPut/fifoToGet" functions.

Use the functions "toPut" and "toGet" instead.? These are overloaded functions that are defined for FIFO and FIFOF (among other types).

J