gateware: remove LiteX attempt
This commit is contained in:
parent
db431e7ba7
commit
296206524c
@ -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])
|
@ -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
|
399
gateware/main.py
Executable file → Normal file
399
gateware/main.py
Executable file → Normal file
@ -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()
|
||||
|
303
gateware/soc.py
303
gateware/soc.py
@ -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()
|
Loading…
Reference in New Issue
Block a user