diff --git a/gateware/led.py b/gateware/led.py new file mode 100644 index 0000000..c930c6d --- /dev/null +++ b/gateware/led.py @@ -0,0 +1,34 @@ +from amaranth import * +from amaranth_soc.wishbone import * + + +class LEDPeripheral(Elaboratable, Interface): + def __init__(self, led_signal): + Interface.__init__(self, addr_width=1, data_width=32) + memory_map = MemoryMap(addr_width=1, data_width=32) + #memory_map.add_resource("my_led", name="led_peripheral", size=1) + self.memory_map = memory_map + + self.led = led_signal + + def elaborate(self, platform): + m = Module() + + storage = Signal(1) + + # Always update read values (both wishbone and the LED outpu) + m.d.comb += [ + self.dat_r[0].eq(storage), + self.led.eq(storage), + ] + + m.d.sync += self.ack.eq(0) # default to no ack + with m.If(self.cyc & self.stb): + # single cycle ack when CYC and STB are asserted + m.d.sync += self.ack.eq(1) + + # Write to our storage register if the value has changed + with m.If(self.we): + m.d.sync += storage.eq(self.dat_w[0]) + + return m diff --git a/gateware/main.py b/gateware/main.py index 9933955..7672d95 100644 --- a/gateware/main.py +++ b/gateware/main.py @@ -3,160 +3,17 @@ from amaranth import * from amaranth.sim import * from amaranth_boards import colorlight_i9 -from amaranth_soc.wishbone import Interface, Arbiter, Decoder -from amaranth_soc.memory import MemoryMap +from amaranth_soc.wishbone import * from minerva.core import Minerva from typing import List from argparse import ArgumentParser -class Blinky(Elaboratable): - def __init__(self): - self.count = Signal(64) - - def elaborate(self, platform): - led = platform.request("led") - - m = Module() - - # Counter - m.d.sync += self.count.eq(self.count + 1) - with m.If(self.count >= 50000000): - m.d.sync += self.count.eq(0) - m.d.sync += led.eq(~led) - - return m # To change clock domain of a module: # new_thing = DomainRenamer("new_clock")(MyElaboratable()) -# We sub-class wishbone.Interface here because it needs to be a bus object to be added as a window to Wishbone stuff -class ROM(Elaboratable, Interface): - def __init__(self, data=None): - #self.size = len(data) - self.data = Memory(width=32, depth=(4096 >> 2), init=data) - self.r = self.data.read_port() - - # Need to init Interface - Interface.__init__(self, addr_width=10, data_width=32) - - # This is effectively a "window", and it has a certain set of resources - # 12 = log2(4096) - memory_map = MemoryMap(addr_width=10, data_width=32) - # TODO need to unify how I deal with size - # In this case, one resource, which is out memory - memory_map.add_resource(self.data, name="rom_data", size=(4096 >> 2)) - - self.memory_map = memory_map - - # Connects memory port signals to wishbone interface - def elaborate(self, platform): - # Stolen from https://vivonomicon.com/2020/04/14/learning-fpga-design-with-nmigen/ - m = Module() - # Register the read port submodule. - m.submodules.r = self.r - - # 'ack' signal should rest at 0. - m.d.sync += self.ack.eq( 0 ) - # Simulated reads only take one cycle, but only acknowledge - # them after 'cyc' and 'stb' are asserted. - with m.If( self.cyc ): - m.d.sync += self.ack.eq( self.stb ) - - # Set 'dat_r' bus signal to the value in the - # requested 'data' array index. - m.d.comb += [ - self.r.addr.eq( self.adr ), - self.dat_r.eq( self.r.data ) - ] - - # End of simulated memory module. - return m - -# TODO support read segmentation or whatever it's called, where you read/write certian bytes from memory -# Otherwise we can't store individual bytes, and this will wreck shit in weird ways. -class RAM(Elaboratable, Interface): - def __init__(self): - #self.size = len(data) - self.data = Memory(width=32, depth=(4096 >> 2)) - self.r = self.data.read_port() - self.w = self.data.write_port() - - # Need to init Interface - Interface.__init__(self, addr_width=10, data_width=32) - - # This is effectively a "window", and it has a certain set of resources - # 12 = log2(4096) - memory_map = MemoryMap(addr_width=10, data_width=32) - # TODO need to unify how I deal with size - # In this case, one resource, which is out memory - memory_map.add_resource(self.data, name="ram_data", size=(4096 >> 2)) - - self.memory_map = memory_map - - # Connects memory port signals to wishbone interface - def elaborate(self, platform): - # Stolen from https://vivonomicon.com/2020/04/14/learning-fpga-design-with-nmigen/ - m = Module() - # Register the read port submodule. - m.submodules.r = self.r - m.submodules.w = self.w - - # 'ack' signal should rest at 0. - m.d.sync += self.ack.eq(0) - # Simulated reads only take one cycle, but only acknowledge - # them after 'cyc' and 'stb' are asserted. - with m.If( self.cyc & self.stb): - m.d.sync += self.ack.eq(1) - - # Write to address if we are writing - with m.If(self.we): - m.d.sync += self.w.en.eq(1) - - # Set 'dat_r' bus signal to the value in the - # requested 'data' array index. - m.d.comb += [ - self.r.addr.eq( self.adr ), - self.dat_r.eq( self.r.data ), - self.w.addr.eq(self.adr), - self.w.data.eq(self.dat_w), - ] - - # End of simulated memory module. - return m - -class LEDPeripheral(Elaboratable, Interface): - def __init__(self, led_signal): - Interface.__init__(self, addr_width=1, data_width=32) - memory_map = MemoryMap(addr_width=1, data_width=32) - #memory_map.add_resource("my_led", name="led_peripheral", size=1) - self.memory_map = memory_map - - self.led = led_signal - - def elaborate(self, platform): - m = Module() - - storage = Signal(1) - - # Always update read values (both wishbone and the LED outpu) - m.d.comb += [ - self.dat_r[0].eq(storage), - self.led.eq(storage), - ] - - m.d.sync += self.ack.eq(0) # default to no ack - with m.If(self.cyc & self.stb): - # single cycle ack when CYC and STB are asserted - m.d.sync += self.ack.eq(1) - - # Write to our storage register if the value has changed - with m.If(self.we): - m.d.sync += storage.eq(self.dat_w[0]) - - return m - # TODO clean this up, generate binary here, maybe even run cargo build def load_firmware_for_mem() -> List[int]: @@ -247,32 +104,18 @@ class SoC(Elaboratable): pass def elaborate(self, platform): - m = Module() + if platform is not None: + led_signal = platform.request("led") + else: + # platform is None in simulation, so provide harnesses for required signals + led_signal = Signal() - led_signal = platform.request("led") - core = Core(led_signal) - m.submodules.core = core + return Core(led_signal) - return m - - -# TODO add more harnessing -class TestDevice(Elaboratable): - def __init__(self): - pass - - - def elaborate(self, platform): - m = Module() - led_signal = Signal() - core = Core(led_signal) - m.submodules.core = core - - return m # TODO add structure to add regression tests def run_sim(): - dut = TestDevice() + dut = SoC() sim = Simulator(dut) def proc(): diff --git a/gateware/memory.py b/gateware/memory.py new file mode 100644 index 0000000..fb98d28 --- /dev/null +++ b/gateware/memory.py @@ -0,0 +1,100 @@ +from amaranth import * +from amaranth_soc.wishbone import * +from amaranth_soc.memory import * + + +# We sub-class wishbone.Interface here because it needs to be a bus object to be added as a window to Wishbone stuff +class ROM(Elaboratable, Interface): + def __init__(self, data=None): + #self.size = len(data) + self.data = Memory(width=32, depth=(4096 >> 2), init=data) + self.r = self.data.read_port() + + # Need to init Interface + Interface.__init__(self, addr_width=10, data_width=32) + + # This is effectively a "window", and it has a certain set of resources + # 12 = log2(4096) + memory_map = MemoryMap(addr_width=10, data_width=32) + # TODO need to unify how I deal with size + # In this case, one resource, which is out memory + memory_map.add_resource(self.data, name="rom_data", size=(4096 >> 2)) + + self.memory_map = memory_map + + # Connects memory port signals to wishbone interface + def elaborate(self, platform): + # Stolen from https://vivonomicon.com/2020/04/14/learning-fpga-design-with-nmigen/ + m = Module() + # Register the read port submodule. + m.submodules.r = self.r + + # 'ack' signal should rest at 0. + m.d.sync += self.ack.eq( 0 ) + # Simulated reads only take one cycle, but only acknowledge + # them after 'cyc' and 'stb' are asserted. + with m.If( self.cyc ): + m.d.sync += self.ack.eq( self.stb ) + + # Set 'dat_r' bus signal to the value in the + # requested 'data' array index. + m.d.comb += [ + self.r.addr.eq( self.adr ), + self.dat_r.eq( self.r.data ) + ] + + # End of simulated memory module. + return m + + +# TODO support read segmentation or whatever it's called, where you read/write certian bytes from memory +# Otherwise we can't store individual bytes, and this will wreck shit in weird ways. +class RAM(Elaboratable, Interface): + def __init__(self): + #self.size = len(data) + self.data = Memory(width=32, depth=(4096 >> 2)) + self.r = self.data.read_port() + self.w = self.data.write_port() + + # Need to init Interface + Interface.__init__(self, addr_width=10, data_width=32) + + # This is effectively a "window", and it has a certain set of resources + # 12 = log2(4096) + memory_map = MemoryMap(addr_width=10, data_width=32) + # TODO need to unify how I deal with size + # In this case, one resource, which is out memory + memory_map.add_resource(self.data, name="ram_data", size=(4096 >> 2)) + + self.memory_map = memory_map + + # Connects memory port signals to wishbone interface + def elaborate(self, platform): + # Stolen from https://vivonomicon.com/2020/04/14/learning-fpga-design-with-nmigen/ + m = Module() + # Register the read port submodule. + m.submodules.r = self.r + m.submodules.w = self.w + + # 'ack' signal should rest at 0. + m.d.sync += self.ack.eq(0) + # Simulated reads only take one cycle, but only acknowledge + # them after 'cyc' and 'stb' are asserted. + with m.If( self.cyc & self.stb): + m.d.sync += self.ack.eq(1) + + # Write to address if we are writing + with m.If(self.we): + m.d.sync += self.w.en.eq(1) + + # Set 'dat_r' bus signal to the value in the + # requested 'data' array index. + m.d.comb += [ + self.r.addr.eq( self.adr ), + self.dat_r.eq( self.r.data ), + self.w.addr.eq(self.adr), + self.w.data.eq(self.dat_w), + ] + + # End of simulated memory module. + return m