7
7
from pathlib import Path , PurePosixPath
8
8
import importlib
9
9
import shutil
10
+ import enum
11
+ from enum import auto
10
12
11
13
from bronzebeard .asm import assemble
12
14
from elftools .elf .elffile import ELFFile
13
15
from amaranth import Module , Memory , Signal , Cat , C
14
16
from amaranth_soc import wishbone
17
+ from amaranth_soc import csr
18
+ from amaranth_soc .csr .wishbone import WishboneCSRBridge
15
19
from amaranth_soc .memory import MemoryMap
16
20
from amaranth .lib .wiring import In , Out , Component , Elaboratable , connect , \
17
21
Signature , flipped
@@ -87,7 +91,7 @@ def elaborate(self, plat):
87
91
return m
88
92
89
93
90
- class Leds (Component ):
94
+ class WBLeds (Component ):
91
95
@property
92
96
def signature (self ):
93
97
return self ._signature
@@ -145,7 +149,56 @@ def elaborate(self, plat):
145
149
return m
146
150
147
151
148
- class Timer (Component ):
152
+ class CSRLeds (Component ):
153
+ @property
154
+ def signature (self ):
155
+ return self ._signature
156
+
157
+ def __init__ (self ):
158
+ self .mux = csr .bus .Multiplexer (addr_width = 4 , data_width = 8 , name = "gpio" )
159
+ self ._signature = self .mux .signature
160
+ self ._signature .members += {
161
+ "leds" : Out (8 ),
162
+ "gpio" : In (Signature ({
163
+ "i" : In (1 ),
164
+ "o" : Out (1 ),
165
+ "oe" : Out (1 )
166
+ })).array (8 )
167
+ }
168
+
169
+ self .leds_reg = csr .Element (8 , "w" , path = ("leds" ,))
170
+ self .inout_reg = csr .Element (8 , "rw" , path = ("inout" ,))
171
+ self .oe_reg = csr .Element (8 , "rw" , path = ("oe" ,))
172
+ self .mux .add (self .leds_reg , name = "leds" )
173
+ self .mux .add (self .inout_reg , name = "inout" , addr = 4 )
174
+ self .mux .add (self .oe_reg , name = "oe" , addr = 8 )
175
+
176
+ super ().__init__ ()
177
+
178
+ def elaborate (self , plat ):
179
+ m = Module ()
180
+ m .submodules .mux = self .mux
181
+
182
+ connect (m , flipped (self .bus ), self .mux .bus )
183
+
184
+ with m .If (self .leds_reg .w_stb ):
185
+ m .d .sync += self .leds .eq (self .leds_reg .w_data )
186
+
187
+ with m .If (self .inout_reg .w_stb ):
188
+ for i in range (8 ):
189
+ m .d .sync += self .gpio [i ].o .eq (self .leds_reg .w_data [i ])
190
+
191
+ for i in range (8 ):
192
+ m .d .comb += self .inout_reg .r_data [i ].eq (self .gpio [i ].i )
193
+
194
+ with m .If (self .oe_reg .w_stb ):
195
+ for i in range (8 ):
196
+ m .d .sync += self .gpio [i ].oe .eq (self .oe_reg .w_data [i ])
197
+
198
+ return m
199
+
200
+
201
+ class WBTimer (Component ):
149
202
@property
150
203
def signature (self ):
151
204
return self ._signature
@@ -186,6 +239,45 @@ def elaborate(self, plat):
186
239
return m
187
240
188
241
242
+ class CSRTimer (Component ):
243
+ @property
244
+ def signature (self ):
245
+ return self ._signature
246
+
247
+ def __init__ (self ):
248
+ self .mux = csr .bus .Multiplexer (addr_width = 3 , data_width = 8 ,
249
+ name = "timer" )
250
+ self ._signature = self .mux .signature
251
+ self ._signature .members += {
252
+ "irq" : Out (1 )
253
+ }
254
+
255
+ self .irq_reg = csr .Element (8 , "r" , path = ("irq" ,))
256
+ self .mux .add (self .irq_reg , name = "irq" )
257
+
258
+ super ().__init__ ()
259
+
260
+ def elaborate (self , plat ):
261
+ m = Module ()
262
+ m .submodules .mux = self .mux
263
+
264
+ connect (m , flipped (self .bus ), self .mux .bus )
265
+
266
+ prescaler = Signal (15 )
267
+
268
+ m .d .sync += prescaler .eq (prescaler + 1 )
269
+ m .d .comb += [
270
+ self .irq .eq (prescaler [14 ]),
271
+ self .irq_reg .r_data .eq (prescaler [14 ])
272
+ ]
273
+
274
+ with m .If (self .irq_reg .r_stb ):
275
+ with m .If (self .irq ):
276
+ m .d .sync += prescaler [14 ].eq (0 )
277
+
278
+ return m
279
+
280
+
189
281
# Taken from: https://github.com/amaranth-lang/amaranth/blob/f9da3c0d166dd2be189945dca5a94e781e74afeb/examples/basic/uart.py # noqa: E501
190
282
class UART (Elaboratable ):
191
283
"""
@@ -301,6 +393,8 @@ def elaborate(self, plat):
301
393
m = Module ()
302
394
m .submodules .ser_internal = self .serial
303
395
396
+ # rx_rdy_prev having reset of 0 will deliberately will trigger IRQ at
397
+ # reset. We use this to detect WB vs CSR bus.
304
398
rx_rdy_irq = Signal ()
305
399
rx_rdy_prev = Signal ()
306
400
tx_ack_irq = Signal ()
@@ -351,17 +445,111 @@ def elaborate(self, plat):
351
445
return m
352
446
353
447
448
+ class CSRSerial (Component ):
449
+ @property
450
+ def signature (self ):
451
+ return self ._signature
452
+
453
+ def __init__ (self ):
454
+ self .mux = csr .bus .Multiplexer (addr_width = 3 , data_width = 8 , alignment = 0 ,
455
+ name = "serial" )
456
+ self ._signature = self .mux .signature
457
+ self ._signature .members += {
458
+ "rx" : In (1 ),
459
+ "tx" : Out (1 ),
460
+ "irq" : Out (1 )
461
+ }
462
+
463
+ self .txrx_reg = csr .Element (8 , "rw" , path = ("txrx" ,))
464
+ self .mux .add (self .txrx_reg , name = "txrx" )
465
+ self .irq_reg = csr .Element (8 , "r" , path = ("irq" ,))
466
+ self .mux .add (self .irq_reg , name = "irq" , addr = 4 )
467
+
468
+ super ().__init__ ()
469
+ self .serial = UART (divisor = 12000000 // 9600 )
470
+
471
+ def elaborate (self , plat ):
472
+ m = Module ()
473
+ m .submodules .ser_internal = self .serial
474
+ m .submodules .mux = self .mux
475
+
476
+ connect (m , flipped (self .bus ), self .mux .bus )
477
+
478
+ # rx_rdy_prev having reset of 1 will suppress IRQ at reset. We use this
479
+ # to detect WB vs CSR bus.
480
+ rx_rdy_irq = Signal ()
481
+ rx_rdy_prev = Signal (reset = 1 )
482
+ tx_ack_irq = Signal ()
483
+ tx_ack_prev = Signal (reset = 1 )
484
+
485
+ m .d .comb += [
486
+ self .irq .eq (rx_rdy_irq | tx_ack_irq ),
487
+ self .tx .eq (self .serial .tx_o ),
488
+ self .serial .rx_i .eq (self .rx )
489
+ ]
490
+
491
+ m .d .sync += [
492
+ rx_rdy_prev .eq (self .serial .rx_rdy ),
493
+ tx_ack_prev .eq (self .serial .tx_ack ),
494
+ ]
495
+
496
+ m .d .comb += [
497
+ self .serial .tx_data .eq (self .txrx_reg .w_data ),
498
+ self .txrx_reg .r_data .eq (self .serial .rx_data ),
499
+ self .irq_reg .r_data .eq (Cat (rx_rdy_irq , tx_ack_irq ))
500
+ ]
501
+
502
+ with m .If (self .txrx_reg .w_stb ):
503
+ m .d .comb += self .serial .tx_rdy .eq (1 )
504
+ with m .If (self .txrx_reg .r_stb ):
505
+ m .d .comb += self .serial .rx_ack .eq (1 )
506
+
507
+ with m .If (self .irq_reg .r_stb ):
508
+ m .d .sync += [
509
+ rx_rdy_irq .eq (0 ),
510
+ tx_ack_irq .eq (0 )
511
+ ]
512
+
513
+ # Don't accidentally miss an IRQ
514
+ with m .If (self .serial .rx_rdy & ~ rx_rdy_prev ):
515
+ m .d .sync += rx_rdy_irq .eq (1 )
516
+ with m .If (self .serial .tx_ack & ~ tx_ack_prev ):
517
+ m .d .sync += tx_ack_irq .eq (1 )
518
+
519
+ return m
520
+
521
+
522
+ class BusType (enum .Enum ):
523
+ WB = auto ()
524
+ CSR = auto ()
525
+
526
+
354
527
# Reimplementation of nextpnr-ice40's AttoSoC example (https://github.com/YosysHQ/nextpnr/tree/master/ice40/smoketest/attosoc), # noqa: E501
355
528
# to exercise attaching Sentinel to amaranth-soc's wishbone components.
356
529
class AttoSoC (Elaboratable ):
357
- def __init__ (self , * , sim = False , num_bytes = 0x400 ):
530
+ # CSR is the default because it's what's encouraged. However, the default
531
+ # for the demo is WB because that's what fits on the ICE40HX1K!
532
+ def __init__ (self , * , sim = False , num_bytes = 0x400 , bus_type = BusType .CSR ):
358
533
self .cpu = Top ()
359
534
self .mem = WBMemory (sim = sim , num_bytes = num_bytes )
360
- self .leds = Leds ()
535
+ self .decoder = wishbone .Decoder (addr_width = 30 , data_width = 32 ,
536
+ granularity = 8 , alignment = 25 )
361
537
self .sim = sim
362
- if not self .sim :
363
- self .timer = Timer ()
364
- self .serial = WBSerial ()
538
+ self .bus_type = bus_type
539
+
540
+ match bus_type :
541
+ case BusType .WB :
542
+ self .leds = WBLeds ()
543
+
544
+ if not self .sim :
545
+ self .timer = WBTimer ()
546
+ self .serial = WBSerial ()
547
+ case BusType .CSR :
548
+ self .leds = CSRLeds ()
549
+
550
+ if not self .sim :
551
+ self .timer = CSRTimer ()
552
+ self .serial = CSRSerial ()
365
553
366
554
@property
367
555
def rom (self ):
@@ -384,13 +572,10 @@ def rom(self, source_or_list):
384
572
def elaborate (self , plat ):
385
573
m = Module ()
386
574
387
- decoder = wishbone .Decoder (addr_width = 30 , data_width = 32 , granularity = 8 ,
388
- alignment = 25 )
389
-
390
575
m .submodules .cpu = self .cpu
391
576
m .submodules .mem = self .mem
392
577
m .submodules .leds = self .leds
393
- m .submodules .decoder = decoder
578
+ m .submodules .decoder = self . decoder
394
579
395
580
if plat :
396
581
for i in range (8 ):
@@ -414,14 +599,38 @@ def elaborate(self, plat):
414
599
gpio .o .eq (self .leds .gpio [i ].o )
415
600
]
416
601
417
- decoder .add (flipped (self .mem .bus ))
418
- decoder .add (flipped (self .leds .bus ), sparse = True )
419
- if not self .sim :
420
- m .submodules .timer = self .timer
421
- decoder .add (flipped (self .timer .bus ), sparse = True )
602
+ self .decoder .add (flipped (self .mem .bus ))
603
+
604
+ if self .bus_type == BusType .WB :
605
+ self .decoder .add (flipped (self .leds .bus ), sparse = True )
422
606
423
- m .submodules .serial = self .serial
424
- decoder .add (flipped (self .serial .bus ), sparse = True )
607
+ if not self .sim :
608
+ m .submodules .timer = self .timer
609
+ self .decoder .add (flipped (self .timer .bus ), sparse = True )
610
+
611
+ m .submodules .serial = self .serial
612
+ self .decoder .add (flipped (self .serial .bus ), sparse = True )
613
+
614
+ elif self .bus_type == BusType .CSR :
615
+ # CSR (has to be done first other mem map "frozen" errors?)
616
+ periph_decode = csr .Decoder (addr_width = 25 , data_width = 8 ,
617
+ alignment = 23 , name = "periph" )
618
+ periph_decode .add (self .leds .bus , addr = 0 )
619
+
620
+ if not self .sim :
621
+ periph_decode .add (self .timer .bus )
622
+ periph_decode .add (self .serial .bus )
623
+ m .submodules .timer = self .timer
624
+ m .submodules .serial = self .serial
625
+
626
+ # Connect peripherals to Wishbone
627
+ periph_wb = WishboneCSRBridge (periph_decode .bus , data_width = 32 )
628
+ self .decoder .add (flipped (periph_wb .wb_bus ))
629
+
630
+ m .submodules .periph_bus = periph_decode
631
+ m .submodules .periph_wb = periph_wb
632
+
633
+ if not self .sim :
425
634
if plat :
426
635
m .d .comb += [
427
636
self .serial .rx .eq (ser .rx .i ),
@@ -434,16 +643,20 @@ def destruct_res(res):
434
643
return ("/" .join (res .path ), res .start , res .end , res .width )
435
644
436
645
print (tabulate (map (destruct_res ,
437
- decoder .bus .memory_map .all_resources ()),
646
+ self . decoder .bus .memory_map .all_resources ()),
438
647
intfmt = ("" , "#010x" , "#010x" , "" ),
439
648
headers = ["name" , "start" , "end" , "width" ]))
440
- connect (m , self .cpu .bus , decoder .bus )
649
+ connect (m , self .cpu .bus , self . decoder .bus )
441
650
442
651
return m
443
652
444
653
445
654
def demo (args ):
446
- asoc = AttoSoC (num_bytes = 0x1000 )
655
+ match args .i :
656
+ case "wishbone" :
657
+ asoc = AttoSoC (num_bytes = 0x1000 , bus_type = BusType .WB )
658
+ case "csr" :
659
+ asoc = AttoSoC (num_bytes = 0x1000 , bus_type = BusType .CSR )
447
660
448
661
if args .g :
449
662
# In-line objcopy -O binary implementation. Probably does not handle
@@ -586,6 +799,9 @@ def main():
586
799
parser .add_argument ("-p" , help = "build platform" ,
587
800
choices = ("icestick" , "ice40_hx8k_b_evn" ),
588
801
default = "icestick" )
802
+ parser .add_argument ("-i" , help = "peripheral interconnect type" ,
803
+ choices = ("wishbone" , "csr" ),
804
+ default = "wishbone" )
589
805
group = parser .add_mutually_exclusive_group ()
590
806
# Remote firmware override/random file generation is not supported;
591
807
# Amaranth does not have provisions for supporting adding your own build
0 commit comments