X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=docs%2FAddingPeripherals.mdwn;h=88d93198e2120699e48ae4a4742cd1f5779e45e9;hb=92cd44bcd9a90ea2f1eb0f49c9a498fde092f331;hp=6e82d985646b777435cefc4d0e089bb81efbbf1d;hpb=57fd225e5963fc9beba132e3a200c92f2a67913a;p=pinmux.git diff --git a/docs/AddingPeripherals.mdwn b/docs/AddingPeripherals.mdwn index 6e82d98..88d9319 100644 --- a/docs/AddingPeripherals.mdwn +++ b/docs/AddingPeripherals.mdwn @@ -75,7 +75,7 @@ the address lines to 13-bit wide: def sdram3(suffix, bank): buspins = [] - inout = [] + inout = [] for i in range(12, 13): buspins.append("SDRAD%d+" % i) for i in range(8, 64): @@ -109,6 +109,12 @@ This gives a declaration that any time the function(s) starting with "SDR" peripheral. Note that flexbus is similarly subdivided into two groups. +Note however that due to a naming convention issue, interfaces must +be declared with names that are lexicographically unique even in +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 Next, we add the peripheral to an actual chip's specification. In this @@ -180,7 +186,544 @@ Returning to the definition of sdram1 and sdram3, this table clearly corresponds to the functions in src/spec/pinfunctions.py which is exactly what we want. It is however extremely important to verify. +Lastly, the peripheral is a "fast" peripheral, i.e. it must not +be added to the "slow" peripherals AXI4-Lite Bus, so must be added +to the list of "fast" peripherals, here: + + ps = PinSpec(pinbanks, fixedpins, function_names, + ['lcd', 'jtag', 'fb', 'sdr']) <-- + + # Bank A, 0-27 + ps.gpio("", ('A', 0), 0, 0, 28) + This basically concludes the first stage of adding a peripheral to 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. + +With the specification now created and well-defined (and now including +the SDRAM interface), the next completely separate phase is to auto-generate +the code that will drop an SDRAM instance onto the fabric of the SoC. + +This particular peripheral is classified as a "Fast Bus" peripheral. +"Slow" peripherals will need to be the specific topic of an alternative +document, however the principles are the same. + +The first requirement is that the pins from the peripheral side be connected +through to IO cells. This can be verified by running the pinmux code +generator (to activate "default" behaviour), just to see what happens: + + $ python src/pinmux_generator.py -o i_class + +Files are auto-generated in ./i\_class/bsv\_src and it is recommended +to examine the pinmux.bsv file in an editor, and search for occurrences +of the string "sdrd63". It can clearly be seen that an interface +named "PeripheralSideSDR" has been auto-generated: + + // interface declaration between SDR and pinmux + (*always_ready,always_enabled*) + interface PeripheralSideSDR; + interface Put#(Bit#(1)) sdrdqm0; + interface Put#(Bit#(1)) sdrdqm1; + interface Put#(Bit#(1)) sdrdqm2; + interface Put#(Bit#(1)) sdrdqm3; + interface Put#(Bit#(1)) sdrdqm4; + interface Put#(Bit#(1)) sdrdqm5; + interface Put#(Bit#(1)) sdrdqm6; + interface Put#(Bit#(1)) sdrdqm7; + interface Put#(Bit#(1)) sdrd0_out; + interface Put#(Bit#(1)) sdrd0_outen; + interface Get#(Bit#(1)) sdrd0_in; + .... + .... + endinterface + +Note that for the data lines, that where in the sdram1 specification function +the signals were named "SDRDn+, out, out-enable *and* in interfaces/methods +have been created, as these will be *directly* connected to the I/O pads. + +Further down the file we see the *actual* connection to the I/O pad (cell). +An example: + + // -------------------- + // ----- cell 161 ----- + + // output muxer for cell idx 161 + cell161_mux_out= + wrsdr_sdrd63_out; + + // outen muxer for cell idx 161 + cell161_mux_outen= + wrsdr_sdrd63_outen; // bi-directional + + // priority-in-muxer for cell idx 161 + + rule assign_wrsdr_sdrd63_in_on_cell161; + wrsdr_sdrd63_in<=cell161_mux_in; + endrule + +Here, given that this is a "dedicated" cell (with no muxing), we have +*direct* assignment of all three signals (in, out, outen). 2-way, 3-way +and 4-way muxing creates the required priority-muxing for inputs and +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. + +However, as the slow\_peripherals module takes care of the IO cells +(because it contains a declared and configured instance of the pinmux +package), signals from the pinmux PeripheralSideSDR instance need +to be passed *through* the slow peripherals module as an external +interface. This will happen automatically once a code-generator class +is added. + +So first, we must identify the nearest similar class. FlexBus looks +like a good candidate, so we take a copy of src/bsv/peripheral\_gen/flexbus.py +called sdram.py. The simplest next step is to global/search/replace +"flexbus" with "sdram", and for peripheral instance declaration replace +"fb" with "sdr". At this phase, despite knowing that it will +auto-generate the wrong code, we add it as a "supported" 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 sdram import sdram <-- + + for k, v in {'uart': uart, + 'rs232': rs232, + 'sdr': sdram, + 'twi': twi, + 'quart': quart, + +Note that the name "SDR" matches with the prefix used in the pinspec +declaration, back in src/spec/pinfunctions.py, except lower-cased. Once this +is done, and the auto-generation tool re-run, examining the +slow\_peripherals.bsv file again shows the following (correct) and only +the following (correct) additions: + + method Bit#(1) quart0_intr; + method Bit#(1) quart1_intr; + interface GPIO_config#(28) pad_configa; + interface PeripheralSideSDR sdr0; <-- + interface PeripheralSideFB fb0; + + .... + .... + interface iocell_side=pinmux.iocell_side; + interface sdr0 = pinmux.peripheral_side.sdr; <-- + interface fb0 = pinmux.peripheral_side.fb; + +These automatically-generated declarations are sufficient to "pass through" +the SDRAM "Peripheral Side", which as we know from examination of the code +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 + +Now we can begin the process of systematically inserting the correct +"voodoo magic" incantations that, as far as this auto-generator tool is +concerned, are just bits of ASCII text. In this particular instance, an +SDRAM peripheral happened to already be *in* the SoC's BSV source code, +such that the process of adding it to the tool is primarily one of +*conversion*. + +**Please note that it is NOT recommended to do two tasks at once. +It is strongly recommended to add any new peripheral to a pre-existing +verified project, manually, by hand, and ONLY then to carry out a +conversion process to have this tool understand how to auto-generate +the fabric** + +So examining the i\_class socgen.bsv file, we also open up +src/bsv/bsv\_lib/soc\_template.bsv in side-by-side windows of maximum +80 characters in width each, and *respect the coding convention for +this exact purpose*, can easily fit two such windows side-by-side +*as well as* a third containing the source code files that turn that +same template into its corresponding output. + +We can now begin by searching for strings "SDRAM" and "sdr" in both +the template and the auto-generated socgen.bsv file. The first such +encounter is the import, in the template: + + `ifdef BOOTROM + import BootRom ::*; + `endif + `ifdef SDRAM <-- xxxx + import sdr_top :: *; <-- xxxx + `endif <-- xxxx + `ifdef BRAM + +This we can **remove**, and drop the corresponding code-fragment into +the sdram slowimport function: + + class sdram(PBase): + + def slowimport(self): + return "import sdr_top::*;" <-- + + def num_axi_regs32(self): + +Now we re-run the auto-generator tool and confirm that, indeed, the +ifdef'd code is gone and replaced with an unconditional import: + + import mqspi :: *; + import sdr_top::*; <-- + import Uart_bs :: *; + import RS232_modified::*; + import mspi :: *; + +Progress! Next, we examine the instance declaration clause. Remember +that we cut/paste the flexbus class, so we are expecting to find code +that declares the sdr0 instance as a FlexBus peripheral. We are +also looking for the hand-created code that is to be *replaced*. Sure enough: + + AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE) + sdr0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor; <-- + AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE) + fb0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor; + ... + ... + `ifdef BOOTROM + BootRom_IFC bootrom <-mkBootRom; + `endif + `ifdef SDRAM <-- + Ifc_sdr_slave sdram<- mksdr_axi4_slave(clk0); <-- + `endif <-- + +So, the mksdr\_axi4\_slave call we *remove* from the template and cut/paste +it into the sdram class's mkfast_peripheral function, making sure to +substitute the hard-coded instance name "sdram" with a python-formatted +template that can insert numerical instance identifiers, should it ever +be desired that there be more than one SDRAM peripheral put into a chip: + +class sdram(PBase): + + ... + ... + def mkfast_peripheral(self): + return "Ifc_sdr_slave sdr{0} <- mksdr_axi4_slave(clk0);" + +Re-run the tool and check that the correct-looking code has been created: + + Ifc_sdr_slave sdr0 <- mksdr_axi4_slave(clk0); <-- + AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE) + fb0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor; + Ifc_rgbttl_dummy lcd0 <- mkrgbttl_dummy(); + +The next thing to do: searching for the string "sdram\_out" shows that the +original hand-generated code contains (contained) a declaration of the +SDRAM Interface, presumably to which, when compiling to run on an FPGA, +the SDRAM interface would be connected at the top level. Through this +interface, connections would be done *by hand* to the IO pads, whereas +now they are to be connected *automatically* (on the peripheral side) +to the IO pads in the pinmux. However, at the time of writing this is +not fully understood by the author, so the fastifdecl and extfastifinstance +functions are modified to generate the correct output but the code is +*commented out* + + def extfastifinstance(self, name, count): + return "// TODO" + self._extifinstance(name, count, "_out", "", True, + ".if_sdram_out") + + def fastifdecl(self, name, count): + return "// (*always_ready*) interface " + \ + "Ifc_sdram_out sdr{0}_out;".format(count) + +Also the corresponding (old) manual declarations of sdram\_out +removed from the template: + + `ifdef SDRAM <-- xxxx + (*always_ready*) interface Ifc_sdram_out sdram_out; <-- xxxx + `endif <-- xxxx + ... + ... + `ifdef SDRAM <--- xxxx + interface sdram_out=sdram.ifc_sdram_out; <--- xxxx + `endif <--- xxxx + +Next, again searching for signs of the "hand-written" code, we encounter +the fabric connectivity, which wires the SDRAM to the AXI4. We note however +that there is not just one AXI slave device but *two*: one for the SDRAM +itself and one for *configuring* the SDRAM. We therefore need to be +quite careful about assigning these, as will be subsequently explained. +First however, the two AXI4 slave interfaces of this peripheral are +declared: + + class sdram(PBase): + + ... + ... + def _mk_connection(self, name=None, count=0): + return ["sdr{0}.axi4_slave_sdram", + "sdr{0}.axi4_slave_cntrl_reg"] + +Note that, again, in case multiple instances are ever to be added, the +python "format" string "{0}" is inserted so that it can be substituted +with the numerical identifier suffix. Also note that the order +of declaration of these two AXI4 slave is **important**. + +Re-running the auto-generator tool, we note the following output has +been created, and match it against the corresponding hand-generated (old) +code: + + `ifdef SDRAM + mkConnection (fabric.v_to_slaves + [fromInteger(valueOf(Sdram_slave_num))], + sdram.axi4_slave_sdram); // + mkConnection (fabric.v_to_slaves + [fromInteger(valueOf(Sdram_cfg_slave_num))], + sdram.axi4_slave_cntrl_reg); // + `endif + + // fabric connections + mkConnection (fabric.v_to_slaves + [fromInteger(valueOf(SDR0_fastslave_num))], + sdr0.axi4_slave_sdram); + mkConnection (fabric.v_to_slaves + [fromInteger(valueOf(SDR0_fastslave_num))], + sdr0.axi4_slave_cntrl_reg); + +Immediately we can spot an issue: whilst the correctly-named slave(s) have +been added, they have been added with the *same* fabric slave index. This +is unsatisfactory and needs resolving. + +Here we need to explain a bit more about what is going on. The fabric +on an AXI4 Bus is allocated numerical slave numbers, and each slave is +also allocated a memory-mapped region that must be resolved in a bi-directional +fashion. i.e whenever a particular memory region is accessed, the AXI +slave peripheral responsible for dealing with it **must** be correctly +identified. So this requires some further crucial information, which is +the size of the region that is to be allocated to each slave device. Later +this will be extended to being part of the specification, but for now +it is auto-allocated based on the size. As a huge hack, it is allocated +in 32-bit chunks, as follows: + +class sdram(PBase): + + def num_axi_regs32(self): + return [0x400000, # defines an entire memory range (hack...) + 12] # defines the number of configuration regs + +So after running the autogenerator again, to confirm that this has +generated the correct code, we examine several files, starting with +fast\+memory\_map.bsv: + + /*====== Fast peripherals Memory Map ======= */ + `define SDR0_0_Base 'h50000000 + `define SDR0_0_End 'h5FFFFFFF // 4194304 32-bit regs + `define SDR0_1_Base 'h60000000 + `define SDR0_1_End 'h600002FF // 12 32-bit regs + +This looks slightly awkward (and in need of an external specification +section for addresses) but is fine: the range is 1GB for the main +map and covers 12 32-bit registers for the SDR Config map. +Next we note the slave numbering: + + typedef 0 SDR0_0__fastslave_num; + typedef 1 SDR0_1__fastslave_num; + typedef 2 FB0_fastslave_num; + typedef 3 LCD0_fastslave_num; + typedef 3 LastGen_fastslave_num; + typedef TAdd#(LastGen_fastslave_num,1) Sdram_slave_num; + typedef TAdd#(Sdram_slave_num ,`ifdef SDRAM 1 `else 0 `endif ) + Sdram_cfg_slave_num; + +Again this looks reasonable and we may subsequently (carefully! noting +the use of the TAdd# chain!) remove the #define for Sdram\_cfg\_slave\_num. +The next phase is to examine the fn\_addr\_to\_fastslave\_num function, +where we note that there were *two* hand-created sections previously, +now joined by two *auto-generated* sections: + + function Tuple2 #(Bool, Bit#(TLog#(Num_Fast_Slaves))) + fn_addr_to_fastslave_num (Bit#(`PADDR) addr); + + if(addr>=`SDRAMMemBase && addr<=`SDRAMMemEnd) + return tuple2(True,fromInteger(valueOf(Sdram_slave_num))); <-- + else if(addr>=`DebugBase && addr<=`DebugEnd) + return tuple2(True,fromInteger(valueOf(Debug_slave_num))); <-- + `ifdef SDRAM + else if(addr>=`SDRAMCfgBase && addr<=`SDRAMCfgEnd ) + return tuple2(True,fromInteger(valueOf(Sdram_cfg_slave_num))); + `endif + + ... + ... + if(addr>=`SDR0_0_Base && addr<=`SDR0_0_End) <-- + return tuple2(True,fromInteger(valueOf(SDR0_0__fastslave_num))); + else + if(addr>=`SDR0_1_Base && addr<=`SDR0_1_End) <-- + return tuple2(True,fromInteger(valueOf(SDR0_1__fastslave_num))); + else + if(addr>=`FB0Base && addr<=`FB0End) + +Now, here is where, in a slightly unusual unique set of circumstances, we +cannot just remove all instances of this address / typedef from the template +code. Looking in the shakti-core repository's src/lib/MemoryMap.bsv file, +the SDRAMMemBase macro is utilise in the is\_IO\_Addr function. So as a +really bad hack, which will need to be properly resolved, whilst the +hand-generated sections from fast\_tuple2\_template.bsv are removed, +and the corresponding (now redundant) defines in src/core/core\_parameters.bsv +are commented out, some temporary typedefs to deal with the name change are +also added: + + `define SDRAMMemBase SDR0_0_Base + `define SDRAMMemEnd SDR0_0_End + +This needs to be addressed (pun intended) including being able to specify +the name(s) of the configuration parameters, as well as specifying which +memory map range they must be added to. + +Now however finally, after carefully comparing the hard-coded fabric +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 + +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 [ + ('sdrdqm', 'osdr_dqm', 'out'), + ('sdrba', 'osdr_ba', 'out'), + ('sdrad', 'osdr_addr', 'out'), + ('sdrd_out', 'osdr_dout', 'out'), + ('sdrd_in', 'ipad_sdr_din', 'in'), + ('sdrd_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.