-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Peripheral API design: exposing bus interfaces #10
Comments
To get my biases out of the way - I am most concerned about the use case where there is no CPU, and possibly no bus. I believe this case is covered by wrapping Approach A seems more flexible to me, in that it can be configured to act like Approach B. With Approach A, I can hook up AXI-Lite to the control port and AXI4 to the data port for AXI SoCs, and just hook everything up to the same WB4 bus for Wishbone SoCs. I believe the downside of this approach can be mitigated by requiring the control and data ports to have matched pipelining delays, or at the very least documenting the difference if one exists so that the SoC integrator can match them if desired. |
I think you just changed my mind on this! (I was in favor of approach B) Both of the use-cases I highlighted for Approach B are actually doable with separate CSR and memory interfaces, namely:
The CSR bus interface could just be bridged by a parent module to the WB4/AXI4 bus, resulting in a "single standard bus interface".
Similarly, a parent module could wrap the peripheral and reorganize its address space, and expose whatever layout may be needed in order to reuse a particular driver. |
So, in the case of peripherals with CSRs, I'm thinking of a class AsyncSerialPeripheral(csr.Peripheral, Elaboratable):
def __init__(self, *, rx_depth=16, tx_depth=16, **kwargs):
super().__init__()
self._phy = AsyncSerial(**kwargs)
self._rx_fifo = SyncFIFO(width=self._phy.rx.data.width, depth=rx_depth)
self._tx_fifo = SyncFIFO(width=self._phy.tx.data.width, depth=tx_depth)
self._divisor = self.csr(self._phy.divisor.width, "rw")
self._rx_data = self.csr(self._phy.rx.data.width, "r")
self._rx_rdy = self.csr(1, "r")
self._tx_data = self.csr(self._phy.tx.data.width, "w")
self._tx_rdy = self.csr(1, "r")
self._bridge = self.csr_bridge()
self.csr_bus = self._bridge.bus
def elaborate(self, platform):
m = Module()
m.submodules.bridge = self._bridge
# ...
return m For memory interfaces, a separate
That way, a peripheral that requires both a CSR bus and a WB4 bus would inherit from both Would this be acceptable ? |
I think that we have to be careful not to limit the structure of the CSR interface. Having the interface glom all the csr.whatever into a single csr.bus would make a harvard interface difficult. perahaps a bus instance and the add to this bus interface would work better. I think @awygle observing that a minimal interface without a CPU or (wishbone|AXI|whatever) interface is important. We should be able to make a nmigen-soc with nothing but 0 or more CSR interfaces. |
FYI: I'm working on a library I'm calling It is based on
|
This issue was discussed in two IRC meetings three years ago, but I forgot to summarize their conclusions. 20/07/20 : https://freenode.irclog.whitequark.org/nmigen/2020-07-20#1595274233-1595276561; There is consensus for Approach A. While a bus-agnostic API (consisting of memory ports and CSR elements) could automate compatibility with multiple bus protocols, some performance-critical features such as bursts would be hard to abstract over, if not impossible. Feature support would be limited to a common denominator. 27/07/20 : https://freenode.irclog.whitequark.org/nmigen/2020-07-27#1595876880-1595885191; Considering an hypothetic flash controller peripheral. It has a memory and a CSR element with a "program" bit. Setting this bit has the side-effect of programming the flash storage with the contents of the memory. Without further assumptions, this interaction is susceptible to data hazards, regardless of how many bus interfaces the peripheral has. Writes may be reordered such that the "program" bit is set before the last word of data reaches its destination. Memory accesses may be delayed, combined or reordered at every step between the initiator, cache hierarchy, interconnect, and the peripheral:
The detection of memory reorderings from the compiler or the CPU is outside the scope of amaranth-soc. Therefore, adding synchronization primitives to the interconnect or peripheral isn't enough to mitigate them. To be effective, synchronization needs to be implemented end-to-end. In such cases, the BSP generated by amaranth-soc should provide constraints to the compiler and the CPU's memory controller. |
Thanks for summarizing this, JF! All of this makes sense to me. |
Peripherals are a currently missing building block from nmigen-soc.
They would provide wrappers to cores by means of a CSR interface (also interrupts, but handling these could be the subject of a separate issue).
For example, an
AsyncSerialPeripheral
wrapper in nmigen-soc would provide access to anAsyncSerial
core in nmigen-stdio. Baudrate, RX/TX data, strobes etc. would be accessed through CSRs.Integration would be straightforward for peripherals that provide nothing more than CSRs:
csr.Multiplexer
, whose bus interface is exposed by the peripheralcsr.Decoder
csr.Decoder
bus interface is bridged to the SoC interconnectBut what about peripherals that also provide a memory interface ? (e.g. DRAM controllers, flash controllers, etc.)
I see two possible approaches:
Approach A: exposing two separate bus interfaces for CSRs and memories
CSRs would be handled the same way as described above, but the peripheral would also provide a separate bus interface to access its memories (e.g. WB4). I think LiteX follows a similar approach.
This has the consequence of locating the CSRs and memories of a given peripheral in separate regions of the SoC address space.
pros:
csr.Decoder
, and the WB4 interface of a peripheral is directly connected to its logic.cons:
Approach B: exposing a single bus interface for both CSRs and memories
Instead of two separate interfaces, a memory-capable peripheral would expose a single bus interface like WB4 or AXI4. This has the consequence of locating all the resources of a peripheral in the same address space region.
wishbone.Decoder
, whose bus interface would be exposed(e.g.
csr.Multiplexer
->WishoneCSRBridge
->wishbone.Decoder
)pros:
(counterargument: users may prefer just using the bare nmigen-stdio cores instead, if available)
cons:
(although I assume that the general case consists of a single CSR bank)
Any thoughts on this ?
cc @whitequark @awygle @enjoy-digital and others
The text was updated successfully, but these errors were encountered: