X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=docs%2FAddingPeripherals.mdwn;h=e1b470d2fef99d9d9778a51e16aef3402365935b;hb=9cc8691165170ef5b43b7823c884e99c21a6ab2e;hp=6e82d985646b777435cefc4d0e089bb81efbbf1d;hpb=57fd225e5963fc9beba132e3a200c92f2a67913a;p=pinmux.git diff --git a/docs/AddingPeripherals.mdwn b/docs/AddingPeripherals.mdwn index 6e82d98..e1b470d 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,327 @@ 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. + +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. + +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 + +