Electronics > FPGA

Dual port ram with different data widths

<< < (3/3)

SiliconWizard:

--- Quote from: ali_asadzadeh on May 04, 2021, 06:14:50 pm ---My code is now simulating ok, the question is how do we really code a true dual port ram with different configs, like a FPGA vendor? I mean how do they implement their IP, do you have any clue?

--- End quote ---

As Bassman59 said, they are very vendor-specific and instantiate low-level primitives to ensure an exact implementation, as opposed to using pure HDL and let the tools infer the right primitives. As I mentioned above, there is NO right and sure way of writing 100% generic HDL code for such memories. That bites, but yeah.

One reason I wanted to do that (and possibly your rationale is the same) is that it's much easier to change any parameter (width, depth, ...), whereas using IPs forces you to re-edit IPs and re-generate them, which can be pretty annoying, but there's no real generic way of handling this.

So yeah. Use vendor IPs, and as NorthGuy said, encapsulate them in your own components with your own interfaces if that helps you write code easier to port to another target.

For very simple stuff, such as basic memory blocks or even simple symmetric dual-port RAM, clean generic HDL code usually works for most vendors, but even then, the tools may infer block RAM or distributed RAM based on their own criterions which you may not understand whatsoever. There usually are attributes you can declare to force some memory to be inferred as block RAM or distributed RAM, but those are usually slightly different from one vendor to the next, so even this can't be quite generic. Also, the tools may decide not to comply with your attributes if it decides it's best to implement memory another way. Now if you don't care how your memory is actually implemented (for instance, when said memory is small enough that you don't really care), generic HDL can be used. Just realize that the result may be surprising.

Oh and lastly, I've already encountered inference bugs, at least with Xilinx tools, for memory implemented as generic HDL. I was implementing a write-through dual-port memory, and the write-through feature (reading on one port @the same address and same cycle as the other port would yield the just written value), that I implemented via some MUX, wouldn't be implemented properly, whereas the HDL code was perfectly correct.

ali_asadzadeh:
Thanks, I did not mean to use vendors IP cores, every body knows that they should use them, I meant that how we can model or code a piece of  Dual port ram with different data widths for example to be implemented in an ASIC or to know how it's internals should work out of curiosity!

Bassman59:

--- Quote from: ali_asadzadeh on May 06, 2021, 05:27:04 am ---Thanks, I did not mean to use vendors IP cores, every body knows that they should use them, I meant that how we can model or code a piece of  Dual port ram with different data widths for example to be implemented in an ASIC or to know how it's internals should work out of curiosity!

--- End quote ---

Look at any vendor's block RAM simulation model that supports different aspect ratios for the memory access. Be warned: some of those models are written in ancient dialects of the languages.

That said, it's not all that difficult. (I use VHDL, so the points here are made for that language. I'm sure SystemVerilog has similar.)

For a single-port memory, or a dual-port with only one write side, you use a signal to model the memory. The trick is that each side of a true dual port memory has its own clock, and each clock domain means separate processes, one for each. And remember how HDL processes (or always blocks) work: the process creates a driver for each signal assigned within. If you have more than one process assigning to a signal, you get more than one driver for that signal. And generally that's bad. You'll get a complier complaint.
 
So the usual way to manage multiple processes assigning to a signal is to use a shared variable. Remember in VHDL, a variable normally exists only within a process. That means it can only be assigned in the process in which it is declared, and it can only be read in that process. A shared variable removes that process-only restriction: it can be accessed anywhere in the architecture of the entity in which it is declared.

Old vendor models use that. There are processes for each domain which can assign to the shared variable memory array and read from it. But there's an obvious danger. What happens when both sides attempt to write to the memory simultaneously? (This is a problem in the real memory, which is where there is a distinction about "read first," "write first," and so on.)

This was recognized by the maintainers of VHDL, so in modern VHDL-2008, the idea of a "protected type" was introduced. And you model the memory as a protected type. The cool thing about protected types is that they are like a C++ class. You use setters and getters (procedures) to access the memory. 

The gotcha is that with VHDL-2008 and later, all shared variables must be a protected type, and if you analyze old code with 2008, the compile will complain.

For a dual-port memory model, you create a protected type that includes the data and the accessor methods, and then you declare a shared variable of that type. Then all accesses to that variable's data are done with the accessor methods, and those methods can be called from anywhere within the architecture.

This is actually a very cool feature. Don't expect much of it to be synthesizable, but for modeling and verification it's great.

ali_asadzadeh:
Thanks for the info :-+

SiliconWizard:

--- Quote from: ali_asadzadeh on May 06, 2021, 05:27:04 am ---Thanks, I did not mean to use vendors IP cores, every body knows that they should use them, I meant that how we can model or code a piece of  Dual port ram with different data widths for example to be implemented in an ASIC or to know how it's internals should work out of curiosity!

--- End quote ---

If you're designing a digital ASIC, it's likely that you will also use some IP for RAM blocks, or at least some blocks from the PDK libraries. So in the end, you're also likely to instantiate black boxes rather than implementing this in pure HDL in a behavioral way, and then merely interconnect them with some glue logic.

This doesn't really answer your question, which in the end is getting to know how this is implemented on a low level, but just wanted to point out that unless you're designing at a very low level, you may not have to care much more than with FPGAs if you're designing ASICs. Really depends on your requirements and specific workflow.

So anyway, considering the case for which the larger width (Wb) is a multiple of the smaller width (Wa): one way to implement this with symmetric dual-port RAM blocks is to use MUXes to further split the larger width in Wb/Wa chunks, as you basically did with your simulation code. One further "exercise" would be to take your code, and replace the behavioral memory with a black box implementing the equivalent memory block. Then you can implement this black box as behavioral memory, or implement it instantiating a low-level primitive for a dual-port RAM. You'll get closer to how it can be done for an ASIC.

Navigation

[0] Message Index

[*] Previous page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod