AddingPeripherals.mdwn
[pinmux.git] / docs / AddingPeripherals.mdwn
index 7f2faa59ef875c610def343421015a8d2f8f454f..f2a405c0a58eabd694d323d9159349d289874b41 100644 (file)
@@ -29,6 +29,31 @@ for the SDRAM peripheral, we find its interface is defined as follows:
         interface Clock sdram_clk;
     endinterface
 
+Also note further down that the code to map, for example, the 8 actual dqm
+pins into a single 8-bit interface has also been auto-generated.  Generally
+it is a good idea to verify that correspondingly the three data in/out/outen
+interfaces have also been correctly generated.
+
+    interface sdr = interface PeripheralSideSDR
+
+          ...
+          ...
+
+          interface dqm = interface Put#(8)
+             method Action put(Bit#(8) in);
+               wrsdr_sdrdqm0 <= in[0];
+               wrsdr_sdrdqm1 <= in[1];
+               wrsdr_sdrdqm2 <= in[2];
+               wrsdr_sdrdqm3 <= in[3];
+               wrsdr_sdrdqm4 <= in[4];
+               wrsdr_sdrdqm5 <= in[5];
+               wrsdr_sdrdqm6 <= in[6];
+               wrsdr_sdrdqm7 <= in[7];
+             endmethod
+           endinterface;
+
+    endinterface;
+
 So now we go to src/spec/pinfunctions.py and add a corresponding function
 that returns a list of all of the required pin signals.  However, we note
 that it is a huge number of pins so a decision is made to split it into
@@ -271,6 +296,84 @@ 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
+
+The sheer number of signals coming out of PeripheralSideSDR is so unwieldy
+that something has to be done.  We therefore create a "MultiBus" interface
+such that the pinmux knows which pins are grouped together by name.
+This is done in src/bsv/interface\_decl.py.
+
+The MultiBus code is quite sophisticated, in that buses can be identified
+by pattern, and removed one by one.  The *remaining* pins are left behind
+as individual single-bit pins.  Starting from a copy of InterfaceFlexBus
+as the most similar code, a cut/paste copy is taken and the new class
+InterfaceSDRAM created:
+
+    class InterfaceSDRAM(InterfaceMultiBus, Interface):
+
+        def __init__(self, ifacename, pinspecs, ganged=None, single=False):
+            Interface.__init__(self, ifacename, pinspecs, ganged, single)
+            InterfaceMultiBus.__init__(self, self.pins)
+            self.add_bus(False, ['dqm', None, None],
+                         "Bit#({0})", "sdrdqm")
+            self.add_bus(True, ['d_out', 'd_out_en', 'd_in'],
+                         "Bit#({0})", "sdrd")
+            self.add_bus(False, ['ad', None, None],
+                         "Bit#({0})", "sdrad")
+            self.add_bus(False, ['ba', None, None],
+                         "Bit#({0})", "sdrba")
+
+        def ifacedef2(self, *args):
+            return InterfaceMultiBus.ifacedef2(self, *args)
+
+Here, annoyingly, the data bus is a mess, requiring identification of
+the three separate names for in, out and outen.  The prefix "sdrd" is however
+unique and obvious in its purpose: anything beginning with "sdrd" is
+treated as a multi-bit bus, and a template for declaring a BSV type is
+given that is automatically passed the numerical quantity of pins detected
+that start with the word "sdrd".
+
+Note that it is critical to lexicographically identify pins correctly,
+so sdrdqm is done **before** sdrd.
+
+Once the buses have been identified the peripheral can be added into
+class Interfaces:
+
+    class Interfaces(InterfacesBase, PeripheralInterfaces):
+        """ contains a list of interface definitions
+        """
+
+        def __init__(self, pth=None):
+            InterfacesBase.__init__(self, Interface, pth,
+                                    {'gpio': InterfaceGPIO,
+                                     'fb': InterfaceFlexBus,
+                                     'sdr': InterfaceSDRAM,  <--
+
+Running the tool again results in a much smaller, tidier output
+that will be a lot less work, later.  Note the automatic inclusion of
+the correct length multi-bit interfaces.  d-out/in/out-en is identified
+as 64-bit, ad is identified as 13-bit, ba as 2 and dqm as 8.
+
+      // interface declaration between SDR and pinmux
+      (*always_ready,always_enabled*)
+      interface PeripheralSideSDR;
+          interface Put#(Bit#(1)) sdrcke;
+          interface Put#(Bit#(1)) sdrrasn;
+          interface Put#(Bit#(1)) sdrcasn;
+          interface Put#(Bit#(1)) sdrwen;
+          interface Put#(Bit#(1)) sdrcsn0;
+
+          interface Put#(Bit#(8)) dqm;
+          interface Put#(Bit#(64)) d_out;
+          interface Put#(Bit#(64)) d_out_en;
+          interface Get#(Bit#(64)) d_in;
+          interface Put#(Bit#(13)) ad;
+          interface Put#(Bit#(2)) ba;
+
+      endinterface
+
+## 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
 because it is missing from the peripheral\_gen side of the tool.
@@ -586,3 +689,210 @@ that drop sdram.axi4\_slave\_sdram and its associated cntrl reg from
 the soc\_template.bsv file.
 
 ## 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
+is a task that (previously having been left to outside tools) now needs to
+be specified, which is to connect the sdram's pins, declared in this
+instance in Ifc\_sdram\_out, and the PeripheralSideSDR instance that
+was kindly / strategically / thoughtfully / absolutely-necessarily exported
+from slow\_peripherals for exactly this purpose.
+
+Recall earlier that we took a cut/paste copy of the flexbus.py code.  If
+we now examine socgen.bsv we find that it contains connections to pins
+that match the FlexBus specification, not SDRAM.  So, returning to the
+declaration of the Ifc\_sdram\_out interface, we first identify the
+single-bit output-only pins, and add a mapping table between them:
+
+    class sdram(PBase):
+
+        def pinname_out(self, pname):
+            return {'sdrwen': 'ifc_sdram_out.osdr_we_n',
+                    'sdrcsn0': 'ifc_sdram_out.osdr_cs_n',
+                    'sdrcke': 'ifc_sdram_out.osdr_cke',
+                    'sdrrasn': 'ifc_sdram_out.osdr_ras_n',
+                    'sdrcasn': 'ifc_sdram_out.osdr_cas_n',
+                    }.get(pname, '')
+
+Re-running the tool confirms that the relevant mkConnections are generated:
+
+    //sdr {'action': True, 'type': 'out', 'name': 'sdrcke'}
+    mkConnection(slow_peripherals.sdr0.sdrcke,
+                sdr0_sdrcke_sync.get);
+    mkConnection(sdr0_sdrcke_sync.put,
+                sdr0.ifc_sdram_out.osdr_cke);
+    //sdr {'action': True, 'type': 'out', 'name': 'sdrrasn'}
+    mkConnection(slow_peripherals.sdr0.sdrrasn,
+                sdr0_sdrrasn_sync.get);
+    mkConnection(sdr0_sdrrasn_sync.put,
+                sdr0.ifc_sdram_out.osdr_ras_n);
+
+Next, the multi-value entries are tackled (both in and out).  At present
+the code is messy, as it does not automatically detect the multiple numerical
+declarations, nor that the entries are sometimes inout (in, out, outen),
+so it is *presently* done by hand:
+
+    class sdram(PBase):
+
+        def _mk_pincon(self, name, count, typ):
+            ret = [PBase._mk_pincon(self, name, count, typ)]
+            assert typ == 'fast' # TODO slow?
+            for pname, stype, ptype in [
+                ('dqm', 'osdr_dqm', 'out'),
+                ('ba', 'osdr_ba', 'out'),
+                ('ad', 'osdr_addr', 'out'),
+                ('d_out', 'osdr_dout', 'out'),
+                ('d_in', 'ipad_sdr_din', 'in'),
+                ('d_out_en', 'osdr_den_n', 'out'),
+            ]:
+                ret.append(self._mk_vpincon(name, count, typ, ptype, pname,
+                                            "ifc_sdram_out.{0}".format(stype)))
+
+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.
+
+# Conclusion
+
+This is not a small project, by any means.