add emmc dummy peripheral
[pinmux.git] / docs / AddingPeripherals.mdwn
index 38d8c4edb3cdce4f317382c573af585ed4feacab..a93f74cb21ff161162d3fdb921b95fd637ad4940 100644 (file)
@@ -1,10 +1,15 @@
 # How to add a new peripheral
 
 This document describes the process of adding a new peripheral to
-the pinmux and auto-generator, through a worked example, adding
-SDRAM.
+the pinmux and auto-generator, through worked examples, adding
+SDRAM and eMMC.  First to be covered is SDMMC
 
-# Creating the specifications
+# Adding a Fast Peripheral
+
+This section covers how to add a peripheral that is intended to go
+onto the "Fast" peripheral bus.
+
+## Creating the specifications
 
 The tool is split into two halves that are separated by tab-separated
 files.  The first step is therefore to add a function that defines
@@ -140,7 +145,7 @@ subsets of their names.  i.e two interfaces, one named "SD" which is
 shorthand for SDMMC and another named "SDRAM" may *not* be added:
 the first has to be the full "SDMMC" or renamed to "MMC".
 
-# Adding the peripheral to a chip's pinmux specification
+## Adding the peripheral to a chip's pinmux specification
 
 Next, we add the peripheral to an actual chip's specification.  In this
 case it is to be added to i\_class, so we open src/spec/i\_class.py.  The
@@ -226,7 +231,7 @@ the pinmux / autogenerator tool.  It allows peripherals to be assessed
 for viability prior to actually committing the engineering resources
 to their deployment.
 
-# Adding the code auto-generators.
+## Adding the code auto-generators.
 
 With the specification now created and well-defined (and now including
 the SDRAM interface), the next completely separate phase is to auto-generate
@@ -296,7 +301,7 @@ straight-muxing for outputs, however in this instance, a deliberate
 pragmatic decision is being taken not to put 92 pins of 133mhz+ signalling
 through muxing.
 
-## Making the peripheral a "MultiBus" peripheral
+### Making the peripheral a "MultiBus" peripheral
 
 The sheer number of signals coming out of PeripheralSideSDR is so unwieldy
 that something has to be done.  We therefore create a "MultiBus" interface
@@ -372,7 +377,7 @@ as 64-bit, ad is identified as 13-bit, ba as 2 and dqm as 8.
 
       endinterface
 
-## Adding the peripheral
+### Adding the peripheral
 
 In examining the slow\_peripherals.bsv file, there should at this stage
 be no sign of an SDRAM peripheral having been added, at all.  This is
@@ -401,7 +406,7 @@ at the bottom of src/bsv/peripheral\_gen/base.py, in the "PFactory"
 
     for k, v in {'uart': uart,
                  'rs232': rs232,
-                 'sdr': sdram,
+                 'sdr': sdram,    <--
                  'twi': twi,
                  'quart': quart,
 
@@ -429,7 +434,7 @@ is directly connected to the relevant IO pad cells, so that the *actual*
 peripheral may be declared in the "fast" fabric and connected up to the
 relevant and required "fast" bus.
 
-## Connecting in the fabric
+### Connecting in the fabric
 
 Now we can begin the process of systematically inserting the correct
 "voodoo magic" incantations that, as far as this auto-generator tool is
@@ -688,7 +693,7 @@ connections to what was formerly named sdram, we may remove the mkConnections
 that drop sdram.axi4\_slave\_sdram and its associated cntrl reg from
 the soc\_template.bsv file.
 
-## Connecting up the pins
+### Connecting up the pins
 
 We are still not done!  It is however worth pointing out that if this peripheral
 were not wired into the pinmux, we would in fact be finished.  However there
@@ -752,3 +757,408 @@ This generates *one* mkConnection for each multi-entry pintype, and here we
 match up with the "InterfaceMultiBus" class from the specification side,
 where pin entries with numerically matching names were "grouped" into single
 multi-bit declarations.
+
+### Adjusting the BSV Interface to a get/put style
+
+For various reasons, related to BSV not permitting wires to be connected
+back-to-back inside the pinmux code, a get/put style of interface had to
+be done.  This requirement has a knock-on effect up the chain into the
+actual peripheral code.  So now the actual interface (Ifc\_sdram\_out)
+has to be converted.  All straight methods (outputs) are converted to Get,
+and Action methods (inputs) converted to Put.  Also, it is just plain
+sensible not to use Bool but to use Bit#, and for the pack / unpack to
+be carried out in the interface.  After conversion, the code looks like this:
+
+    interface Ifc_sdram_out;
+        (*always_enabled, always_ready*)
+        interface Put#(Bit#(64)) ipad_sdr_din;
+        interface Get#(Bit#(64)) osdr_dout;
+        interface Get#(Bit#(64)) osdr_den_n;
+        interface Get#(Bit#(1))  osdr_cke;
+        interface Get#(Bit#(1))  osdr_cs_n;
+        interface Get#(Bit#(1))  osdr_ras_n;
+        interface Get#(Bit#(1))  osdr_cas_n;
+        interface Get#(Bit#(1))  osdr_we_n;
+        interface Get#(Bit#(8))  osdr_dqm;
+        interface Get#(Bit#(2))  osdr_ba;
+        interface Get#(Bit#(13)) osdr_addr;
+
+        method Bit#(9) sdram_sdio_ctrl;
+        interface Clock sdram_clk;
+    endinterface
+
+Note that osdr\_den\_n is now **64** bit **not** 8, as discussed above.
+After conversion, the code looks like this:
+
+    interface Ifc_sdram_out ifc_sdram_out;
+
+        interface ipad_sdr_din = interface Put
+            method Action put(Bit#(64) in)
+                sdr_cntrl.ipad_sdr_din <= in;
+            endmethod
+        endinterface;
+
+        interface osdr_dout = interface Get
+          method ActionValue#(Bit#(64)) get;
+            return sdr_cntrl.osdr_dout();
+          endmethod
+        endinterface;
+
+        interface osdr_den_n = interface Get
+            method ActionValue#(Bit#(64)) get;
+                Bit#(64) temp;
+                for (int i=0; i<8; i=i+1) begin
+                  temp[i*8] = sdr_cntrl.osdr_den_n[i];
+                end
+                return temp;
+            endmethod
+        endinterface;
+
+        interface osdr_cke = interface Get
+          method ActionValue#(Bit#(1)) get;
+            return pack(sdr_cntrl.osdr_cke());
+          endmethod
+        endinterface;
+
+        ...
+        ...
+
+    endinterface;
+
+Note that the data input is quite straightforward, as is data out,
+and cke: whether 8-bit, 13-bit or 64-bit, the conversion process is
+mundane, with only Bool having to be converted to Bit#(1) with a call
+to pack.  The data-enable however is a massive hack: whilst 64 enable
+lines come in, only every 8th bit is actually utilised and passed
+through.  Whether this should be changed is a matter for debate that
+is outside of the scope of this document.
+
+Lastly for this phase we have two anomalous interfaces, exposing
+the control registers and the clock, that have been moved out of
+Ifc\_sdram\_out, to the level above (Ifc\_sdr\_slave) so that the sole
+set of interfaces exposed for inter-connection by the auto-generator is
+related exclusively to the actual pins.  Resolution of these two issues
+is currently outside of the scope of this document.
+
+### Clock synchronisation
+
+Astute readers, if their heads have not exploded by this point, will
+have noticed earlier that the SDRAM instance was declared with an entirely
+different clock domain from slow peripherals.  Whilst the pinmux is
+completely combinatorial logic that is entirely unclocked, BSV has no
+means of informing a *module* of this fact, and consequently a weird
+null-clock splicing trick is required.
+
+This code is again auto-generated.  However, it is slightly tricky to
+describe and there are several edge-cases, including ones where the
+peripheral is a slow peripheral (UART is an example) that is driven
+from a UART clock but it is connected up inside the slow peripherals
+code; FlexBus is a Fast Bus peripheral that needs syncing up; RGB/TTL
+likewise, but JTAG is specifically declared inside the SoC and passed
+through, but its instantiation requires a separate incoming clock.
+
+Examining the similarity between the creation of an SDRAM instance
+and the JTAG instance, the jtag code therefore looks like it is the
+best candidate fit.  However, it passes through a reset signal as well.
+Instead, we modify this to create clk0 but use the slow\_reset
+signal:
+
+    class sdram(PBase):
+
+        def get_clk_spc(self, typ):
+            return "tck, slow_reset"
+
+        def get_clock_reset(self, name, count):
+            return "slow_clock,  slow_reset"
+
+The resultant synchronisation code, that accepts pairs of clock/reset
+tuples in order to take care of the prerequisite null reset and clock
+"synchronisation", looks like this:
+
+        Ifc_sync#(Bit#(1)) sdr0_sdrcke_sync <-mksyncconnection(
+                    clk0, slow_reset, slow_clock, slow_reset);
+        Ifc_sync#(Bit#(1)) sdr0_sdrrasn_sync <-mksyncconnection(
+                    clk0, slow_reset, slow_clock, slow_reset);
+        ...
+        ...
+        Ifc_sync#(Bit#(64)) sdr0_d_in_sync <-mksyncconnection(
+            slow_clock, slow_reset, clk0, slow_reset);
+
+Note that inputs are in reverse order from outputs.  With the inclusion of
+clock synchronisation, automatic chains of mkConnections are set up between
+the actual peripheral and the peripheral side of the pinmux:
+
+    mkConnection(slow_peripherals.sdr0.sdrcke,
+                sdr0_sdrcke_sync.get);
+    mkConnection(sdr0_sdrcke_sync.put,
+                sdr0.ifc_sdram_out.osdr_cke);
+
+Interestingly, if *actual* clock synchronisation were ever to be needed,
+it could easily be taken care of with relatively little extra work.  However,
+it is worth emphasising that the pinmux is *entirely* unclocked zero-reset
+combinatorial logic.
+
+# Adding a "Slow" Peripheral
+
+This example will be cheating by cut/paste copying an existing very
+similar interface, SD/MMC.  The SD/MMC interface is presently a "dummy"
+that will be extended later.  However given that the peripherals are
+so very very similar, common base classes will be used, in a style that
+has also been used in SPI/QSPI and also UART.
+
+## Adding the pin specifications
+
+Looking at src/spec/pinfunctions.py we find that an emmc function actually
+already exists.  So unlike sdram, there are no modifications to be made.
+We can check that it works by creating an example, say in src/spec/i\_class.py
+by adding an emmc interface to Bank B:
+
+    ps.gpio("", ('B', 0), 0, 0, 18)
+    ps.flexbus1("", ('B', 0), 1, spec=flexspec)
+    ps.emmc("", ('B', 0), 3)                       <---
+
+    ps.flexbus2("", ('C', 0), 0)
+
+We then need to generate the spec.  At the top level the following command is
+then run:
+
+    $ python src/pinmux_generator.py -o i_class -s i_class
+
+Checking the resultant markdown file ./i\_class/i\_class.mdwn, we find that
+several entries have been added at the required location:
+
+| Pin | Mux0        | Mux1        | Mux2        | Mux3        |
+| --- | ----------- | ----------- | ----------- | ----------- |
+|  28 | B GPIOB_B0  | B FB_AD2    |             | B EMMC_CMD  |
+|  29 | B GPIOB_B1  | B FB_AD3    |             | B EMMC_CLK  |
+|  30 | B GPIOB_B2  | B FB_AD4    |             | B EMMC_D0   |
+|  31 | B GPIOB_B3  | B FB_AD5    |             | B EMMC_D1   |
+|  32 | B GPIOB_B4  | B FB_AD6    |             | B EMMC_D2   |
+|  33 | B GPIOB_B5  | B FB_AD7    |             | B EMMC_D3   |
+|  34 | B GPIOB_B6  | B FB_CS0    |             | B EMMC_D4   |
+|  35 | B GPIOB_B7  | B FB_CS1    |             | B EMMC_D5   |
+|  36 | B GPIOB_B8  | B FB_ALE    |             | B EMMC_D6   |
+|  37 | B GPIOB_B9  | B FB_OE     | B FB_TBST   | B EMMC_D7   |
+|  38 | B GPIOB_B10 | B FB_RW     |             |             |
+
+We also check i\_class/interfaces.txt to see if the single requested emmc
+interface is there:
+
+    gpiob   1
+    eint    1
+    mqspi   1
+    emmc    1   <--
+    uart    3
+
+Also we examine the i\_class/emmc.txt file to check that it has the right
+types of pin definitions:
+
+    cmd out
+    clk out
+    d0  inout   bus
+    d1  inout   bus
+    d2  inout   bus
+    d3  inout   bus
+    d4  inout   bus
+    d5  inout   bus
+    d6  inout   bus
+    d7  inout   bus
+
+and we check the i\_class/pinmap.txt tab-separated file to see if it
+contains the entries corresponding to the markdown table:
+
+    24  A   4   gpioa_a24   mspi1_ck    jtag_tms    mmc0_d0
+    25  A   4   gpioa_a25   mspi1_nss   jtag_tdi    mmc0_d1
+    26  A   4   gpioa_a26   mspi1_io0   jtag_tdo    mmc0_d2
+    27  A   4   gpioa_a27   mspi1_io1   jtag_tck    mmc0_d3
+    28  B   4   gpiob_b0    fb_ad2      emmc_cmd
+    29  B   4   gpiob_b1    fb_ad3      emmc_clk
+    30  B   4   gpiob_b2    fb_ad4      emmc_d0
+    31  B   4   gpiob_b3    fb_ad5      emmc_d1
+    32  B   4   gpiob_b4    fb_ad6      emmc_d2
+    33  B   4   gpiob_b5    fb_ad7      emmc_d3
+    34  B   4   gpiob_b6    fb_cs0      emmc_d4
+    35  B   4   gpiob_b7    fb_cs1      emmc_d5
+    36  B   4   gpiob_b8    fb_ale      emmc_d6
+    37  B   4   gpiob_b9    fb_oe   fb_tbst emmc_d7
+
+This concludes this section as the purpose of the spec-generation side,
+to create documentation and TSV files for the second phase, has been
+fulfilled.  Note that we did *not* declare in PinSpec that this
+peripheral is to be added onto the fastbus, as by default peripherals
+are added to a single AXI4-Lite interface.
+
+## Adding the pinmux code auto-generator
+
+The next phase begins with adding class support to auto-generate the pinmux
+code.  Starting with the following command:
+
+    $ python src/pinmux_generator.py -o i_class
+
+The first thing to do is look at i\_class/bsv\_src/pinmux.bsv, and search
+for both PeripheralSideMMC and PeripheralSideEMMC.  PeripheralSideMMC is
+very short and compact:
+
+      // interface declaration between MMC and pinmux
+      (*always_ready,always_enabled*)
+      interface PeripheralSideMMC;
+          interface Put#(Bit#(1)) cmd;
+          interface Put#(Bit#(1)) clk;
+          interface Put#(Bit#(4)) out;
+          interface Put#(Bit#(4)) out_en;
+          interface Get#(Bit#(4)) in;
+      endinterface
+
+whereas PeripheralSideEMMC is a mess:
+
+      interface PeripheralSideEMMC;
+          interface Put#(Bit#(1)) cmd;
+          interface Put#(Bit#(1)) clk;
+          interface Put#(Bit#(1)) d0_out;
+          interface Put#(Bit#(1)) d0_outen;
+          interface Get#(Bit#(1)) d0_in;
+          interface Put#(Bit#(1)) d1_out;
+          interface Put#(Bit#(1)) d1_outen;
+          interface Get#(Bit#(1)) d1_in;
+          interface Put#(Bit#(1)) d2_out;
+          interface Put#(Bit#(1)) d2_outen;
+          interface Get#(Bit#(1)) d2_in;
+          ...
+          ...
+      endinterface
+
+To correct this, we need to create an InterfaceEMMC class in
+src/bsv/interface\_decl.py that generates the right code.  However on
+close inspection, given that the format needed is identical (except for
+the number of data lines), we can probably get away with using *exactly*
+the same class:
+
+    class Interfaces(InterfacesBase, PeripheralInterfaces):
+
+        def __init__(self, pth=None):
+            InterfacesBase.__init__(self, Interface, pth,
+                                    {'gpio': InterfaceGPIO,
+                                     ...
+                                     ...
+                                     'mmc': InterfaceSD,
+                                     'emmc': InterfaceSD,    <--
+                                     'fb': InterfaceFlexBus,
+                                     ...
+
+and after re-running the command the output looks like this:
+
+      interface PeripheralSideEMMC;
+          interface Put#(Bit#(1)) cmd;
+          interface Put#(Bit#(1)) clk;
+          interface Put#(Bit#(8)) out;
+          interface Put#(Bit#(8)) out_en;
+          interface Get#(Bit#(8)) in;
+      endinterface
+
+Success!  The class InterfaceSD appears to be sufficiently generic that
+it could understand that it had been passed 8-pins worth of data with
+exactly the same names, rather than 4.  This is encouraging in the sense
+that re-using the SD/MMC BSV generation code should also be as easy.
+
+## Adding the slow peripheral code-generator
+
+So this time we will try cut/pasting src/bsv/peripheral\_gen/sdmmc.py
+to create a base class, MMCBase.  The only two common functions are
+pinname\_out and \_mk\_pincon.
+
+class MMCBase(PBase):
+
+    def pinname_out(self, pname):
+        if pname in ['cmd', 'clk']:
+            return pname
+        return ''
+
+    def _mk_pincon(self, name, count, typ):
+        ...
+        ...
+
+Then, the sdmmc class is modified to inherit it, this time cutting *out*
+all but those two functions:
+
+    from bsv.peripheral\_gen.mmcbase import MMCBase  <--
+
+    class sdmmc(MMCBase):    <--
+
+And at the same time we create an emmc.py file where all occurrences of
+sdmmc are replaced with emmc:
+
+    class emmc(MMCBase):
+
+        def slowimport(self):
+            return "import emmc_dummy              :: *;"
+
+        ...
+        ...
+
+        def _mk_connection(self, name=None, count=0):
+            return "emmc{0}.slave"
+
+Finally, to use it, just as with sdram, we add the new peripheral
+at the bottom of src/bsv/peripheral\_gen/base.py, in the "PFactory"
+(Peripheral Factory) class:
+
+    from gpio import gpio
+    from rgbttl import rgbttl
+    from flexbus import flexbus
+    from emmc import emmc        <--
+
+    for k, v in {'uart': uart,
+                 'rs232': rs232,
+                 'emmc': emmc,   <--
+
+For the actual auto-generation phase, this really should be all that's
+needed.  Re-running the code-generator we can examine the auto-generated
+slow\_peripherals.bsv file and can confirm that yes, an "import emmc\_dummy"
+has been added, that an mmc0 instance has been created, that it is added
+to the slave fabric, and that its cmd, clk and in/out/out\_en are all
+connected up.
+
+The last remaining task will therefore be to create an interim emmc
+"dummy" BSV file.
+
+## Creating the dummy emmc peripheral
+
+Adding the actual peripheral is done in a different repository,
+shakti-peripherals, which can be cloned with:
+
+    $ git clone gitolite3@libre-riscv.org:shakti-peripherals.git
+
+or public:
+
+    $ git clone git://libre-riscv.org/shakti-peripherals.git
+
+Here, what we will do is take a straight copy of
+src/peripherals/sdmmc/sdcard\_dummy.bsv and call it
+src/peripherals/emmc/emmc\_dummy.bsv.  Then replace all occurrences
+of "sdcard" with "emmc" and also update the SDBUSWIDTH from 4 to 8.
+Whilst this appears wasteful it is by far the simplest and quickest
+way to get working code, that should definitely, definitely be
+re-factored later.
+
+The next stage is to return to the pinmux repository and add the
+import of the new emmc subdirectory to the BSVINCDIR in both
+src/bsv/Makefile.template and Makefile.peripherals.template:
+
+    BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/spi
+    BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/sdmmc
+    BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/emmc
+    BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/flexbus
+
+Really these should also be auto-generated.  Testing through compiling
+can now take place.
+
+
+# Conclusion
+
+This is not a small project, by any means.  However the payoff in saved
+time is enormous.  The conversion of SDRAM from a hand-crafted fixed and
+manually laborious task to an automated one took around a day, with debugging
+and testing to follow.  Once done (particularly, once done with *prior tested
+peripherals*), it becomes possible to add *any arbitrary number* of such
+peripherals with a single line of code, back in the specification.
+