From 296206524cab8d4c45cf344af72d3d9af9e11a9a Mon Sep 17 00:00:00 2001 From: David Lenfesty Date: Mon, 23 Jan 2023 22:28:03 -0700 Subject: [PATCH] gateware: remove LiteX attempt --- gateware/led_gpio.py | 14 -- gateware/led_gpio.v | 80 --------- gateware/main.py | 399 +++++++++++++++++++++++++++++-------------- gateware/soc.py | 303 -------------------------------- 4 files changed, 268 insertions(+), 528 deletions(-) delete mode 100644 gateware/led_gpio.py delete mode 100644 gateware/led_gpio.v mode change 100755 => 100644 gateware/main.py delete mode 100644 gateware/soc.py diff --git a/gateware/led_gpio.py b/gateware/led_gpio.py deleted file mode 100644 index d839147..0000000 --- a/gateware/led_gpio.py +++ /dev/null @@ -1,14 +0,0 @@ -from migen import * - -from litex.soc.interconnect.csr import * - -from pathlib import Path - -class LedGpio(AutoCSR, Module): - def __init__(self, platform, led: Signal): - #source = Path(__file__).parent / "led_gpio.v" - #platform.add_source(source) - - self.register = CSRStorage(8) - - self.comb += led.eq(self.register.storage[0]) diff --git a/gateware/led_gpio.v b/gateware/led_gpio.v deleted file mode 100644 index 2a70e66..0000000 --- a/gateware/led_gpio.v +++ /dev/null @@ -1,80 +0,0 @@ -`default_nettype none - -module led_gpio #( - parameter DATA_WIDTH = 32, - parameter ADR_WIDTH = 32, - parameter SEL_WIDTH = 4 // ??? -) ( - // Main signals - input wire clk, - input wire rst, - - // verilator lint_off UNUSED - // ---- LiteX Wishbone interface - // Address - input wire [ADR_WIDTH-1:0] adr, - // Data input (write) - input wire [DATA_WIDTH-1:0] dat_w, - // Data output (read) - output reg [DATA_WIDTH-1:0] dat_r, - // What parts of data are valid? - input wire [SEL_WIDTH-1:0] sel, - // Start cycle - input wire cyc, - // Enables wishbone interface - input wire stb, - // Bus cycle finished - output reg ack, - // Is this cycle a read or write? - input wire we, - // Cycle type - input wire [2:0] cti, - // Burst type - input wire [1:0] bte, - // Asserted if cycle completes with error - output wire err, - // verilator lint_on UNUSED - - // Output - output wire led -); - -// Tracks if we have started a new wishbone cycle. This or similar is needed so we don't -// think we're handling multiple wishbone cycles inside one. -reg cycle_started; -reg led_state; - -always @(posedge clk) begin - // Always reset the cycle started - cycle_started <= 0; - - if (rst) begin - led_state <= 0; - end else begin - // TODO ADR checking - ack <= 0; - err <= 0; // We never have any bus errors - dat_r <= 0; - - if (!cycle_started && stb && cyc) begin - cycle_started <= 1; // Start cycle to be reset next clock - ack <= 1; // We always acknowledge immediately, we only take one clock to process - - if (we) begin - // Writes to LED, so we need to assign output - led_state <= dat_w[0]; - end else begin - // We want reads to get LED status - dat_r <= 3; - end - end - end -end - -always @(*) begin - led = !led_state; -end - -endmodule - -`default_nettype wire diff --git a/gateware/main.py b/gateware/main.py old mode 100755 new mode 100644 index 275a426..9933955 --- a/gateware/main.py +++ b/gateware/main.py @@ -1,166 +1,303 @@ #!/usr/bin/env python3 -# General imports -from migen import * +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 litex.build.io import DDROutput +from minerva.core import Minerva -from litex_boards.platforms import colorlight_i5 +from typing import List +from argparse import ArgumentParser -from litex.build.lattice.trellis import trellis_args, trellis_argdict +class Blinky(Elaboratable): + def __init__(self): + self.count = Signal(64) -from litex.soc.cores.clock import * -from litex.soc.integration.soc_core import * -from litex.soc.integration.builder import * + def elaborate(self, platform): + led = platform.request("led") -from litex.soc.interconnect.csr import * + m = Module() -from litex.soc.interconnect import wishbone -from litex.soc.integration.soc import SoCRegion + # 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) -from litedram.modules import M12L64322A -from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY + return m -from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII +# To change clock domain of a module: +# new_thing = DomainRenamer("new_clock")(MyElaboratable()) -# My hardware -import led_gpio +# 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() -# Module to configure clocks and resets -class _CRG(Module): - def __init__(self, platform, sys_clk_freq, use_internal_osc=False, with_usb_pll=False, with_video_pll=False, sdram_rate="1:1"): - self.rst = Signal() - self.clock_domains.cd_sys = ClockDomain() - if sdram_rate == "1:2": - self.clock_domains.cd_sys2x = ClockDomain() - self.clock_domains.cd_sys2x_ps = ClockDomain() - else: - self.clock_domains.cd_sys_ps = ClockDomain() + # 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)) - # Clk / Rst - if not use_internal_osc: - clk = platform.request("clk25") - clk_freq = 25e6 - else: - clk = Signal() - div = 5 - self.specials += Instance("OSCG", - p_DIV = div, - o_OSC = clk - ) - clk_freq = 310e6/div + self.memory_map = memory_map - rst_n = platform.request("cpu_reset_n") + # 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 - # PLL - self.submodules.pll = pll = ECP5PLL() - self.comb += pll.reset.eq(~rst_n | self.rst) - pll.register_clkin(clk, clk_freq) - pll.create_clkout(self.cd_sys, sys_clk_freq) - if sdram_rate == "1:2": - pll.create_clkout(self.cd_sys2x, 2*sys_clk_freq) - pll.create_clkout(self.cd_sys2x_ps, 2*sys_clk_freq, phase=180) # Idealy 90° but needs to be increased. - else: - pll.create_clkout(self.cd_sys_ps, sys_clk_freq, phase=180) # Idealy 90° but needs to be increased. + # '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 ) - # USB PLL - if with_usb_pll: - self.submodules.usb_pll = usb_pll = ECP5PLL() - self.comb += usb_pll.reset.eq(~rst_n | self.rst) - usb_pll.register_clkin(clk, clk_freq) - self.clock_domains.cd_usb_12 = ClockDomain() - self.clock_domains.cd_usb_48 = ClockDomain() - usb_pll.create_clkout(self.cd_usb_12, 12e6, margin=0) - usb_pll.create_clkout(self.cd_usb_48, 48e6, margin=0) + # 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 ) + ] - # Video PLL - if with_video_pll: - self.submodules.video_pll = video_pll = ECP5PLL() - self.comb += video_pll.reset.eq(~rst_n | self.rst) - video_pll.register_clkin(clk, clk_freq) - self.clock_domains.cd_hdmi = ClockDomain() - self.clock_domains.cd_hdmi5x = ClockDomain() - video_pll.create_clkout(self.cd_hdmi, 40e6, margin=0) - video_pll.create_clkout(self.cd_hdmi5x, 200e6, margin=0) + # End of simulated memory module. + return m - # SDRAM clock - sdram_clk = ClockSignal("sys2x_ps" if sdram_rate == "1:2" else "sys_ps") - self.specials += DDROutput(1, 0, platform.request("sdram_clock"), sdram_clk) +# 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 -# SoC definition - this basically instantiates hardware -class SoC(SoCCore): +# TODO clean this up, generate binary here, maybe even run cargo build +def load_firmware_for_mem() -> List[int]: + with open('../firmware/fw.bin', 'rb') as f: + # Stored as little endian, LSB first?? + data = f.read() + out = [] + assert(len(data) % 4 == 0) + for i in range(int(len(data) / 4)): + out.append(int.from_bytes(data[i*4:i*4+4], byteorder='little')) - csr_peripherals = ["led_gpio"] - #csr_map_update(SoCCore.csr_map, csr_peripherals) + return out - # While there are more configurations in what I'm basing this off of, I'm reducing it to - # one supported config. - def __init__(self, **kwargs): - platform = colorlight_i5.Platform(board="i9", revision = "7.2", toolchain="trellis") +class Core(Elaboratable): + def __init__(self, led_signal): + self.count = Signal(64) + self.cpu = Minerva(reset_address=0x01000000) + self.arbiter = Arbiter(addr_width=32, data_width=32) + self.decoder = Decoder(addr_width=32, data_width=32) + self.led_signal = led_signal - sys_clk_freq = 50e6 + def elaborate(self, platform): + m = Module() + m.submodules.cpu = self.cpu + m.submodules.arbiter = self.arbiter + m.submodules.decoder = self.decoder - self.submodules.crg = _CRG(platform, sys_clk_freq, sdram_rate="1:1") + # Connect ibus and dbus together for simplicity for now + minerva_wb_features = ["cti", "bte", "err"] + self.ibus = Interface(addr_width=32, data_width=32, features=minerva_wb_features) + # Note this is a set of statements! without assigning to the comb domain, this will do nothing + m.d.comb += self.cpu.ibus.connect(self.ibus) + self.arbiter.add(self.ibus) - # Initialize base SoC core stuff, with given system clock - SoCCore.__init__(self, platform, int(sys_clk_freq), ident = "Sonar SoC on Colorlight i9", **kwargs) + self.dbus = Interface(addr_width=32, data_width=32, features=minerva_wb_features) + m.d.comb += self.cpu.dbus.connect(self.dbus) # Don't use .eq, use .connect, which will appropriately assign signals + # using .eq() gave me Multiple Driven errors + self.arbiter.add(self.dbus) - # Set SPI flash with correct configuration - from litespi.modules import W25Q64 as SpiFlashModule - from litespi.opcodes import SpiNorFlashOpCodes as Codes - # 1x SPI interface (as opposed to QSPI or something), and use the simplest READ timing commands - self.add_spi_flash(mode="1x", module=SpiFlashModule(Codes.READ_1_1_1)) - - # Set up SDRAM - sdrphy_cls = GENSDRPHY - self.submodules.sdrphy = sdrphy_cls(platform.request("sdram")) - self.add_sdram("sdram", - phy = self.sdrphy, - module = M12L64322A(sys_clk_freq, "1:1"), - l2_cache_size = 8192, - ) - - # LED blinky thing - #wb_interface = wishbone.Interface() - led = platform.request("user_led_n") - self.submodules.led_gpio = led_gpio.LedGpio(platform, led) - #wb_interface.connect_to_pads(led, mode="slave") - - #self.add_memory_region("led_gpio", 0x8F000000, 0x1000, type="dawda") - #self.add_wb_slave(0x8F000000, wb_interface, 0x1000) - - # vague attempt based on - #region = SoCRegion(origin=0x8F000000, size=0x1000, cached=False) - #self.bus.add_slave(name="led_gpio", slave=wb_interface, region=region) - - # TODO ethernet + # TODO do something with interrupts + # These are interrupts headed into the CPU + self.interrupts = Signal(32) + m.d.comb += [ + # Used for mtime registers, which are memory-mapped (not CSR), so I would have to implement. + # If I cared. + self.cpu.timer_interrupt.eq(0), + # Ostensibly exposed to us so we can interrupt one hart (CPU in this context) from another, we don't need this. + self.cpu.software_interrupt.eq(0), + # External interrupt lines, would be for any interrupts I implemented w/ a custom interrupt controller. + # Most likely I'll map a few lines to some peripherals, won't do anything fancy + self.cpu.external_interrupt.eq(self.interrupts), + ] -def main(): - from litex.soc.integration.soc import LiteXSoCArgumentParser - parser = LiteXSoCArgumentParser(description="LiteX SoC for FPGA sonar") - target_group = parser.add_argument_group(title="Target options") - target_group.add_argument("--build", action="store_true", help="Build design") - target_group.add_argument("--load", action="store_true", help="Load design onto board") - builder_args(parser) - soc_core_args(parser) - trellis_args(parser) - args = parser.parse_args() + fw = load_firmware_for_mem() - soc = SoC(**soc_core_argdict(args)) + # Hook up memory space + self.rom = ROM(fw) + m.submodules.rom = self.rom + # Problem: not sure to handle how we do byte vs word addressing properly + # So doing this shift is a bit of a hacky way to impl anything + start, _stop, _step = self.decoder.add(self.rom, addr=(0x01000000 >> 2)) + print(f"ROM added at 0x{start:08x}") - builder = Builder(soc, **builder_argdict(args)) - builder_kargs = trellis_argdict(args) - if args.build: - builder.build(**builder_kargs) + self.ram = RAM() + m.submodules.ram = self.ram + start, _stop, _step = self.decoder.add(self.ram) + print(f"RAM added at 0x{start:08x}") - if args.load: - prog = soc.platform.create_programmer() - prog.load_bitstream(builder.get_bitstream_filename(mode="sram")) + self.led = LEDPeripheral(self.led_signal) + m.submodules.led = self.led + start, _stop, _step = self.decoder.add(self.led) + print(f"LED added at 0x{start:08x}") + + # Connect arbiter to decoder + m.d.comb += self.arbiter.bus.connect(self.decoder.bus) + + # 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 + +class SoC(Elaboratable): + def __init__(self): + pass + + def elaborate(self, platform): + m = Module() + + led_signal = platform.request("led") + core = Core(led_signal) + m.submodules.core = core + + 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() + sim = Simulator(dut) + + def proc(): + for i in range(10000): + yield Tick() + + sim.add_clock(1e-6) + sim.add_sync_process(proc) + with sim.write_vcd('test.vcd', gtkw_file='test.gtkw'): + sim.reset() + sim.run() if __name__ == "__main__": - main() + args = ArgumentParser(description="ARVP Sonar Acquisition FPGA gateware.") + args.add_argument("--build", action="store_true", help="Build bitstream.") + args.add_argument("--gen-debug-verilog", action="store_true", help="Save debug verilog.") + # TODO maybe allow an optional arg to specify an individual test to run? + args.add_argument("--test", action="store_true", help="Run RTL test suite and report results.") + args.add_argument("--save-vcd", action="store_true", help="Save VCD waveforms from test(s).") + args = args.parse_args() + + if args.build: + colorlight_i9.Colorlight_i9_Platform().build(SoC(), debug_verilog=args.gen_debug_verilog) + + if args.test: + # TODO pass save_vcd arg through + run_sim() diff --git a/gateware/soc.py b/gateware/soc.py deleted file mode 100644 index 9933955..0000000 --- a/gateware/soc.py +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/env python3 - -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 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]: - with open('../firmware/fw.bin', 'rb') as f: - # Stored as little endian, LSB first?? - data = f.read() - out = [] - assert(len(data) % 4 == 0) - for i in range(int(len(data) / 4)): - out.append(int.from_bytes(data[i*4:i*4+4], byteorder='little')) - - return out - -class Core(Elaboratable): - def __init__(self, led_signal): - self.count = Signal(64) - self.cpu = Minerva(reset_address=0x01000000) - self.arbiter = Arbiter(addr_width=32, data_width=32) - self.decoder = Decoder(addr_width=32, data_width=32) - self.led_signal = led_signal - - def elaborate(self, platform): - m = Module() - m.submodules.cpu = self.cpu - m.submodules.arbiter = self.arbiter - m.submodules.decoder = self.decoder - - # Connect ibus and dbus together for simplicity for now - minerva_wb_features = ["cti", "bte", "err"] - self.ibus = Interface(addr_width=32, data_width=32, features=minerva_wb_features) - # Note this is a set of statements! without assigning to the comb domain, this will do nothing - m.d.comb += self.cpu.ibus.connect(self.ibus) - self.arbiter.add(self.ibus) - - self.dbus = Interface(addr_width=32, data_width=32, features=minerva_wb_features) - m.d.comb += self.cpu.dbus.connect(self.dbus) # Don't use .eq, use .connect, which will appropriately assign signals - # using .eq() gave me Multiple Driven errors - self.arbiter.add(self.dbus) - - # TODO do something with interrupts - # These are interrupts headed into the CPU - self.interrupts = Signal(32) - m.d.comb += [ - # Used for mtime registers, which are memory-mapped (not CSR), so I would have to implement. - # If I cared. - self.cpu.timer_interrupt.eq(0), - # Ostensibly exposed to us so we can interrupt one hart (CPU in this context) from another, we don't need this. - self.cpu.software_interrupt.eq(0), - # External interrupt lines, would be for any interrupts I implemented w/ a custom interrupt controller. - # Most likely I'll map a few lines to some peripherals, won't do anything fancy - self.cpu.external_interrupt.eq(self.interrupts), - ] - - - fw = load_firmware_for_mem() - - # Hook up memory space - self.rom = ROM(fw) - m.submodules.rom = self.rom - # Problem: not sure to handle how we do byte vs word addressing properly - # So doing this shift is a bit of a hacky way to impl anything - start, _stop, _step = self.decoder.add(self.rom, addr=(0x01000000 >> 2)) - print(f"ROM added at 0x{start:08x}") - - self.ram = RAM() - m.submodules.ram = self.ram - start, _stop, _step = self.decoder.add(self.ram) - print(f"RAM added at 0x{start:08x}") - - self.led = LEDPeripheral(self.led_signal) - m.submodules.led = self.led - start, _stop, _step = self.decoder.add(self.led) - print(f"LED added at 0x{start:08x}") - - # Connect arbiter to decoder - m.d.comb += self.arbiter.bus.connect(self.decoder.bus) - - # 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 - -class SoC(Elaboratable): - def __init__(self): - pass - - def elaborate(self, platform): - m = Module() - - led_signal = platform.request("led") - core = Core(led_signal) - m.submodules.core = core - - 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() - sim = Simulator(dut) - - def proc(): - for i in range(10000): - yield Tick() - - sim.add_clock(1e-6) - sim.add_sync_process(proc) - with sim.write_vcd('test.vcd', gtkw_file='test.gtkw'): - sim.reset() - sim.run() - - -if __name__ == "__main__": - args = ArgumentParser(description="ARVP Sonar Acquisition FPGA gateware.") - args.add_argument("--build", action="store_true", help="Build bitstream.") - args.add_argument("--gen-debug-verilog", action="store_true", help="Save debug verilog.") - # TODO maybe allow an optional arg to specify an individual test to run? - args.add_argument("--test", action="store_true", help="Run RTL test suite and report results.") - args.add_argument("--save-vcd", action="store_true", help="Save VCD waveforms from test(s).") - args = args.parse_args() - - if args.build: - colorlight_i9.Colorlight_i9_Platform().build(SoC(), debug_verilog=args.gen_debug_verilog) - - if args.test: - # TODO pass save_vcd arg through - run_sim()