736a7c3f77e4e2ee8a39690124e3a74ff877ef99
[pinmux.git] / docs / AddingPeripherals.mdwn
1 # How to add a new peripheral
2
3 This document describes the process of adding a new peripheral to
4 the pinmux and auto-generator, through a worked example, adding
5 SDRAM.
6
7 # Creating the specifications
8
9 The tool is split into two halves that are separated by tab-separated
10 files. The first step is therefore to add a function that defines
11 the peripheral as a python function. That implies in turn that the
12 pinouts of the peripheral must be known. Looking at the BSV code
13 for the SDRAM peripheral, we find its interface is defined as follows:
14
15 interface Ifc_sdram_out;
16 (*always_enabled,always_ready*)
17 method Action ipad_sdr_din(Bit#(64) pad_sdr_din);
18 method Bit#(9) sdram_sdio_ctrl();
19 method Bit#(64) osdr_dout();
20 method Bit#(8) osdr_den_n();
21 method Bool osdr_cke();
22 method Bool osdr_cs_n();
23 method Bool osdr_ras_n ();
24 method Bool osdr_cas_n ();
25 method Bool osdr_we_n ();
26 method Bit#(8) osdr_dqm ();
27 method Bit#(2) osdr_ba ();
28 method Bit#(13) osdr_addr ();
29 interface Clock sdram_clk;
30 endinterface
31
32 Also note further down that the code to map, for example, the 8 actual dqm
33 pins into a single 8-bit interface has also been auto-generated. Generally
34 it is a good idea to verify that correspondingly the three data in/out/outen
35 interfaces have also been correctly generated.
36
37 interface sdr = interface PeripheralSideSDR
38
39 ...
40 ...
41
42 interface dqm = interface Put#(8)
43 method Action put(Bit#(8) in);
44 wrsdr_sdrdqm0 <= in[0];
45 wrsdr_sdrdqm1 <= in[1];
46 wrsdr_sdrdqm2 <= in[2];
47 wrsdr_sdrdqm3 <= in[3];
48 wrsdr_sdrdqm4 <= in[4];
49 wrsdr_sdrdqm5 <= in[5];
50 wrsdr_sdrdqm6 <= in[6];
51 wrsdr_sdrdqm7 <= in[7];
52 endmethod
53 endinterface;
54
55 endinterface;
56
57 So now we go to src/spec/pinfunctions.py and add a corresponding function
58 that returns a list of all of the required pin signals. However, we note
59 that it is a huge number of pins so a decision is made to split it into
60 groups: sdram1, sdram2 and sdram3. Firstly, sdram1, covering the base
61 functionality:
62
63 def sdram1(suffix, bank):
64 buspins = []
65 inout = []
66 for i in range(8):
67 pname = "SDRDQM%d*" % i
68 buspins.append(pname)
69 for i in range(8):
70 pname = "SDRD%d*" % i
71 buspins.append(pname)
72 inout.append(pname)
73 for i in range(12):
74 buspins.append("SDRAD%d+" % i)
75 for i in range(2):
76 buspins.append("SDRBA%d+" % i)
77 buspins += ['SDRCKE+', 'SDRRASn+', 'SDRCASn+', 'SDRWEn+',
78 'SDRCSn0++']
79 return (buspins, inout)
80
81 This function, if used on its own, would define an 8-bit SDRAM bus with
82 12-bit addressing. Checking off the names against the corresponding BSV
83 definition we find that most of them are straightforward. Outputs
84 must have a "+" after the name (in the python representation), inputs
85 must have a "-".
86
87 However we run smack into an interesting brick-wall with the in/out pins.
88 In/out pins which are routed through the same IO pad need a *triplet* of
89 signals: one input wire, one output wire and *one direction control wire*.
90 Here however we find that the SDRAM controller, which is a wrapper around
91 the opencores SDRAM controller, has a *banked* approach to direction-control
92 that will need to be dealt with, later. So we do *not* make the mistake
93 of adding 8 SDRDENx pins: the BSV code will need to be modified to
94 add 64 one-for-one enabling pins. We do not also make the mistake of
95 adding separate unidirectional "in" and separate unidirectional "out" signals
96 under different names, as the pinmux code is a *PAD* centric tool.
97
98 The second function extends the 8-bit data bus to 64-bits, and extends
99 the address lines to 13-bit wide:
100
101 def sdram3(suffix, bank):
102 buspins = []
103 inout = []
104 for i in range(12, 13):
105 buspins.append("SDRAD%d+" % i)
106 for i in range(8, 64):
107 pname = "SDRD%d*" % i
108 buspins.append(pname)
109 inout.append(pname)
110 return (buspins, inout)
111
112 In this way, alternative SDRAM controller implementations can use sdram1
113 on its own; implementors may add "extenders" (named sdram2, sdram4) that
114 cover extra functionality, and, interestingly, in a pinbank scenario,
115 the number of pins on any given GPIO bank may be kept to a sane level.
116
117 The next phase is to add the (now supported) peripheral to the list
118 of pinspecs at the bottom of the file, so that it can actually be used:
119
120 pinspec = (('IIS', i2s),
121 ('MMC', emmc),
122 ('FB', flexbus1),
123 ('FB', flexbus2),
124 ('SDR', sdram1),
125 ('SDR', sdram2),
126 ('SDR', sdram3), <---
127 ('EINT', eint),
128 ('PWM', pwm),
129 ('GPIO', gpio),
130 )
131
132 This gives a declaration that any time the function(s) starting with
133 "sdram" are used to add pins to a pinmux, it will be part of the
134 "SDR" peripheral. Note that flexbus is similarly subdivided into
135 two groups.
136
137 Note however that due to a naming convention issue, interfaces must
138 be declared with names that are lexicographically unique even in
139 subsets of their names. i.e two interfaces, one named "SD" which is
140 shorthand for SDMMC and another named "SDRAM" may *not* be added:
141 the first has to be the full "SDMMC" or renamed to "MMC".
142
143 # Adding the peripheral to a chip's pinmux specification
144
145 Next, we add the peripheral to an actual chip's specification. In this
146 case it is to be added to i\_class, so we open src/spec/i\_class.py. The
147 first thing to do is to add a single-mux (dedicated) bank of 92 pins (!)
148 which covers all of the 64-bit Data lines, 13 addresses and supporting
149 bank-selects and control lines. It is added as Bank "D", the next
150 contiguous bank:
151
152 def pinspec():
153 pinbanks = {
154 'A': (28, 4),
155 'B': (18, 4),
156 'C': (24, 1),
157 'D': (92, 1), <---
158 }
159 fixedpins = {
160 'CTRL_SYS': [
161
162 This declares the width of the pinmux to one (a dedicated peripheral
163 bank). Note in passing that A and B are both 4-entry.
164 Next, an SDRAM interface is conveniently added to the chip's pinmux
165 with two simple lines of code:
166
167 ps.gpio("", ('B', 0), 0, 0, 18)
168 ps.flexbus1("", ('B', 0), 1, spec=flexspec)
169
170 ps.flexbus2("", ('C', 0), 0)
171
172 ps.sdram1("", ('D', 0), 0) <--
173 ps.sdram3("", ('D', 35), 0) <--
174
175 Note that the first argument is blank, indicating that this is the only
176 SDRAM interface to be added. If more than one SDRAM interface is desired
177 they would be numbered from 0 and identified by their suffix. The second
178 argument is a tuple of (Bank Name, Bank Row Number), and the third argument
179 is the pinmux column (which in this case must be zero).
180
181 At the top level the following command is then run:
182
183 $ python src/pinmux_generator.py -o i_class -s i_class
184
185 The output may be found in the ./i\_class subdirectory, and it is worth
186 examining the i\_class.mdwn file. A table named "Bank D" will have been
187 created and it is worth just showing the first few entries here:
188
189 | Pin | Mux0 | Mux1 | Mux2 | Mux3 |
190 | --- | ----------- | ----------- | ----------- | ----------- |
191 | 70 | D SDR_SDRDQM0 |
192 | 71 | D SDR_SDRDQM1 |
193 | 72 | D SDR_SDRDQM2 |
194 | 73 | D SDR_SDRDQM3 |
195 | 74 | D SDR_SDRDQM4 |
196 | 75 | D SDR_SDRDQM5 |
197 | 76 | D SDR_SDRDQM6 |
198 | 77 | D SDR_SDRDQM7 |
199 | 78 | D SDR_SDRD0 |
200 | 79 | D SDR_SDRD1 |
201 | 80 | D SDR_SDRD2 |
202 | 81 | D SDR_SDRD3 |
203 | 82 | D SDR_SDRD4 |
204 | 83 | D SDR_SDRD5 |
205 | 84 | D SDR_SDRD6 |
206 | 85 | D SDR_SDRD7 |
207 | 86 | D SDR_SDRAD0 |
208 | 87 | D SDR_SDRAD1 |
209
210 Returning to the definition of sdram1 and sdram3, this table clearly
211 corresponds to the functions in src/spec/pinfunctions.py which is
212 exactly what we want. It is however extremely important to verify.
213
214 Lastly, the peripheral is a "fast" peripheral, i.e. it must not
215 be added to the "slow" peripherals AXI4-Lite Bus, so must be added
216 to the list of "fast" peripherals, here:
217
218 ps = PinSpec(pinbanks, fixedpins, function_names,
219 ['lcd', 'jtag', 'fb', 'sdr']) <--
220
221 # Bank A, 0-27
222 ps.gpio("", ('A', 0), 0, 0, 28)
223
224 This basically concludes the first stage of adding a peripheral to
225 the pinmux / autogenerator tool. It allows peripherals to be assessed
226 for viability prior to actually committing the engineering resources
227 to their deployment.
228
229 # Adding the code auto-generators.
230
231 With the specification now created and well-defined (and now including
232 the SDRAM interface), the next completely separate phase is to auto-generate
233 the code that will drop an SDRAM instance onto the fabric of the SoC.
234
235 This particular peripheral is classified as a "Fast Bus" peripheral.
236 "Slow" peripherals will need to be the specific topic of an alternative
237 document, however the principles are the same.
238
239 The first requirement is that the pins from the peripheral side be connected
240 through to IO cells. This can be verified by running the pinmux code
241 generator (to activate "default" behaviour), just to see what happens:
242
243 $ python src/pinmux_generator.py -o i_class
244
245 Files are auto-generated in ./i\_class/bsv\_src and it is recommended
246 to examine the pinmux.bsv file in an editor, and search for occurrences
247 of the string "sdrd63". It can clearly be seen that an interface
248 named "PeripheralSideSDR" has been auto-generated:
249
250 // interface declaration between SDR and pinmux
251 (*always_ready,always_enabled*)
252 interface PeripheralSideSDR;
253 interface Put#(Bit#(1)) sdrdqm0;
254 interface Put#(Bit#(1)) sdrdqm1;
255 interface Put#(Bit#(1)) sdrdqm2;
256 interface Put#(Bit#(1)) sdrdqm3;
257 interface Put#(Bit#(1)) sdrdqm4;
258 interface Put#(Bit#(1)) sdrdqm5;
259 interface Put#(Bit#(1)) sdrdqm6;
260 interface Put#(Bit#(1)) sdrdqm7;
261 interface Put#(Bit#(1)) sdrd0_out;
262 interface Put#(Bit#(1)) sdrd0_outen;
263 interface Get#(Bit#(1)) sdrd0_in;
264 ....
265 ....
266 endinterface
267
268 Note that for the data lines, that where in the sdram1 specification function
269 the signals were named "SDRDn+, out, out-enable *and* in interfaces/methods
270 have been created, as these will be *directly* connected to the I/O pads.
271
272 Further down the file we see the *actual* connection to the I/O pad (cell).
273 An example:
274
275 // --------------------
276 // ----- cell 161 -----
277
278 // output muxer for cell idx 161
279 cell161_mux_out=
280 wrsdr_sdrd63_out;
281
282 // outen muxer for cell idx 161
283 cell161_mux_outen=
284 wrsdr_sdrd63_outen; // bi-directional
285
286 // priority-in-muxer for cell idx 161
287
288 rule assign_wrsdr_sdrd63_in_on_cell161;
289 wrsdr_sdrd63_in<=cell161_mux_in;
290 endrule
291
292 Here, given that this is a "dedicated" cell (with no muxing), we have
293 *direct* assignment of all three signals (in, out, outen). 2-way, 3-way
294 and 4-way muxing creates the required priority-muxing for inputs and
295 straight-muxing for outputs, however in this instance, a deliberate
296 pragmatic decision is being taken not to put 92 pins of 133mhz+ signalling
297 through muxing.
298
299 ## Making the peripheral a "MultiBus" peripheral
300
301 The sheer number of signals coming out of PeripheralSideSDR is so unwieldy
302 that something has to be done. We therefore create a "MultiBus" interface
303 such that the pinmux knows which pins are grouped together by name.
304 This is done in src/bsv/interface\_decl.py.
305
306 The MultiBus code is quite sophisticated, in that buses can be identified
307 by pattern, and removed one by one. The *remaining* pins are left behind
308 as individual single-bit pins. Starting from a copy of InterfaceFlexBus
309 as the most similar code, a cut/paste copy is taken and the new class
310 InterfaceSDRAM created:
311
312 class InterfaceSDRAM(InterfaceMultiBus, Interface):
313
314 def __init__(self, ifacename, pinspecs, ganged=None, single=False):
315 Interface.__init__(self, ifacename, pinspecs, ganged, single)
316 InterfaceMultiBus.__init__(self, self.pins)
317 self.add_bus(False, ['dqm', None, None],
318 "Bit#({0})", "sdrdqm")
319 self.add_bus(True, ['d_out', 'd_out_en', 'd_in'],
320 "Bit#({0})", "sdrd")
321 self.add_bus(False, ['ad', None, None],
322 "Bit#({0})", "sdrad")
323 self.add_bus(False, ['ba', None, None],
324 "Bit#({0})", "sdrba")
325
326 def ifacedef2(self, *args):
327 return InterfaceMultiBus.ifacedef2(self, *args)
328
329 Here, annoyingly, the data bus is a mess, requiring identification of
330 the three separate names for in, out and outen. The prefix "sdrd" is however
331 unique and obvious in its purpose: anything beginning with "sdrd" is
332 treated as a multi-bit bus, and a template for declaring a BSV type is
333 given that is automatically passed the numerical quantity of pins detected
334 that start with the word "sdrd".
335
336 Note that it is critical to lexicographically identify pins correctly,
337 so sdrdqm is done **before** sdrd.
338
339 Once the buses have been identified the peripheral can be added into
340 class Interfaces:
341
342 class Interfaces(InterfacesBase, PeripheralInterfaces):
343 """ contains a list of interface definitions
344 """
345
346 def __init__(self, pth=None):
347 InterfacesBase.__init__(self, Interface, pth,
348 {'gpio': InterfaceGPIO,
349 'fb': InterfaceFlexBus,
350 'sdr': InterfaceSDRAM, <--
351
352 Running the tool again results in a much smaller, tidier output
353 that will be a lot less work, later. Note the automatic inclusion of
354 the correct length multi-bit interfaces. d-out/in/out-en is identified
355 as 64-bit, ad is identified as 13-bit, ba as 2 and dqm as 8.
356
357 // interface declaration between SDR and pinmux
358 (*always_ready,always_enabled*)
359 interface PeripheralSideSDR;
360 interface Put#(Bit#(1)) sdrcke;
361 interface Put#(Bit#(1)) sdrrasn;
362 interface Put#(Bit#(1)) sdrcasn;
363 interface Put#(Bit#(1)) sdrwen;
364 interface Put#(Bit#(1)) sdrcsn0;
365
366 interface Put#(Bit#(8)) dqm;
367 interface Put#(Bit#(64)) d_out;
368 interface Put#(Bit#(64)) d_out_en;
369 interface Get#(Bit#(64)) d_in;
370 interface Put#(Bit#(13)) ad;
371 interface Put#(Bit#(2)) ba;
372
373 endinterface
374
375 ## Adding the peripheral
376
377 In examining the slow\_peripherals.bsv file, there should at this stage
378 be no sign of an SDRAM peripheral having been added, at all. This is
379 because it is missing from the peripheral\_gen side of the tool.
380
381 However, as the slow\_peripherals module takes care of the IO cells
382 (because it contains a declared and configured instance of the pinmux
383 package), signals from the pinmux PeripheralSideSDR instance need
384 to be passed *through* the slow peripherals module as an external
385 interface. This will happen automatically once a code-generator class
386 is added.
387
388 So first, we must identify the nearest similar class. FlexBus looks
389 like a good candidate, so we take a copy of src/bsv/peripheral\_gen/flexbus.py
390 called sdram.py. The simplest next step is to global/search/replace
391 "flexbus" with "sdram", and for peripheral instance declaration replace
392 "fb" with "sdr". At this phase, despite knowing that it will
393 auto-generate the wrong code, we add it as a "supported" peripheral
394 at the bottom of src/bsv/peripheral\_gen/base.py, in the "PFactory"
395 (Peripheral Factory) class:
396
397 from gpio import gpio
398 from rgbttl import rgbttl
399 from flexbus import flexbus
400 from sdram import sdram <--
401
402 for k, v in {'uart': uart,
403 'rs232': rs232,
404 'sdr': sdram,
405 'twi': twi,
406 'quart': quart,
407
408 Note that the name "SDR" matches with the prefix used in the pinspec
409 declaration, back in src/spec/pinfunctions.py, except lower-cased. Once this
410 is done, and the auto-generation tool re-run, examining the
411 slow\_peripherals.bsv file again shows the following (correct) and only
412 the following (correct) additions:
413
414 method Bit#(1) quart0_intr;
415 method Bit#(1) quart1_intr;
416 interface GPIO_config#(28) pad_configa;
417 interface PeripheralSideSDR sdr0; <--
418 interface PeripheralSideFB fb0;
419
420 ....
421 ....
422 interface iocell_side=pinmux.iocell_side;
423 interface sdr0 = pinmux.peripheral_side.sdr; <--
424 interface fb0 = pinmux.peripheral_side.fb;
425
426 These automatically-generated declarations are sufficient to "pass through"
427 the SDRAM "Peripheral Side", which as we know from examination of the code
428 is directly connected to the relevant IO pad cells, so that the *actual*
429 peripheral may be declared in the "fast" fabric and connected up to the
430 relevant and required "fast" bus.
431
432 ## Connecting in the fabric
433
434 Now we can begin the process of systematically inserting the correct
435 "voodoo magic" incantations that, as far as this auto-generator tool is
436 concerned, are just bits of ASCII text. In this particular instance, an
437 SDRAM peripheral happened to already be *in* the SoC's BSV source code,
438 such that the process of adding it to the tool is primarily one of
439 *conversion*.
440
441 **Please note that it is NOT recommended to do two tasks at once.
442 It is strongly recommended to add any new peripheral to a pre-existing
443 verified project, manually, by hand, and ONLY then to carry out a
444 conversion process to have this tool understand how to auto-generate
445 the fabric**
446
447 So examining the i\_class socgen.bsv file, we also open up
448 src/bsv/bsv\_lib/soc\_template.bsv in side-by-side windows of maximum
449 80 characters in width each, and *respect the coding convention for
450 this exact purpose*, can easily fit two such windows side-by-side
451 *as well as* a third containing the source code files that turn that
452 same template into its corresponding output.
453
454 We can now begin by searching for strings "SDRAM" and "sdr" in both
455 the template and the auto-generated socgen.bsv file. The first such
456 encounter is the import, in the template:
457
458 `ifdef BOOTROM
459 import BootRom ::*;
460 `endif
461 `ifdef SDRAM <-- xxxx
462 import sdr_top :: *; <-- xxxx
463 `endif <-- xxxx
464 `ifdef BRAM
465
466 This we can **remove**, and drop the corresponding code-fragment into
467 the sdram slowimport function:
468
469 class sdram(PBase):
470
471 def slowimport(self):
472 return "import sdr_top::*;" <--
473
474 def num_axi_regs32(self):
475
476 Now we re-run the auto-generator tool and confirm that, indeed, the
477 ifdef'd code is gone and replaced with an unconditional import:
478
479 import mqspi :: *;
480 import sdr_top::*; <--
481 import Uart_bs :: *;
482 import RS232_modified::*;
483 import mspi :: *;
484
485 Progress! Next, we examine the instance declaration clause. Remember
486 that we cut/paste the flexbus class, so we are expecting to find code
487 that declares the sdr0 instance as a FlexBus peripheral. We are
488 also looking for the hand-created code that is to be *replaced*. Sure enough:
489
490 AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE)
491 sdr0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor; <--
492 AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE)
493 fb0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor;
494 ...
495 ...
496 `ifdef BOOTROM
497 BootRom_IFC bootrom <-mkBootRom;
498 `endif
499 `ifdef SDRAM <--
500 Ifc_sdr_slave sdram<- mksdr_axi4_slave(clk0); <--
501 `endif <--
502
503 So, the mksdr\_axi4\_slave call we *remove* from the template and cut/paste
504 it into the sdram class's mkfast_peripheral function, making sure to
505 substitute the hard-coded instance name "sdram" with a python-formatted
506 template that can insert numerical instance identifiers, should it ever
507 be desired that there be more than one SDRAM peripheral put into a chip:
508
509 class sdram(PBase):
510
511 ...
512 ...
513 def mkfast_peripheral(self):
514 return "Ifc_sdr_slave sdr{0} <- mksdr_axi4_slave(clk0);"
515
516 Re-run the tool and check that the correct-looking code has been created:
517
518 Ifc_sdr_slave sdr0 <- mksdr_axi4_slave(clk0); <--
519 AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE)
520 fb0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor;
521 Ifc_rgbttl_dummy lcd0 <- mkrgbttl_dummy();
522
523 The next thing to do: searching for the string "sdram\_out" shows that the
524 original hand-generated code contains (contained) a declaration of the
525 SDRAM Interface, presumably to which, when compiling to run on an FPGA,
526 the SDRAM interface would be connected at the top level. Through this
527 interface, connections would be done *by hand* to the IO pads, whereas
528 now they are to be connected *automatically* (on the peripheral side)
529 to the IO pads in the pinmux. However, at the time of writing this is
530 not fully understood by the author, so the fastifdecl and extfastifinstance
531 functions are modified to generate the correct output but the code is
532 *commented out*
533
534 def extfastifinstance(self, name, count):
535 return "// TODO" + self._extifinstance(name, count, "_out", "", True,
536 ".if_sdram_out")
537
538 def fastifdecl(self, name, count):
539 return "// (*always_ready*) interface " + \
540 "Ifc_sdram_out sdr{0}_out;".format(count)
541
542 Also the corresponding (old) manual declarations of sdram\_out
543 removed from the template:
544
545 `ifdef SDRAM <-- xxxx
546 (*always_ready*) interface Ifc_sdram_out sdram_out; <-- xxxx
547 `endif <-- xxxx
548 ...
549 ...
550 `ifdef SDRAM <--- xxxx
551 interface sdram_out=sdram.ifc_sdram_out; <--- xxxx
552 `endif <--- xxxx
553
554 Next, again searching for signs of the "hand-written" code, we encounter
555 the fabric connectivity, which wires the SDRAM to the AXI4. We note however
556 that there is not just one AXI slave device but *two*: one for the SDRAM
557 itself and one for *configuring* the SDRAM. We therefore need to be
558 quite careful about assigning these, as will be subsequently explained.
559 First however, the two AXI4 slave interfaces of this peripheral are
560 declared:
561
562 class sdram(PBase):
563
564 ...
565 ...
566 def _mk_connection(self, name=None, count=0):
567 return ["sdr{0}.axi4_slave_sdram",
568 "sdr{0}.axi4_slave_cntrl_reg"]
569
570 Note that, again, in case multiple instances are ever to be added, the
571 python "format" string "{0}" is inserted so that it can be substituted
572 with the numerical identifier suffix. Also note that the order
573 of declaration of these two AXI4 slave is **important**.
574
575 Re-running the auto-generator tool, we note the following output has
576 been created, and match it against the corresponding hand-generated (old)
577 code:
578
579 `ifdef SDRAM
580 mkConnection (fabric.v_to_slaves
581 [fromInteger(valueOf(Sdram_slave_num))],
582 sdram.axi4_slave_sdram); //
583 mkConnection (fabric.v_to_slaves
584 [fromInteger(valueOf(Sdram_cfg_slave_num))],
585 sdram.axi4_slave_cntrl_reg); //
586 `endif
587
588 // fabric connections
589 mkConnection (fabric.v_to_slaves
590 [fromInteger(valueOf(SDR0_fastslave_num))],
591 sdr0.axi4_slave_sdram);
592 mkConnection (fabric.v_to_slaves
593 [fromInteger(valueOf(SDR0_fastslave_num))],
594 sdr0.axi4_slave_cntrl_reg);
595
596 Immediately we can spot an issue: whilst the correctly-named slave(s) have
597 been added, they have been added with the *same* fabric slave index. This
598 is unsatisfactory and needs resolving.
599
600 Here we need to explain a bit more about what is going on. The fabric
601 on an AXI4 Bus is allocated numerical slave numbers, and each slave is
602 also allocated a memory-mapped region that must be resolved in a bi-directional
603 fashion. i.e whenever a particular memory region is accessed, the AXI
604 slave peripheral responsible for dealing with it **must** be correctly
605 identified. So this requires some further crucial information, which is
606 the size of the region that is to be allocated to each slave device. Later
607 this will be extended to being part of the specification, but for now
608 it is auto-allocated based on the size. As a huge hack, it is allocated
609 in 32-bit chunks, as follows:
610
611 class sdram(PBase):
612
613 def num_axi_regs32(self):
614 return [0x400000, # defines an entire memory range (hack...)
615 12] # defines the number of configuration regs
616
617 So after running the autogenerator again, to confirm that this has
618 generated the correct code, we examine several files, starting with
619 fast\+memory\_map.bsv:
620
621 /*====== Fast peripherals Memory Map ======= */
622 `define SDR0_0_Base 'h50000000
623 `define SDR0_0_End 'h5FFFFFFF // 4194304 32-bit regs
624 `define SDR0_1_Base 'h60000000
625 `define SDR0_1_End 'h600002FF // 12 32-bit regs
626
627 This looks slightly awkward (and in need of an external specification
628 section for addresses) but is fine: the range is 1GB for the main
629 map and covers 12 32-bit registers for the SDR Config map.
630 Next we note the slave numbering:
631
632 typedef 0 SDR0_0__fastslave_num;
633 typedef 1 SDR0_1__fastslave_num;
634 typedef 2 FB0_fastslave_num;
635 typedef 3 LCD0_fastslave_num;
636 typedef 3 LastGen_fastslave_num;
637 typedef TAdd#(LastGen_fastslave_num,1) Sdram_slave_num;
638 typedef TAdd#(Sdram_slave_num ,`ifdef SDRAM 1 `else 0 `endif )
639 Sdram_cfg_slave_num;
640
641 Again this looks reasonable and we may subsequently (carefully! noting
642 the use of the TAdd# chain!) remove the #define for Sdram\_cfg\_slave\_num.
643 The next phase is to examine the fn\_addr\_to\_fastslave\_num function,
644 where we note that there were *two* hand-created sections previously,
645 now joined by two *auto-generated* sections:
646
647 function Tuple2 #(Bool, Bit#(TLog#(Num_Fast_Slaves)))
648 fn_addr_to_fastslave_num (Bit#(`PADDR) addr);
649
650 if(addr>=`SDRAMMemBase && addr<=`SDRAMMemEnd)
651 return tuple2(True,fromInteger(valueOf(Sdram_slave_num))); <--
652 else if(addr>=`DebugBase && addr<=`DebugEnd)
653 return tuple2(True,fromInteger(valueOf(Debug_slave_num))); <--
654 `ifdef SDRAM
655 else if(addr>=`SDRAMCfgBase && addr<=`SDRAMCfgEnd )
656 return tuple2(True,fromInteger(valueOf(Sdram_cfg_slave_num)));
657 `endif
658
659 ...
660 ...
661 if(addr>=`SDR0_0_Base && addr<=`SDR0_0_End) <--
662 return tuple2(True,fromInteger(valueOf(SDR0_0__fastslave_num)));
663 else
664 if(addr>=`SDR0_1_Base && addr<=`SDR0_1_End) <--
665 return tuple2(True,fromInteger(valueOf(SDR0_1__fastslave_num)));
666 else
667 if(addr>=`FB0Base && addr<=`FB0End)
668
669 Now, here is where, in a slightly unusual unique set of circumstances, we
670 cannot just remove all instances of this address / typedef from the template
671 code. Looking in the shakti-core repository's src/lib/MemoryMap.bsv file,
672 the SDRAMMemBase macro is utilise in the is\_IO\_Addr function. So as a
673 really bad hack, which will need to be properly resolved, whilst the
674 hand-generated sections from fast\_tuple2\_template.bsv are removed,
675 and the corresponding (now redundant) defines in src/core/core\_parameters.bsv
676 are commented out, some temporary typedefs to deal with the name change are
677 also added:
678
679 `define SDRAMMemBase SDR0_0_Base
680 `define SDRAMMemEnd SDR0_0_End
681
682 This needs to be addressed (pun intended) including being able to specify
683 the name(s) of the configuration parameters, as well as specifying which
684 memory map range they must be added to.
685
686 Now however finally, after carefully comparing the hard-coded fabric
687 connections to what was formerly named sdram, we may remove the mkConnections
688 that drop sdram.axi4\_slave\_sdram and its associated cntrl reg from
689 the soc\_template.bsv file.
690
691 ## Connecting up the pins
692
693 We are still not done! It is however worth pointing out that if this peripheral
694 were not wired into the pinmux, we would in fact be finished. However there
695 is a task that (previously having been left to outside tools) now needs to
696 be specified, which is to connect the sdram's pins, declared in this
697 instance in Ifc\_sdram\_out, and the PeripheralSideSDR instance that
698 was kindly / strategically / thoughtfully / absolutely-necessarily exported
699 from slow\_peripherals for exactly this purpose.
700
701 Recall earlier that we took a cut/paste copy of the flexbus.py code. If
702 we now examine socgen.bsv we find that it contains connections to pins
703 that match the FlexBus specification, not SDRAM. So, returning to the
704 declaration of the Ifc\_sdram\_out interface, we first identify the
705 single-bit output-only pins, and add a mapping table between them:
706
707 class sdram(PBase):
708
709 def pinname_out(self, pname):
710 return {'sdrwen': 'ifc_sdram_out.osdr_we_n',
711 'sdrcsn0': 'ifc_sdram_out.osdr_cs_n',
712 'sdrcke': 'ifc_sdram_out.osdr_cke',
713 'sdrrasn': 'ifc_sdram_out.osdr_ras_n',
714 'sdrcasn': 'ifc_sdram_out.osdr_cas_n',
715 }.get(pname, '')
716
717 Re-running the tool confirms that the relevant mkConnections are generated:
718
719 //sdr {'action': True, 'type': 'out', 'name': 'sdrcke'}
720 mkConnection(slow_peripherals.sdr0.sdrcke,
721 sdr0_sdrcke_sync.get);
722 mkConnection(sdr0_sdrcke_sync.put,
723 sdr0.ifc_sdram_out.osdr_cke);
724 //sdr {'action': True, 'type': 'out', 'name': 'sdrrasn'}
725 mkConnection(slow_peripherals.sdr0.sdrrasn,
726 sdr0_sdrrasn_sync.get);
727 mkConnection(sdr0_sdrrasn_sync.put,
728 sdr0.ifc_sdram_out.osdr_ras_n);
729
730 Next, the multi-value entries are tackled (both in and out). At present
731 the code is messy, as it does not automatically detect the multiple numerical
732 declarations, nor that the entries are sometimes inout (in, out, outen),
733 so it is *presently* done by hand:
734
735 class sdram(PBase):
736
737 def _mk_pincon(self, name, count, typ):
738 ret = [PBase._mk_pincon(self, name, count, typ)]
739 assert typ == 'fast' # TODO slow?
740 for pname, stype, ptype in [
741 ('dqm', 'osdr_dqm', 'out'),
742 ('ba', 'osdr_ba', 'out'),
743 ('ad', 'osdr_addr', 'out'),
744 ('d_out', 'osdr_dout', 'out'),
745 ('d_in', 'ipad_sdr_din', 'in'),
746 ('d_out_en', 'osdr_den_n', 'out'),
747 ]:
748 ret.append(self._mk_vpincon(name, count, typ, ptype, pname,
749 "ifc_sdram_out.{0}".format(stype)))
750
751 This generates *one* mkConnection for each multi-entry pintype, and here we
752 match up with the "InterfaceMultiBus" class from the specification side,
753 where pin entries with numerically matching names were "grouped" into single
754 multi-bit declarations.
755
756 ## Adjusting the BSV Interface to a get/put style
757
758 For various reasons, related to BSV not permitting wires to be connected
759 back-to-back inside the pinmux code, a get/put style of interface had to
760 be done. This requirement has a knock-on effect up the chain into the
761 actual peripheral code. So now the actual interface (Ifc\_sdram\_out)
762 has to be converted. All straight methods (outputs) are converted to Get,
763 and Action methods (inputs) converted to Put. Also, it is just plain
764 sensible not to use Bool but to use Bit#, and for the pack / unpack to
765 be carried out in the interface. After conversion, the code looks like this:
766
767 interface Ifc_sdram_out;
768 (*always_enabled, always_ready*)
769 interface Put#(Bit#(64)) ipad_sdr_din;
770 interface Get#(Bit#(64)) osdr_dout;
771 interface Get#(Bit#(64)) osdr_den_n;
772 interface Get#(Bit#(1)) osdr_cke;
773 interface Get#(Bit#(1)) osdr_cs_n;
774 interface Get#(Bit#(1)) osdr_ras_n;
775 interface Get#(Bit#(1)) osdr_cas_n;
776 interface Get#(Bit#(1)) osdr_we_n;
777 interface Get#(Bit#(8)) osdr_dqm;
778 interface Get#(Bit#(2)) osdr_ba;
779 interface Get#(Bit#(13)) osdr_addr;
780
781 method Bit#(9) sdram_sdio_ctrl;
782 interface Clock sdram_clk;
783 endinterface
784
785 Note that osdr\_den\_n is now **64** bit **not** 8, as discussed above.
786 After conversion, the code looks like this:
787
788 interface Ifc_sdram_out ifc_sdram_out;
789
790 interface ipad_sdr_din = interface Put
791 method Action put(Bit#(64) in)
792 sdr_cntrl.ipad_sdr_din <= in;
793 endmethod
794 endinterface;
795
796 interface osdr_dout = interface Get
797 method ActionValue#(Bit#(64)) get;
798 return sdr_cntrl.osdr_dout();
799 endmethod
800 endinterface;
801
802 interface osdr_den_n = interface Get
803 method ActionValue#(Bit#(64)) get;
804 Bit#(64) temp;
805 for (int i=0; i<8; i=i+1) begin
806 temp[i*8] = sdr_cntrl.osdr_den_n[i];
807 end
808 return temp;
809 endmethod
810 endinterface;
811
812 interface osdr_cke = interface Get
813 method ActionValue#(Bit#(1)) get;
814 return pack(sdr_cntrl.osdr_cke());
815 endmethod
816 endinterface;
817
818 ...
819 ...
820
821 endinterface;
822
823 Note that the data input is quite straightforward, as is data out,
824 and cke: whether 8-bit, 13-bit or 64-bit, the conversion process is
825 mundane, with only Bool having to be converted to Bit#(1) with a call
826 to pack. The data-enable however is a massive hack: whilst 64 enable
827 lines come in, only every 8th bit is actually utilised and passed
828 through. Whether this should be changed is a matter for debate that
829 is outside of the scope of this document.
830