Skip to content

Commit 12e79ef

Browse files
committed
Add option to place peripherals on CSR bus. Update firmware to accommodate different I/O addrs. Update fixtures to accommodate amaranth_soc quirks.
1 parent 39005c1 commit 12e79ef

File tree

4 files changed

+341
-46
lines changed

4 files changed

+341
-46
lines changed

examples/attosoc.py

Lines changed: 237 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@
77
from pathlib import Path, PurePosixPath
88
import importlib
99
import shutil
10+
import enum
11+
from enum import auto
1012

1113
from bronzebeard.asm import assemble
1214
from elftools.elf.elffile import ELFFile
1315
from amaranth import Module, Memory, Signal, Cat, C
1416
from amaranth_soc import wishbone
17+
from amaranth_soc import csr
18+
from amaranth_soc.csr.wishbone import WishboneCSRBridge
1519
from amaranth_soc.memory import MemoryMap
1620
from amaranth.lib.wiring import In, Out, Component, Elaboratable, connect, \
1721
Signature, flipped
@@ -87,7 +91,7 @@ def elaborate(self, plat):
8791
return m
8892

8993

90-
class Leds(Component):
94+
class WBLeds(Component):
9195
@property
9296
def signature(self):
9397
return self._signature
@@ -145,7 +149,56 @@ def elaborate(self, plat):
145149
return m
146150

147151

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):
149202
@property
150203
def signature(self):
151204
return self._signature
@@ -186,6 +239,45 @@ def elaborate(self, plat):
186239
return m
187240

188241

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+
189281
# Taken from: https://github.com/amaranth-lang/amaranth/blob/f9da3c0d166dd2be189945dca5a94e781e74afeb/examples/basic/uart.py # noqa: E501
190282
class UART(Elaboratable):
191283
"""
@@ -301,6 +393,8 @@ def elaborate(self, plat):
301393
m = Module()
302394
m.submodules.ser_internal = self.serial
303395

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.
304398
rx_rdy_irq = Signal()
305399
rx_rdy_prev = Signal()
306400
tx_ack_irq = Signal()
@@ -351,17 +445,111 @@ def elaborate(self, plat):
351445
return m
352446

353447

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+
354527
# Reimplementation of nextpnr-ice40's AttoSoC example (https://github.com/YosysHQ/nextpnr/tree/master/ice40/smoketest/attosoc), # noqa: E501
355528
# to exercise attaching Sentinel to amaranth-soc's wishbone components.
356529
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):
358533
self.cpu = Top()
359534
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)
361537
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()
365553

366554
@property
367555
def rom(self):
@@ -384,13 +572,10 @@ def rom(self, source_or_list):
384572
def elaborate(self, plat):
385573
m = Module()
386574

387-
decoder = wishbone.Decoder(addr_width=30, data_width=32, granularity=8,
388-
alignment=25)
389-
390575
m.submodules.cpu = self.cpu
391576
m.submodules.mem = self.mem
392577
m.submodules.leds = self.leds
393-
m.submodules.decoder = decoder
578+
m.submodules.decoder = self.decoder
394579

395580
if plat:
396581
for i in range(8):
@@ -414,14 +599,38 @@ def elaborate(self, plat):
414599
gpio.o.eq(self.leds.gpio[i].o)
415600
]
416601

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)
422606

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:
425634
if plat:
426635
m.d.comb += [
427636
self.serial.rx.eq(ser.rx.i),
@@ -434,16 +643,20 @@ def destruct_res(res):
434643
return ("/".join(res.path), res.start, res.end, res.width)
435644

436645
print(tabulate(map(destruct_res,
437-
decoder.bus.memory_map.all_resources()),
646+
self.decoder.bus.memory_map.all_resources()),
438647
intfmt=("", "#010x", "#010x", ""),
439648
headers=["name", "start", "end", "width"]))
440-
connect(m, self.cpu.bus, decoder.bus)
649+
connect(m, self.cpu.bus, self.decoder.bus)
441650

442651
return m
443652

444653

445654
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)
447660

448661
if args.g:
449662
# In-line objcopy -O binary implementation. Probably does not handle
@@ -586,6 +799,9 @@ def main():
586799
parser.add_argument("-p", help="build platform",
587800
choices=("icestick", "ice40_hx8k_b_evn"),
588801
default="icestick")
802+
parser.add_argument("-i", help="peripheral interconnect type",
803+
choices=("wishbone", "csr"),
804+
default="wishbone")
589805
group = parser.add_mutually_exclusive_group()
590806
# Remote firmware override/random file generation is not supported;
591807
# Amaranth does not have provisions for supporting adding your own build

0 commit comments

Comments
 (0)