testing
[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 worked examples, adding
5 SDRAM and eMMC. First to be covered is SDMMC
6
7 # Adding a Fast Peripheral
8
9 This section covers how to add a peripheral that is intended to go
10 onto the "Fast" peripheral bus.
11
12 ## Creating the specifications
13
14 The tool is split into two halves that are separated by tab-separated
15 files. The first step is therefore to add a function that defines
16 the peripheral as a python function. That implies in turn that the
17 pinouts of the peripheral must be known. Looking at the BSV code
18 for the SDRAM peripheral, we find its interface is defined as follows:
19
20 interface Ifc_sdram_out;
21 (*always_enabled,always_ready*)
22 method Action ipad_sdr_din(Bit#(64) pad_sdr_din);
23 method Bit#(9) sdram_sdio_ctrl();
24 method Bit#(64) osdr_dout();
25 method Bit#(8) osdr_den_n();
26 method Bool osdr_cke();
27 method Bool osdr_cs_n();
28 method Bool osdr_ras_n ();
29 method Bool osdr_cas_n ();
30 method Bool osdr_we_n ();
31 method Bit#(8) osdr_dqm ();
32 method Bit#(2) osdr_ba ();
33 method Bit#(13) osdr_addr ();
34 interface Clock sdram_clk;
35 endinterface
36
37 Also note further down that the code to map, for example, the 8 actual dqm
38 pins into a single 8-bit interface has also been auto-generated. Generally
39 it is a good idea to verify that correspondingly the three data in/out/outen
40 interfaces have also been correctly generated.
41
42 interface sdr = interface PeripheralSideSDR
43
44 ...
45 ...
46
47 interface dqm = interface Put#(8)
48 method Action put(Bit#(8) in);
49 wrsdr_sdrdqm0 <= in[0];
50 wrsdr_sdrdqm1 <= in[1];
51 wrsdr_sdrdqm2 <= in[2];
52 wrsdr_sdrdqm3 <= in[3];
53 wrsdr_sdrdqm4 <= in[4];
54 wrsdr_sdrdqm5 <= in[5];
55 wrsdr_sdrdqm6 <= in[6];
56 wrsdr_sdrdqm7 <= in[7];
57 endmethod
58 endinterface;
59
60 endinterface;
61
62 So now we go to src/spec/pinfunctions.py and add a corresponding function
63 that returns a list of all of the required pin signals. However, we note
64 that it is a huge number of pins so a decision is made to split it into
65 groups: sdram1, sdram2 and sdram3. Firstly, sdram1, covering the base
66 functionality:
67
68 def sdram1(suffix, bank):
69 buspins = []
70 inout = []
71 for i in range(8):
72 pname = "SDRDQM%d*" % i
73 buspins.append(pname)
74 for i in range(8):
75 pname = "SDRD%d*" % i
76 buspins.append(pname)
77 inout.append(pname)
78 for i in range(12):
79 buspins.append("SDRAD%d+" % i)
80 for i in range(2):
81 buspins.append("SDRBA%d+" % i)
82 buspins += ['SDRCKE+', 'SDRRASn+', 'SDRCASn+', 'SDRWEn+',
83 'SDRCSn0++']
84 return (buspins, inout)
85
86 This function, if used on its own, would define an 8-bit SDRAM bus with
87 12-bit addressing. Checking off the names against the corresponding BSV
88 definition we find that most of them are straightforward. Outputs
89 must have a "+" after the name (in the python representation), inputs
90 must have a "-".
91
92 However we run smack into an interesting brick-wall with the in/out pins.
93 In/out pins which are routed through the same IO pad need a *triplet* of
94 signals: one input wire, one output wire and *one direction control wire*.
95 Here however we find that the SDRAM controller, which is a wrapper around
96 the opencores SDRAM controller, has a *banked* approach to direction-control
97 that will need to be dealt with, later. So we do *not* make the mistake
98 of adding 8 SDRDENx pins: the BSV code will need to be modified to
99 add 64 one-for-one enabling pins. We do not also make the mistake of
100 adding separate unidirectional "in" and separate unidirectional "out" signals
101 under different names, as the pinmux code is a *PAD* centric tool.
102
103 The second function extends the 8-bit data bus to 64-bits, and extends
104 the address lines to 13-bit wide:
105
106 def sdram3(suffix, bank):
107 buspins = []
108 inout = []
109 for i in range(12, 13):
110 buspins.append("SDRAD%d+" % i)
111 for i in range(8, 64):
112 pname = "SDRD%d*" % i
113 buspins.append(pname)
114 inout.append(pname)
115 return (buspins, inout)
116
117 In this way, alternative SDRAM controller implementations can use sdram1
118 on its own; implementors may add "extenders" (named sdram2, sdram4) that
119 cover extra functionality, and, interestingly, in a pinbank scenario,
120 the number of pins on any given GPIO bank may be kept to a sane level.
121
122 The next phase is to add the (now supported) peripheral to the list
123 of pinspecs at the bottom of the file, so that it can actually be used:
124
125 pinspec = (('IIS', i2s),
126 ('MMC', emmc),
127 ('FB', flexbus1),
128 ('FB', flexbus2),
129 ('SDR', sdram1),
130 ('SDR', sdram2),
131 ('SDR', sdram3), <---
132 ('EINT', eint),
133 ('PWM', pwm),
134 ('GPIO', gpio),
135 )
136
137 This gives a declaration that any time the function(s) starting with
138 "sdram" are used to add pins to a pinmux, it will be part of the
139 "SDR" peripheral. Note that flexbus is similarly subdivided into
140 two groups.
141
142 Note however that due to a naming convention issue, interfaces must
143 be declared with names that are lexicographically unique even in
144 subsets of their names. i.e two interfaces, one named "SD" which is
145 shorthand for SDMMC and another named "SDRAM" may *not* be added:
146 the first has to be the full "SDMMC" or renamed to "MMC".
147
148 ## Adding the peripheral to a chip's pinmux specification
149
150 Next, we add the peripheral to an actual chip's specification. In this
151 case it is to be added to i\_class, so we open src/spec/i\_class.py. The
152 first thing to do is to add a single-mux (dedicated) bank of 92 pins (!)
153 which covers all of the 64-bit Data lines, 13 addresses and supporting
154 bank-selects and control lines. It is added as Bank "D", the next
155 contiguous bank:
156
157 def pinspec():
158 pinbanks = {
159 'A': (28, 4),
160 'B': (18, 4),
161 'C': (24, 1),
162 'D': (92, 1), <---
163 }
164 fixedpins = {
165 'CTRL_SYS': [
166
167 This declares the width of the pinmux to one (a dedicated peripheral
168 bank). Note in passing that A and B are both 4-entry.
169 Next, an SDRAM interface is conveniently added to the chip's pinmux
170 with two simple lines of code:
171
172 ps.gpio("", ('B', 0), 0, 0, 18)
173 ps.flexbus1("", ('B', 0), 1, spec=flexspec)
174
175 ps.flexbus2("", ('C', 0), 0)
176
177 ps.sdram1("", ('D', 0), 0) <--
178 ps.sdram3("", ('D', 35), 0) <--
179
180 Note that the first argument is blank, indicating that this is the only
181 SDRAM interface to be added. If more than one SDRAM interface is desired
182 they would be numbered from 0 and identified by their suffix. The second
183 argument is a tuple of (Bank Name, Bank Row Number), and the third argument
184 is the pinmux column (which in this case must be zero).
185
186 At the top level the following command is then run:
187
188 $ python src/pinmux_generator.py -o i_class -s i_class
189
190 The output may be found in the ./i\_class subdirectory, and it is worth
191 examining the i\_class.mdwn file. A table named "Bank D" will have been
192 created and it is worth just showing the first few entries here:
193
194 | Pin | Mux0 | Mux1 | Mux2 | Mux3 |
195 | --- | ----------- | ----------- | ----------- | ----------- |
196 | 70 | D SDR_SDRDQM0 |
197 | 71 | D SDR_SDRDQM1 |
198 | 72 | D SDR_SDRDQM2 |
199 | 73 | D SDR_SDRDQM3 |
200 | 74 | D SDR_SDRDQM4 |
201 | 75 | D SDR_SDRDQM5 |
202 | 76 | D SDR_SDRDQM6 |
203 | 77 | D SDR_SDRDQM7 |
204 | 78 | D SDR_SDRD0 |
205 | 79 | D SDR_SDRD1 |
206 | 80 | D SDR_SDRD2 |
207 | 81 | D SDR_SDRD3 |
208 | 82 | D SDR_SDRD4 |
209 | 83 | D SDR_SDRD5 |
210 | 84 | D SDR_SDRD6 |
211 | 85 | D SDR_SDRD7 |
212 | 86 | D SDR_SDRAD0 |
213 | 87 | D SDR_SDRAD1 |
214
215 Returning to the definition of sdram1 and sdram3, this table clearly
216 corresponds to the functions in src/spec/pinfunctions.py which is
217 exactly what we want. It is however extremely important to verify.
218
219 Lastly, the peripheral is a "fast" peripheral, i.e. it must not
220 be added to the "slow" peripherals AXI4-Lite Bus, so must be added
221 to the list of "fast" peripherals, here:
222
223 ps = PinSpec(pinbanks, fixedpins, function_names,
224 ['lcd', 'jtag', 'fb', 'sdr']) <--
225
226 # Bank A, 0-27
227 ps.gpio("", ('A', 0), 0, 0, 28)
228
229 This basically concludes the first stage of adding a peripheral to
230 the pinmux / autogenerator tool. It allows peripherals to be assessed
231 for viability prior to actually committing the engineering resources
232 to their deployment.
233
234 ## Adding the code auto-generators.
235
236 With the specification now created and well-defined (and now including
237 the SDRAM interface), the next completely separate phase is to auto-generate
238 the code that will drop an SDRAM instance onto the fabric of the SoC.
239
240 This particular peripheral is classified as a "Fast Bus" peripheral.
241 "Slow" peripherals will need to be the specific topic of an alternative
242 document, however the principles are the same.
243
244 The first requirement is that the pins from the peripheral side be connected
245 through to IO cells. This can be verified by running the pinmux code
246 generator (to activate "default" behaviour), just to see what happens:
247
248 $ python src/pinmux_generator.py -o i_class
249
250 Files are auto-generated in ./i\_class/bsv\_src and it is recommended
251 to examine the pinmux.bsv file in an editor, and search for occurrences
252 of the string "sdrd63". It can clearly be seen that an interface
253 named "PeripheralSideSDR" has been auto-generated:
254
255 // interface declaration between SDR and pinmux
256 (*always_ready,always_enabled*)
257 interface PeripheralSideSDR;
258 interface Put#(Bit#(1)) sdrdqm0;
259 interface Put#(Bit#(1)) sdrdqm1;
260 interface Put#(Bit#(1)) sdrdqm2;
261 interface Put#(Bit#(1)) sdrdqm3;
262 interface Put#(Bit#(1)) sdrdqm4;
263 interface Put#(Bit#(1)) sdrdqm5;
264 interface Put#(Bit#(1)) sdrdqm6;
265 interface Put#(Bit#(1)) sdrdqm7;
266 interface Put#(Bit#(1)) sdrd0_out;
267 interface Put#(Bit#(1)) sdrd0_outen;
268 interface Get#(Bit#(1)) sdrd0_in;
269 ....
270 ....
271 endinterface
272
273 Note that for the data lines, that where in the sdram1 specification function
274 the signals were named "SDRDn+, out, out-enable *and* in interfaces/methods
275 have been created, as these will be *directly* connected to the I/O pads.
276
277 Further down the file we see the *actual* connection to the I/O pad (cell).
278 An example:
279
280 // --------------------
281 // ----- cell 161 -----
282
283 // output muxer for cell idx 161
284 cell161_mux_out=
285 wrsdr_sdrd63_out;
286
287 // outen muxer for cell idx 161
288 cell161_mux_outen=
289 wrsdr_sdrd63_outen; // bi-directional
290
291 // priority-in-muxer for cell idx 161
292
293 rule assign_wrsdr_sdrd63_in_on_cell161;
294 wrsdr_sdrd63_in<=cell161_mux_in;
295 endrule
296
297 Here, given that this is a "dedicated" cell (with no muxing), we have
298 *direct* assignment of all three signals (in, out, outen). 2-way, 3-way
299 and 4-way muxing creates the required priority-muxing for inputs and
300 straight-muxing for outputs, however in this instance, a deliberate
301 pragmatic decision is being taken not to put 92 pins of 133mhz+ signalling
302 through muxing.
303
304 ### Making the peripheral a "MultiBus" peripheral
305
306 The sheer number of signals coming out of PeripheralSideSDR is so unwieldy
307 that something has to be done. We therefore create a "MultiBus" interface
308 such that the pinmux knows which pins are grouped together by name.
309 This is done in src/bsv/interface\_decl.py.
310
311 The MultiBus code is quite sophisticated, in that buses can be identified
312 by pattern, and removed one by one. The *remaining* pins are left behind
313 as individual single-bit pins. Starting from a copy of InterfaceFlexBus
314 as the most similar code, a cut/paste copy is taken and the new class
315 InterfaceSDRAM created:
316
317 class InterfaceSDRAM(InterfaceMultiBus, Interface):
318
319 def __init__(self, ifacename, pinspecs, ganged=None, single=False):
320 Interface.__init__(self, ifacename, pinspecs, ganged, single)
321 InterfaceMultiBus.__init__(self, self.pins)
322 self.add_bus(False, ['dqm', None, None],
323 "Bit#({0})", "sdrdqm")
324 self.add_bus(True, ['d_out', 'd_out_en', 'd_in'],
325 "Bit#({0})", "sdrd")
326 self.add_bus(False, ['ad', None, None],
327 "Bit#({0})", "sdrad")
328 self.add_bus(False, ['ba', None, None],
329 "Bit#({0})", "sdrba")
330
331 def ifacedef2(self, *args):
332 return InterfaceMultiBus.ifacedef2(self, *args)
333
334 Here, annoyingly, the data bus is a mess, requiring identification of
335 the three separate names for in, out and outen. The prefix "sdrd" is however
336 unique and obvious in its purpose: anything beginning with "sdrd" is
337 treated as a multi-bit bus, and a template for declaring a BSV type is
338 given that is automatically passed the numerical quantity of pins detected
339 that start with the word "sdrd".
340
341 Note that it is critical to lexicographically identify pins correctly,
342 so sdrdqm is done **before** sdrd.
343
344 Once the buses have been identified the peripheral can be added into
345 class Interfaces:
346
347 class Interfaces(InterfacesBase, PeripheralInterfaces):
348 """ contains a list of interface definitions
349 """
350
351 def __init__(self, pth=None):
352 InterfacesBase.__init__(self, Interface, pth,
353 {'gpio': InterfaceGPIO,
354 'fb': InterfaceFlexBus,
355 'sdr': InterfaceSDRAM, <--
356
357 Running the tool again results in a much smaller, tidier output
358 that will be a lot less work, later. Note the automatic inclusion of
359 the correct length multi-bit interfaces. d-out/in/out-en is identified
360 as 64-bit, ad is identified as 13-bit, ba as 2 and dqm as 8.
361
362 // interface declaration between SDR and pinmux
363 (*always_ready,always_enabled*)
364 interface PeripheralSideSDR;
365 interface Put#(Bit#(1)) sdrcke;
366 interface Put#(Bit#(1)) sdrrasn;
367 interface Put#(Bit#(1)) sdrcasn;
368 interface Put#(Bit#(1)) sdrwen;
369 interface Put#(Bit#(1)) sdrcsn0;
370
371 interface Put#(Bit#(8)) dqm;
372 interface Put#(Bit#(64)) d_out;
373 interface Put#(Bit#(64)) d_out_en;
374 interface Get#(Bit#(64)) d_in;
375 interface Put#(Bit#(13)) ad;
376 interface Put#(Bit#(2)) ba;
377
378 endinterface
379
380 ### Adding the peripheral
381
382 In examining the slow\_peripherals.bsv file, there should at this stage
383 be no sign of an SDRAM peripheral having been added, at all. This is
384 because it is missing from the peripheral\_gen side of the tool.
385
386 However, as the slow\_peripherals module takes care of the IO cells
387 (because it contains a declared and configured instance of the pinmux
388 package), signals from the pinmux PeripheralSideSDR instance need
389 to be passed *through* the slow peripherals module as an external
390 interface. This will happen automatically once a code-generator class
391 is added.
392
393 So first, we must identify the nearest similar class. FlexBus looks
394 like a good candidate, so we take a copy of src/bsv/peripheral\_gen/flexbus.py
395 called sdram.py. The simplest next step is to global/search/replace
396 "flexbus" with "sdram", and for peripheral instance declaration replace
397 "fb" with "sdr". At this phase, despite knowing that it will
398 auto-generate the wrong code, we add it as a "supported" peripheral
399 at the bottom of src/bsv/peripheral\_gen/base.py, in the "PFactory"
400 (Peripheral Factory) class:
401
402 from gpio import gpio
403 from rgbttl import rgbttl
404 from flexbus import flexbus
405 from sdram import sdram <--
406
407 for k, v in {'uart': uart,
408 'rs232': rs232,
409 'sdr': sdram, <--
410 'twi': twi,
411 'quart': quart,
412
413 Note that the name "SDR" matches with the prefix used in the pinspec
414 declaration, back in src/spec/pinfunctions.py, except lower-cased. Once this
415 is done, and the auto-generation tool re-run, examining the
416 slow\_peripherals.bsv file again shows the following (correct) and only
417 the following (correct) additions:
418
419 method Bit#(1) quart0_intr;
420 method Bit#(1) quart1_intr;
421 interface GPIO_config#(28) pad_configa;
422 interface PeripheralSideSDR sdr0; <--
423 interface PeripheralSideFB fb0;
424
425 ....
426 ....
427 interface iocell_side=pinmux.iocell_side;
428 interface sdr0 = pinmux.peripheral_side.sdr; <--
429 interface fb0 = pinmux.peripheral_side.fb;
430
431 These automatically-generated declarations are sufficient to "pass through"
432 the SDRAM "Peripheral Side", which as we know from examination of the code
433 is directly connected to the relevant IO pad cells, so that the *actual*
434 peripheral may be declared in the "fast" fabric and connected up to the
435 relevant and required "fast" bus.
436
437 ### Connecting in the fabric
438
439 Now we can begin the process of systematically inserting the correct
440 "voodoo magic" incantations that, as far as this auto-generator tool is
441 concerned, are just bits of ASCII text. In this particular instance, an
442 SDRAM peripheral happened to already be *in* the SoC's BSV source code,
443 such that the process of adding it to the tool is primarily one of
444 *conversion*.
445
446 **Please note that it is NOT recommended to do two tasks at once.
447 It is strongly recommended to add any new peripheral to a pre-existing
448 verified project, manually, by hand, and ONLY then to carry out a
449 conversion process to have this tool understand how to auto-generate
450 the fabric**
451
452 So examining the i\_class socgen.bsv file, we also open up
453 src/bsv/bsv\_lib/soc\_template.bsv in side-by-side windows of maximum
454 80 characters in width each, and *respect the coding convention for
455 this exact purpose*, can easily fit two such windows side-by-side
456 *as well as* a third containing the source code files that turn that
457 same template into its corresponding output.
458
459 We can now begin by searching for strings "SDRAM" and "sdr" in both
460 the template and the auto-generated socgen.bsv file. The first such
461 encounter is the import, in the template:
462
463 `ifdef BOOTROM
464 import BootRom ::*;
465 `endif
466 `ifdef SDRAM <-- xxxx
467 import sdr_top :: *; <-- xxxx
468 `endif <-- xxxx
469 `ifdef BRAM
470
471 This we can **remove**, and drop the corresponding code-fragment into
472 the sdram slowimport function:
473
474 class sdram(PBase):
475
476 def slowimport(self):
477 return "import sdr_top::*;" <--
478
479 def num_axi_regs32(self):
480
481 Now we re-run the auto-generator tool and confirm that, indeed, the
482 ifdef'd code is gone and replaced with an unconditional import:
483
484 import mqspi :: *;
485 import sdr_top::*; <--
486 import Uart_bs :: *;
487 import RS232_modified::*;
488 import mspi :: *;
489
490 Progress! Next, we examine the instance declaration clause. Remember
491 that we cut/paste the flexbus class, so we are expecting to find code
492 that declares the sdr0 instance as a FlexBus peripheral. We are
493 also looking for the hand-created code that is to be *replaced*. Sure enough:
494
495 AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE)
496 sdr0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor; <--
497 AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE)
498 fb0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor;
499 ...
500 ...
501 `ifdef BOOTROM
502 BootRom_IFC bootrom <-mkBootRom;
503 `endif
504 `ifdef SDRAM <--
505 Ifc_sdr_slave sdram<- mksdr_axi4_slave(clk0); <--
506 `endif <--
507
508 So, the mksdr\_axi4\_slave call we *remove* from the template and cut/paste
509 it into the sdram class's mkfast_peripheral function, making sure to
510 substitute the hard-coded instance name "sdram" with a python-formatted
511 template that can insert numerical instance identifiers, should it ever
512 be desired that there be more than one SDRAM peripheral put into a chip:
513
514 class sdram(PBase):
515
516 ...
517 ...
518 def mkfast_peripheral(self):
519 return "Ifc_sdr_slave sdr{0} <- mksdr_axi4_slave(clk0);"
520
521 Re-run the tool and check that the correct-looking code has been created:
522
523 Ifc_sdr_slave sdr0 <- mksdr_axi4_slave(clk0); <--
524 AXI4_Slave_to_FlexBus_Master_Xactor_IFC #(`PADDR, `DATA, `USERSPACE)
525 fb0 <- mkAXI4_Slave_to_FlexBus_Master_Xactor;
526 Ifc_rgbttl_dummy lcd0 <- mkrgbttl_dummy();
527
528 The next thing to do: searching for the string "sdram\_out" shows that the
529 original hand-generated code contains (contained) a declaration of the
530 SDRAM Interface, presumably to which, when compiling to run on an FPGA,
531 the SDRAM interface would be connected at the top level. Through this
532 interface, connections would be done *by hand* to the IO pads, whereas
533 now they are to be connected *automatically* (on the peripheral side)
534 to the IO pads in the pinmux. However, at the time of writing this is
535 not fully understood by the author, so the fastifdecl and extfastifinstance
536 functions are modified to generate the correct output but the code is
537 *commented out*
538
539 def extfastifinstance(self, name, count):
540 return "// TODO" + self._extifinstance(name, count, "_out", "", True,
541 ".if_sdram_out")
542
543 def fastifdecl(self, name, count):
544 return "// (*always_ready*) interface " + \
545 "Ifc_sdram_out sdr{0}_out;".format(count)
546
547 Also the corresponding (old) manual declarations of sdram\_out
548 removed from the template:
549
550 `ifdef SDRAM <-- xxxx
551 (*always_ready*) interface Ifc_sdram_out sdram_out; <-- xxxx
552 `endif <-- xxxx
553 ...
554 ...
555 `ifdef SDRAM <--- xxxx
556 interface sdram_out=sdram.ifc_sdram_out; <--- xxxx
557 `endif <--- xxxx
558
559 Next, again searching for signs of the "hand-written" code, we encounter
560 the fabric connectivity, which wires the SDRAM to the AXI4. We note however
561 that there is not just one AXI slave device but *two*: one for the SDRAM
562 itself and one for *configuring* the SDRAM. We therefore need to be
563 quite careful about assigning these, as will be subsequently explained.
564 First however, the two AXI4 slave interfaces of this peripheral are
565 declared:
566
567 class sdram(PBase):
568
569 ...
570 ...
571 def _mk_connection(self, name=None, count=0):
572 return ["sdr{0}.axi4_slave_sdram",
573 "sdr{0}.axi4_slave_cntrl_reg"]
574
575 Note that, again, in case multiple instances are ever to be added, the
576 python "format" string "{0}" is inserted so that it can be substituted
577 with the numerical identifier suffix. Also note that the order
578 of declaration of these two AXI4 slave is **important**.
579
580 Re-running the auto-generator tool, we note the following output has
581 been created, and match it against the corresponding hand-generated (old)
582 code:
583
584 `ifdef SDRAM
585 mkConnection (fabric.v_to_slaves
586 [fromInteger(valueOf(Sdram_slave_num))],
587 sdram.axi4_slave_sdram); //
588 mkConnection (fabric.v_to_slaves
589 [fromInteger(valueOf(Sdram_cfg_slave_num))],
590 sdram.axi4_slave_cntrl_reg); //
591 `endif
592
593 // fabric connections
594 mkConnection (fabric.v_to_slaves
595 [fromInteger(valueOf(SDR0_fastslave_num))],
596 sdr0.axi4_slave_sdram);
597 mkConnection (fabric.v_to_slaves
598 [fromInteger(valueOf(SDR0_fastslave_num))],
599 sdr0.axi4_slave_cntrl_reg);
600
601 Immediately we can spot an issue: whilst the correctly-named slave(s) have
602 been added, they have been added with the *same* fabric slave index. This
603 is unsatisfactory and needs resolving.
604
605 Here we need to explain a bit more about what is going on. The fabric
606 on an AXI4 Bus is allocated numerical slave numbers, and each slave is
607 also allocated a memory-mapped region that must be resolved in a bi-directional
608 fashion. i.e whenever a particular memory region is accessed, the AXI
609 slave peripheral responsible for dealing with it **must** be correctly
610 identified. So this requires some further crucial information, which is
611 the size of the region that is to be allocated to each slave device. Later
612 this will be extended to being part of the specification, but for now
613 it is auto-allocated based on the size. As a huge hack, it is allocated
614 in 32-bit chunks, as follows:
615
616 class sdram(PBase):
617
618 def num_axi_regs32(self):
619 return [0x400000, # defines an entire memory range (hack...)
620 12] # defines the number of configuration regs
621
622 So after running the autogenerator again, to confirm that this has
623 generated the correct code, we examine several files, starting with
624 fast\+memory\_map.bsv:
625
626 /*====== Fast peripherals Memory Map ======= */
627 `define SDR0_0_Base 'h50000000
628 `define SDR0_0_End 'h5FFFFFFF // 4194304 32-bit regs
629 `define SDR0_1_Base 'h60000000
630 `define SDR0_1_End 'h600002FF // 12 32-bit regs
631
632 This looks slightly awkward (and in need of an external specification
633 section for addresses) but is fine: the range is 1GB for the main
634 map and covers 12 32-bit registers for the SDR Config map.
635 Next we note the slave numbering:
636
637 typedef 0 SDR0_0__fastslave_num;
638 typedef 1 SDR0_1__fastslave_num;
639 typedef 2 FB0_fastslave_num;
640 typedef 3 LCD0_fastslave_num;
641 typedef 3 LastGen_fastslave_num;
642 typedef TAdd#(LastGen_fastslave_num,1) Sdram_slave_num;
643 typedef TAdd#(Sdram_slave_num ,`ifdef SDRAM 1 `else 0 `endif )
644 Sdram_cfg_slave_num;
645
646 Again this looks reasonable and we may subsequently (carefully! noting
647 the use of the TAdd# chain!) remove the #define for Sdram\_cfg\_slave\_num.
648 The next phase is to examine the fn\_addr\_to\_fastslave\_num function,
649 where we note that there were *two* hand-created sections previously,
650 now joined by two *auto-generated* sections:
651
652 function Tuple2 #(Bool, Bit#(TLog#(Num_Fast_Slaves)))
653 fn_addr_to_fastslave_num (Bit#(`PADDR) addr);
654
655 if(addr>=`SDRAMMemBase && addr<=`SDRAMMemEnd)
656 return tuple2(True,fromInteger(valueOf(Sdram_slave_num))); <--
657 else if(addr>=`DebugBase && addr<=`DebugEnd)
658 return tuple2(True,fromInteger(valueOf(Debug_slave_num))); <--
659 `ifdef SDRAM
660 else if(addr>=`SDRAMCfgBase && addr<=`SDRAMCfgEnd )
661 return tuple2(True,fromInteger(valueOf(Sdram_cfg_slave_num)));
662 `endif
663
664 ...
665 ...
666 if(addr>=`SDR0_0_Base && addr<=`SDR0_0_End) <--
667 return tuple2(True,fromInteger(valueOf(SDR0_0__fastslave_num)));
668 else
669 if(addr>=`SDR0_1_Base && addr<=`SDR0_1_End) <--
670 return tuple2(True,fromInteger(valueOf(SDR0_1__fastslave_num)));
671 else
672 if(addr>=`FB0Base && addr<=`FB0End)
673
674 Now, here is where, in a slightly unusual unique set of circumstances, we
675 cannot just remove all instances of this address / typedef from the template
676 code. Looking in the shakti-core repository's src/lib/MemoryMap.bsv file,
677 the SDRAMMemBase macro is utilise in the is\_IO\_Addr function. So as a
678 really bad hack, which will need to be properly resolved, whilst the
679 hand-generated sections from fast\_tuple2\_template.bsv are removed,
680 and the corresponding (now redundant) defines in src/core/core\_parameters.bsv
681 are commented out, some temporary typedefs to deal with the name change are
682 also added:
683
684 `define SDRAMMemBase SDR0_0_Base
685 `define SDRAMMemEnd SDR0_0_End
686
687 This needs to be addressed (pun intended) including being able to specify
688 the name(s) of the configuration parameters, as well as specifying which
689 memory map range they must be added to.
690
691 Now however finally, after carefully comparing the hard-coded fabric
692 connections to what was formerly named sdram, we may remove the mkConnections
693 that drop sdram.axi4\_slave\_sdram and its associated cntrl reg from
694 the soc\_template.bsv file.
695
696 ### Connecting up the pins
697
698 We are still not done! It is however worth pointing out that if this peripheral
699 were not wired into the pinmux, we would in fact be finished. However there
700 is a task that (previously having been left to outside tools) now needs to
701 be specified, which is to connect the sdram's pins, declared in this
702 instance in Ifc\_sdram\_out, and the PeripheralSideSDR instance that
703 was kindly / strategically / thoughtfully / absolutely-necessarily exported
704 from slow\_peripherals for exactly this purpose.
705
706 Recall earlier that we took a cut/paste copy of the flexbus.py code. If
707 we now examine socgen.bsv we find that it contains connections to pins
708 that match the FlexBus specification, not SDRAM. So, returning to the
709 declaration of the Ifc\_sdram\_out interface, we first identify the
710 single-bit output-only pins, and add a mapping table between them:
711
712 class sdram(PBase):
713
714 def pinname_out(self, pname):
715 return {'sdrwen': 'ifc_sdram_out.osdr_we_n',
716 'sdrcsn0': 'ifc_sdram_out.osdr_cs_n',
717 'sdrcke': 'ifc_sdram_out.osdr_cke',
718 'sdrrasn': 'ifc_sdram_out.osdr_ras_n',
719 'sdrcasn': 'ifc_sdram_out.osdr_cas_n',
720 }.get(pname, '')
721
722 Re-running the tool confirms that the relevant mkConnections are generated:
723
724 //sdr {'action': True, 'type': 'out', 'name': 'sdrcke'}
725 mkConnection(slow_peripherals.sdr0.sdrcke,
726 sdr0_sdrcke_sync.get);
727 mkConnection(sdr0_sdrcke_sync.put,
728 sdr0.ifc_sdram_out.osdr_cke);
729 //sdr {'action': True, 'type': 'out', 'name': 'sdrrasn'}
730 mkConnection(slow_peripherals.sdr0.sdrrasn,
731 sdr0_sdrrasn_sync.get);
732 mkConnection(sdr0_sdrrasn_sync.put,
733 sdr0.ifc_sdram_out.osdr_ras_n);
734
735 Next, the multi-value entries are tackled (both in and out). At present
736 the code is messy, as it does not automatically detect the multiple numerical
737 declarations, nor that the entries are sometimes inout (in, out, outen),
738 so it is *presently* done by hand:
739
740 class sdram(PBase):
741
742 def _mk_pincon(self, name, count, typ):
743 ret = [PBase._mk_pincon(self, name, count, typ)]
744 assert typ == 'fast' # TODO slow?
745 for pname, stype, ptype in [
746 ('dqm', 'osdr_dqm', 'out'),
747 ('ba', 'osdr_ba', 'out'),
748 ('ad', 'osdr_addr', 'out'),
749 ('d_out', 'osdr_dout', 'out'),
750 ('d_in', 'ipad_sdr_din', 'in'),
751 ('d_out_en', 'osdr_den_n', 'out'),
752 ]:
753 ret.append(self._mk_vpincon(name, count, typ, ptype, pname,
754 "ifc_sdram_out.{0}".format(stype)))
755
756 This generates *one* mkConnection for each multi-entry pintype, and here we
757 match up with the "InterfaceMultiBus" class from the specification side,
758 where pin entries with numerically matching names were "grouped" into single
759 multi-bit declarations.
760
761 ### Adjusting the BSV Interface to a get/put style
762
763 For various reasons, related to BSV not permitting wires to be connected
764 back-to-back inside the pinmux code, a get/put style of interface had to
765 be done. This requirement has a knock-on effect up the chain into the
766 actual peripheral code. So now the actual interface (Ifc\_sdram\_out)
767 has to be converted. All straight methods (outputs) are converted to Get,
768 and Action methods (inputs) converted to Put. Also, it is just plain
769 sensible not to use Bool but to use Bit#, and for the pack / unpack to
770 be carried out in the interface. After conversion, the code looks like this:
771
772 interface Ifc_sdram_out;
773 (*always_enabled, always_ready*)
774 interface Put#(Bit#(64)) ipad_sdr_din;
775 interface Get#(Bit#(64)) osdr_dout;
776 interface Get#(Bit#(64)) osdr_den_n;
777 interface Get#(Bit#(1)) osdr_cke;
778 interface Get#(Bit#(1)) osdr_cs_n;
779 interface Get#(Bit#(1)) osdr_ras_n;
780 interface Get#(Bit#(1)) osdr_cas_n;
781 interface Get#(Bit#(1)) osdr_we_n;
782 interface Get#(Bit#(8)) osdr_dqm;
783 interface Get#(Bit#(2)) osdr_ba;
784 interface Get#(Bit#(13)) osdr_addr;
785
786 method Bit#(9) sdram_sdio_ctrl;
787 interface Clock sdram_clk;
788 endinterface
789
790 Note that osdr\_den\_n is now **64** bit **not** 8, as discussed above.
791 After conversion, the code looks like this:
792
793 interface Ifc_sdram_out ifc_sdram_out;
794
795 interface ipad_sdr_din = interface Put
796 method Action put(Bit#(64) in)
797 sdr_cntrl.ipad_sdr_din <= in;
798 endmethod
799 endinterface;
800
801 interface osdr_dout = interface Get
802 method ActionValue#(Bit#(64)) get;
803 return sdr_cntrl.osdr_dout();
804 endmethod
805 endinterface;
806
807 interface osdr_den_n = interface Get
808 method ActionValue#(Bit#(64)) get;
809 Bit#(64) temp;
810 for (int i=0; i<8; i=i+1) begin
811 temp[i*8] = sdr_cntrl.osdr_den_n[i];
812 end
813 return temp;
814 endmethod
815 endinterface;
816
817 interface osdr_cke = interface Get
818 method ActionValue#(Bit#(1)) get;
819 return pack(sdr_cntrl.osdr_cke());
820 endmethod
821 endinterface;
822
823 ...
824 ...
825
826 endinterface;
827
828 Note that the data input is quite straightforward, as is data out,
829 and cke: whether 8-bit, 13-bit or 64-bit, the conversion process is
830 mundane, with only Bool having to be converted to Bit#(1) with a call
831 to pack. The data-enable however is a massive hack: whilst 64 enable
832 lines come in, only every 8th bit is actually utilised and passed
833 through. Whether this should be changed is a matter for debate that
834 is outside of the scope of this document.
835
836 Lastly for this phase we have two anomalous interfaces, exposing
837 the control registers and the clock, that have been moved out of
838 Ifc\_sdram\_out, to the level above (Ifc\_sdr\_slave) so that the sole
839 set of interfaces exposed for inter-connection by the auto-generator is
840 related exclusively to the actual pins. Resolution of these two issues
841 is currently outside of the scope of this document.
842
843 ### Clock synchronisation
844
845 Astute readers, if their heads have not exploded by this point, will
846 have noticed earlier that the SDRAM instance was declared with an entirely
847 different clock domain from slow peripherals. Whilst the pinmux is
848 completely combinatorial logic that is entirely unclocked, BSV has no
849 means of informing a *module* of this fact, and consequently a weird
850 null-clock splicing trick is required.
851
852 This code is again auto-generated. However, it is slightly tricky to
853 describe and there are several edge-cases, including ones where the
854 peripheral is a slow peripheral (UART is an example) that is driven
855 from a UART clock but it is connected up inside the slow peripherals
856 code; FlexBus is a Fast Bus peripheral that needs syncing up; RGB/TTL
857 likewise, but JTAG is specifically declared inside the SoC and passed
858 through, but its instantiation requires a separate incoming clock.
859
860 Examining the similarity between the creation of an SDRAM instance
861 and the JTAG instance, the jtag code therefore looks like it is the
862 best candidate fit. However, it passes through a reset signal as well.
863 Instead, we modify this to create clk0 but use the slow\_reset
864 signal:
865
866 class sdram(PBase):
867
868 def get_clk_spc(self, typ):
869 return "tck, slow_reset"
870
871 def get_clock_reset(self, name, count):
872 return "slow_clock, slow_reset"
873
874 The resultant synchronisation code, that accepts pairs of clock/reset
875 tuples in order to take care of the prerequisite null reset and clock
876 "synchronisation", looks like this:
877
878 Ifc_sync#(Bit#(1)) sdr0_sdrcke_sync <-mksyncconnection(
879 clk0, slow_reset, slow_clock, slow_reset);
880 Ifc_sync#(Bit#(1)) sdr0_sdrrasn_sync <-mksyncconnection(
881 clk0, slow_reset, slow_clock, slow_reset);
882 ...
883 ...
884 Ifc_sync#(Bit#(64)) sdr0_d_in_sync <-mksyncconnection(
885 slow_clock, slow_reset, clk0, slow_reset);
886
887 Note that inputs are in reverse order from outputs. With the inclusion of
888 clock synchronisation, automatic chains of mkConnections are set up between
889 the actual peripheral and the peripheral side of the pinmux:
890
891 mkConnection(slow_peripherals.sdr0.sdrcke,
892 sdr0_sdrcke_sync.get);
893 mkConnection(sdr0_sdrcke_sync.put,
894 sdr0.ifc_sdram_out.osdr_cke);
895
896 Interestingly, if *actual* clock synchronisation were ever to be needed,
897 it could easily be taken care of with relatively little extra work. However,
898 it is worth emphasising that the pinmux is *entirely* unclocked zero-reset
899 combinatorial logic.
900
901 # Adding a "Slow" Peripheral
902
903 This example will be cheating by cut/paste copying an existing very
904 similar interface, SD/MMC. The SD/MMC interface is presently a "dummy"
905 that will be extended later. However given that the peripherals are
906 so very very similar, common base classes will be used, in a style that
907 has also been used in SPI/QSPI and also UART.
908
909 ## Adding the pin specifications
910
911 Looking at src/spec/pinfunctions.py we find that an emmc function actually
912 already exists. So unlike sdram, there are no modifications to be made.
913 We can check that it works by creating an example, say in src/spec/i\_class.py
914 by adding an emmc interface to Bank B:
915
916 ps.gpio("", ('B', 0), 0, 0, 18)
917 ps.flexbus1("", ('B', 0), 1, spec=flexspec)
918 ps.emmc("", ('B', 0), 3) <---
919
920 ps.flexbus2("", ('C', 0), 0)
921
922 We then need to generate the spec. At the top level the following command is
923 then run:
924
925 $ python src/pinmux_generator.py -o i_class -s i_class
926
927 Checking the resultant markdown file ./i\_class/i\_class.mdwn, we find that
928 several entries have been added at the required location:
929
930 | Pin | Mux0 | Mux1 | Mux2 | Mux3 |
931 | --- | ----------- | ----------- | ----------- | ----------- |
932 | 28 | B GPIOB_B0 | B FB_AD2 | | B EMMC_CMD |
933 | 29 | B GPIOB_B1 | B FB_AD3 | | B EMMC_CLK |
934 | 30 | B GPIOB_B2 | B FB_AD4 | | B EMMC_D0 |
935 | 31 | B GPIOB_B3 | B FB_AD5 | | B EMMC_D1 |
936 | 32 | B GPIOB_B4 | B FB_AD6 | | B EMMC_D2 |
937 | 33 | B GPIOB_B5 | B FB_AD7 | | B EMMC_D3 |
938 | 34 | B GPIOB_B6 | B FB_CS0 | | B EMMC_D4 |
939 | 35 | B GPIOB_B7 | B FB_CS1 | | B EMMC_D5 |
940 | 36 | B GPIOB_B8 | B FB_ALE | | B EMMC_D6 |
941 | 37 | B GPIOB_B9 | B FB_OE | B FB_TBST | B EMMC_D7 |
942 | 38 | B GPIOB_B10 | B FB_RW | | |
943
944 We also check i\_class/interfaces.txt to see if the single requested emmc
945 interface is there:
946
947 gpiob 1
948 eint 1
949 mqspi 1
950 emmc 1 <--
951 uart 3
952
953 Also we examine the i\_class/emmc.txt file to check that it has the right
954 types of pin definitions:
955
956 cmd out
957 clk out
958 d0 inout bus
959 d1 inout bus
960 d2 inout bus
961 d3 inout bus
962 d4 inout bus
963 d5 inout bus
964 d6 inout bus
965 d7 inout bus
966
967 and we check the i\_class/pinmap.txt tab-separated file to see if it
968 contains the entries corresponding to the markdown table:
969
970 24 A 4 gpioa_a24 mspi1_ck jtag_tms mmc0_d0
971 25 A 4 gpioa_a25 mspi1_nss jtag_tdi mmc0_d1
972 26 A 4 gpioa_a26 mspi1_io0 jtag_tdo mmc0_d2
973 27 A 4 gpioa_a27 mspi1_io1 jtag_tck mmc0_d3
974 28 B 4 gpiob_b0 fb_ad2 emmc_cmd
975 29 B 4 gpiob_b1 fb_ad3 emmc_clk
976 30 B 4 gpiob_b2 fb_ad4 emmc_d0
977 31 B 4 gpiob_b3 fb_ad5 emmc_d1
978 32 B 4 gpiob_b4 fb_ad6 emmc_d2
979 33 B 4 gpiob_b5 fb_ad7 emmc_d3
980 34 B 4 gpiob_b6 fb_cs0 emmc_d4
981 35 B 4 gpiob_b7 fb_cs1 emmc_d5
982 36 B 4 gpiob_b8 fb_ale emmc_d6
983 37 B 4 gpiob_b9 fb_oe fb_tbst emmc_d7
984
985 This concludes this section as the purpose of the spec-generation side,
986 to create documentation and TSV files for the second phase, has been
987 fulfilled. Note that we did *not* declare in PinSpec that this
988 peripheral is to be added onto the fastbus, as by default peripherals
989 are added to a single AXI4-Lite interface.
990
991 ## Adding the pinmux code auto-generator
992
993 The next phase begins with adding class support to auto-generate the pinmux
994 code. Starting with the following command:
995
996 $ python src/pinmux_generator.py -o i_class
997
998 The first thing to do is look at i\_class/bsv\_src/pinmux.bsv, and search
999 for both PeripheralSideMMC and PeripheralSideEMMC. PeripheralSideMMC is
1000 very short and compact:
1001
1002 // interface declaration between MMC and pinmux
1003 (*always_ready,always_enabled*)
1004 interface PeripheralSideMMC;
1005 interface Put#(Bit#(1)) cmd;
1006 interface Put#(Bit#(1)) clk;
1007 interface Put#(Bit#(4)) out;
1008 interface Put#(Bit#(4)) out_en;
1009 interface Get#(Bit#(4)) in;
1010 endinterface
1011
1012 whereas PeripheralSideEMMC is a mess:
1013
1014 interface PeripheralSideEMMC;
1015 interface Put#(Bit#(1)) cmd;
1016 interface Put#(Bit#(1)) clk;
1017 interface Put#(Bit#(1)) d0_out;
1018 interface Put#(Bit#(1)) d0_outen;
1019 interface Get#(Bit#(1)) d0_in;
1020 interface Put#(Bit#(1)) d1_out;
1021 interface Put#(Bit#(1)) d1_outen;
1022 interface Get#(Bit#(1)) d1_in;
1023 interface Put#(Bit#(1)) d2_out;
1024 interface Put#(Bit#(1)) d2_outen;
1025 interface Get#(Bit#(1)) d2_in;
1026 ...
1027 ...
1028 endinterface
1029
1030 To correct this, we need to create an InterfaceEMMC class in
1031 src/bsv/interface\_decl.py that generates the right code. However on
1032 close inspection, given that the format needed is identical (except for
1033 the number of data lines), we can probably get away with using *exactly*
1034 the same class:
1035
1036 class Interfaces(InterfacesBase, PeripheralInterfaces):
1037
1038 def __init__(self, pth=None):
1039 InterfacesBase.__init__(self, Interface, pth,
1040 {'gpio': InterfaceGPIO,
1041 ...
1042 ...
1043 'mmc': InterfaceSD,
1044 'emmc': InterfaceSD, <--
1045 'fb': InterfaceFlexBus,
1046 ...
1047
1048 and after re-running the command the output looks like this:
1049
1050 interface PeripheralSideEMMC;
1051 interface Put#(Bit#(1)) cmd;
1052 interface Put#(Bit#(1)) clk;
1053 interface Put#(Bit#(8)) out;
1054 interface Put#(Bit#(8)) out_en;
1055 interface Get#(Bit#(8)) in;
1056 endinterface
1057
1058 Success! The class InterfaceSD appears to be sufficiently generic that
1059 it could understand that it had been passed 8-pins worth of data with
1060 exactly the same names, rather than 4. This is encouraging in the sense
1061 that re-using the SD/MMC BSV generation code should also be as easy.
1062
1063 ## Adding the slow peripheral code-generator
1064
1065 So this time we will try cut/pasting src/bsv/peripheral\_gen/sdmmc.py
1066 to create a base class, MMCBase. The only two common functions are
1067 pinname\_out and \_mk\_pincon.
1068
1069 class MMCBase(PBase):
1070
1071 def pinname_out(self, pname):
1072 if pname in ['cmd', 'clk']:
1073 return pname
1074 return ''
1075
1076 def _mk_pincon(self, name, count, typ):
1077 ...
1078 ...
1079
1080 Then, the sdmmc class is modified to inherit it, this time cutting *out*
1081 all but those two functions:
1082
1083 from bsv.peripheral\_gen.mmcbase import MMCBase <--
1084
1085 class sdmmc(MMCBase): <--
1086
1087 And at the same time we create an emmc.py file where all occurrences of
1088 sdmmc are replaced with emmc:
1089
1090 class emmc(MMCBase):
1091
1092 def slowimport(self):
1093 return "import emmc_dummy :: *;"
1094
1095 ...
1096 ...
1097
1098 def _mk_connection(self, name=None, count=0):
1099 return "emmc{0}.slave"
1100
1101 Finally, to use it, just as with sdram, we add the new peripheral
1102 at the bottom of src/bsv/peripheral\_gen/base.py, in the "PFactory"
1103 (Peripheral Factory) class:
1104
1105 from gpio import gpio
1106 from rgbttl import rgbttl
1107 from flexbus import flexbus
1108 from emmc import emmc <--
1109
1110 for k, v in {'uart': uart,
1111 'rs232': rs232,
1112 'emmc': emmc, <--
1113
1114 For the actual auto-generation phase, this really should be all that's
1115 needed. Re-running the code-generator we can examine the auto-generated
1116 slow\_peripherals.bsv file and can confirm that yes, an "import emmc\_dummy"
1117 has been added, that an mmc0 instance has been created, that it is added
1118 to the slave fabric, and that its cmd, clk and in/out/out\_en are all
1119 connected up.
1120
1121 The last remaining task will therefore be to create an interim emmc
1122 "dummy" BSV file.
1123
1124 ## Creating the dummy emmc peripheral
1125
1126 Adding the actual peripheral is done in a different repository,
1127 shakti-peripherals, which can be cloned with:
1128
1129 $ git clone gitolite3@libre-riscv.org:shakti-peripherals.git
1130
1131 or public:
1132
1133 $ git clone git://libre-riscv.org/shakti-peripherals.git
1134
1135 Here, what we will do is take a straight copy of
1136 src/peripherals/sdmmc/sdcard\_dummy.bsv and call it
1137 src/peripherals/emmc/emmc\_dummy.bsv. Then replace all occurrences
1138 of "sdcard" with "emmc" and also update the SDBUSWIDTH from 4 to 8.
1139 Whilst this appears wasteful it is by far the simplest and quickest
1140 way to get working code, that should definitely, definitely be
1141 re-factored later.
1142
1143 The next stage is to return to the pinmux repository and add the
1144 import of the new emmc subdirectory to the BSVINCDIR in both
1145 src/bsv/Makefile.template and Makefile.peripherals.template:
1146
1147 BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/spi
1148 BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/sdmmc
1149 BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/emmc
1150 BSVINCDIR:= $(BSVINCDIR):../../../src/peripherals/src/peripherals/flexbus
1151
1152 Really these should also be auto-generated. Testing through compiling
1153 can now take place.
1154
1155 ## Compiling the BSV to verilog
1156
1157 Here an additional repository is required, which can be cloned as follows:
1158
1159 $ git clone gitolite3@libre-riscv.org:shakti-iclass.git
1160
1161 or public:
1162
1163 $ git clone git://libre-riscv.org/shakti-iclass.git
1164
1165 This pulls in submodules automatically, and begins building the BSV,
1166 using the following commands:
1167
1168 $ ./bin/gitmoduleupdate.sh
1169 $ make
1170
1171 # Conclusion
1172
1173 This is not a small project, by any means. However the payoff in saved
1174 time is enormous. The conversion of SDRAM from a hand-crafted fixed and
1175 manually laborious task to an automated one took around a day, with debugging
1176 and testing to follow. Once done (particularly, once done with *prior tested
1177 peripherals*), it becomes possible to add *any arbitrary number* of such
1178 peripherals with a single line of code, back in the specification.
1179