# diff between linen-org/linien commit ID 93f1f50: # https://github.com/linien-org/linien/commit/93f1f50ebd86fe3314cab5a549462d0fcbf6a658 # and elhep/linien commit ID b73eea0: # https://github.com/elhep/linien/commit/b73eea07889dda8b55f0cf4c2afde96cf4c3efd1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b3f3683..98c6e51 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: 23.11.0 hooks: - id: black - exclude: ^(gateware/logic/|gateware/lowlevel/|gateware/linien_module.py|linien-server/linien_server/csrmap.py) + exclude: ^(gateware/logic/|gateware/lowlevel/|gateware/linien_module.py|linien-server/linien_server/csrmap.py|gateware/targets) - repo: https://github.com/pycqa/isort rev: 5.12.0 diff --git a/gateware/build_fpga_image.sh b/gateware/build_fpga_image.sh index f822402..be7401c 100644 --- a/gateware/build_fpga_image.sh +++ b/gateware/build_fpga_image.sh @@ -16,4 +16,9 @@ export PATH=$VIVADOPATH:$PATH rm linien-server/linien_server/gateware.bin -f # run with -m option to avoid errors related to relative imports without breaking pytest -python3 -m gateware.fpga_image_helper \ No newline at end of file + +if [ -z "$1" ]; then + python3 -m gateware.fpga_image_helper +else + python3 -m gateware.fpga_image_helper -p $1 +fi \ No newline at end of file diff --git a/gateware/fpga_image_helper.py b/gateware/fpga_image_helper.py index 6c34429..a0b12d0 100644 --- a/gateware/fpga_image_helper.py +++ b/gateware/fpga_image_helper.py @@ -1,5 +1,6 @@ # Copyright 2014-2015 Robert Jördens # Copyright 2018-2022 Benjamin Wiegand +# Copyright 2023 Jakub Matyas # # This file is part of Linien and based on redpid. # @@ -23,14 +24,16 @@ from pathlib import Path REPO_ROOT_DIR = Path(__file__).resolve().parents[1] from .bit2bin import bit2bin -from .hw_platform import Platform -from .linien_module import RootModule def py_csrconstants(map, fil): fil.write("csr_constants = {\n") - for k, v in root.linien.csrbanks.constants: - fil.write(" '{}_{}': {},\n".format(k, v.name, v.value.value)) + for k, v in root.csrbanks.constants: + if k == "linien": + # compaitbility layer + fil.write(" '{}': {},\n".format(v.name, v.value.value)) + else: + fil.write(" '{}_{}': {},\n".format(k, v.name, v.value.value)) fil.write("}\n\n") @@ -52,26 +55,49 @@ def get_csrmap(banks): def py_csrmap(it, fil): fil.write("csr = {\n") for reg in it: - fil.write(" '{}_{}': ({}, 0x{:03x}, {}, {}),\n".format(*reg)) + main_name = reg[0] + secondary_name = reg[1] + # compaitbility layer + if main_name == "linien" or secondary_name.startswith(main_name): + fil.write(" '{}': ({}, 0x{:03x}, {}, {}),\n".format(*reg[1:])) + else: + fil.write(" '{}_{}': ({}, 0x{:03x}, {}, {}),\n".format(*reg)) fil.write("}\n") if __name__ == "__main__": - platform = Platform() - root = RootModule(platform) + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("-p", "--platform", default=None) + args = parser.parse_args() + if args.platform is None or args.platform.lower() == "redpitaya": + from gateware.hw_platform import Platform + from gateware.targets.red_pitaya import PitayaSoC + + platform = Platform() + root = PitayaSoC(platform) + elif args.platform.lower() == "fastservo": + from fast_servo.gateware.fast_servo_platform import Platform + + from gateware.targets.fast_servo import LinienFastServo + + platform = Platform() + root = LinienFastServo(platform) + else: + raise ValueError("Unknown platform") + + platform.add_source_dir(REPO_ROOT_DIR / "gateware" / "verilog") + build_dir = REPO_ROOT_DIR / "gateware" / "build" + platform.build(root, build_name="top", build_dir=build_dir, run=True) with open( REPO_ROOT_DIR / "linien-server" / "linien_server" / "csrmap.py", "w" ) as fil: - py_csrconstants(root.linien.csrbanks.constants, fil) - csr = get_csrmap(root.linien.csrbanks.banks) + py_csrconstants(root.csrbanks.constants, fil) + csr = get_csrmap([*root.csrbanks.banks, *root.linien.csrbanks.banks]) py_csrmap(csr, fil) fil.write("states = {}\n".format(repr(root.linien.state_names))) fil.write("signals = {}\n".format(repr(root.linien.signal_names))) - - platform.add_source_dir(REPO_ROOT_DIR / "gateware" / "verilog") - build_dir = REPO_ROOT_DIR / "gateware" / "build" - platform.build(root, build_name="top", build_dir=build_dir) bit2bin( build_dir / "top.bit", REPO_ROOT_DIR / "linien-server" / "linien_server" / "gateware.bin", diff --git a/gateware/linien_module.py b/gateware/linien_module.py index 16ca186..6905ac0 100644 --- a/gateware/linien_module.py +++ b/gateware/linien_module.py @@ -2,6 +2,7 @@ # Copyright 2018-2022 Benjamin Wiegand # Copyright 2021-2023 Bastian Leykauf # Copyright 2022 Christian Freier +# Copyright 2023 Jakub Matyas # # This file is part of Linien and based on redpid. # @@ -36,19 +37,13 @@ from misoc.interconnect.csr import AutoCSR, CSRStatus, CSRStorage from .logic.autolock import FPGAAutolock from .logic.chains import FastChain, SlowChain, cross_connect from .logic.decimation import Decimate -from .logic.delta_sigma import DeltaSigma from .logic.iir import Iir from .logic.limit import LimitCSR from .logic.modulate import Modulate from .logic.pid import PID from .logic.sweep import SweepCSR -from .lowlevel.analog import PitayaAnalog -from .lowlevel.crg import CRG -from .lowlevel.dna import DNA -from .lowlevel.gpio import Gpio -from .lowlevel.pitaya_ps import PitayaPS, Sys2CSR, SysCDC, SysInterconnect +from .lowlevel.pitaya_ps import Sys2CSR from .lowlevel.scopegen import ScopeGen -from .lowlevel.xadc import XADC class LinienLogic(Module, AutoCSR): @@ -156,45 +151,24 @@ class LinienLogic(Module, AutoCSR): class LinienModule(Module, AutoCSR): - def __init__(self, platform): + def __init__(self, soc): width = 14 signal_width = 25 coeff_width = 25 chain_factor_bits = 8 self.init_submodules( - width, signal_width, coeff_width, chain_factor_bits, platform + width, signal_width, coeff_width, chain_factor_bits, soc ) - self.connect_everything(width, signal_width, coeff_width, chain_factor_bits) + self.connect_everything(width, signal_width, coeff_width, chain_factor_bits, soc) def init_submodules( - self, width, signal_width, coeff_width, chain_factor_bits, platform + self, width, signal_width, coeff_width, chain_factor_bits, soc ): - sys_double = ClockDomainsRenamer("sys_double") self.submodules.logic = LinienLogic( coeff_width=coeff_width, chain_factor_width=chain_factor_bits ) - self.submodules.analog = PitayaAnalog( - platform.request("adc"), platform.request("dac") - ) - self.submodules.xadc = XADC(platform.request("xadc")) - - for i in range(4): - pwm = platform.request("pwm", i) - ds = sys_double(DeltaSigma(width=15)) - self.comb += pwm.eq(ds.out) - setattr(self.submodules, f"ds{i}", ds) - - exp = platform.request("exp") - self.submodules.gpio_n = Gpio(exp.n) - self.submodules.gpio_p = Gpio(exp.p) - - leds = Cat(*(platform.request("user_led", i) for i in range(8))) - self.comb += leds.eq(self.gpio_n.o) - - self.submodules.dna = DNA(version=2) - self.submodules.fast_a = FastChain( width, signal_width, @@ -210,18 +184,22 @@ class LinienModule(Module, AutoCSR): offset_signal=self.logic.chain_b_offset_signed, ) + # FIXME: does it do anything?! _ = ClockDomainsRenamer("sys_slow") sys_double = ClockDomainsRenamer("sys_double") max_decimation = 16 self.submodules.decimate = sys_double(Decimate(max_decimation)) self.clock_domains.cd_decimated_clock = ClockDomain() decimated_clock = ClockDomainsRenamer("decimated_clock") + # TODO: No support for slow Analog Out on Fast Servo self.submodules.slow_chain = decimated_clock(SlowChain()) - self.submodules.scopegen = ScopeGen(signal_width) + soc.add_interconnect_slave(self.scopegen.scope_sys) + soc.add_interconnect_slave(self.scopegen.asg_sys) + self.state_names, self.signal_names = cross_connect( - self.gpio_n, + soc.gpio_n, [ ("fast_a", self.fast_a), ("fast_b", self.fast_b), @@ -233,10 +211,6 @@ class LinienModule(Module, AutoCSR): ) csr_map = { - "dna": 28, - "xadc": 29, - "gpio_n": 30, - "gpio_p": 31, "fast_a": 0, "fast_b": 1, "slow_chain": 2, @@ -251,19 +225,13 @@ class LinienModule(Module, AutoCSR): name if mem is None else name + "_" + mem.name_override ], ) - self.submodules.sys2csr = Sys2CSR() - self.submodules.csrcon = csr_bus.Interconnect( - self.sys2csr.csr, self.csrbanks.get_buses() - ) - self.submodules.syscdc = SysCDC() - self.comb += self.syscdc.target.connect(self.sys2csr.sys) - def connect_everything(self, width, signal_width, coeff_width, chain_factor_bits): + def connect_everything(self, width, signal_width, coeff_width, chain_factor_bits, soc): s = signal_width - width self.comb += [ - self.fast_a.adc.eq(self.analog.adc_a), - self.fast_b.adc.eq(self.analog.adc_b), + self.fast_a.adc.eq(soc.analog.adc_a), + self.fast_b.adc.eq(soc.analog.adc_b), ] # now, we combine the output of the two paths, with a variable factor each. @@ -297,7 +265,7 @@ class LinienModule(Module, AutoCSR): self.comb += [ If( self.logic.pid_only_mode.storage, - self.logic.pid.input.eq(self.analog.adc_a << s), + self.logic.pid.input.eq(soc.analog.adc_a << s), ).Else( self.logic.pid.input.eq(mixed_limited), ), @@ -347,40 +315,42 @@ class LinienModule(Module, AutoCSR): # ANALOG OUTPUTS --------------------------------------------------------------- # ANALOG OUT 0 gets a special treatment because it may contain signal of slow # pid or sweep - analog_out = Signal((width + 3, True)) - self.comb += [ - analog_out.eq( - Mux( - self.logic.sweep_channel.storage == OutputChannel.ANALOG_OUT0, - self.logic.sweep.y, - 0, - ) - + Mux( - self.logic.sweep_channel.storage == OutputChannel.ANALOG_OUT0, - self.logic.out_offset_signed, - 0, - ) - + Mux( - self.logic.slow_control_channel.storage + + if soc.soc_name == "RedPitaya": + analog_out = Signal((width + 3, True)) + self.comb += [ + analog_out.eq( + Mux( + self.logic.sweep_channel.storage == OutputChannel.ANALOG_OUT0, + self.logic.sweep.y, + 0, + ) + + Mux( + self.logic.sweep_channel.storage == OutputChannel.ANALOG_OUT0, + self.logic.out_offset_signed, + 0, + ) + + Mux( + self.logic.slow_control_channel.storage == OutputChannel.ANALOG_OUT0, self.slow_chain.output, - 0, - ) - ), - ] - # NOTE: not sure why limit is used - self.comb += self.slow_chain.limit.x.eq(analog_out) - # ds0 apparently has 16 bit, but only allowing positive values --> "15 bit"? - slow_out_shifted = Signal(15) - self.sync += slow_out_shifted.eq((self.slow_chain.limit.y << 1) + (1 << 14)) - self.comb += self.ds0.data.eq(slow_out_shifted) - - # connect other analog outputs - self.comb += [ - self.ds1.data.eq(self.logic.analog_out_1.storage), - self.ds2.data.eq(self.logic.analog_out_2.storage), - self.ds3.data.eq(self.logic.analog_out_3.storage), - ] + 0, + ) + ), + ] + # NOTE: not sure why limit is used + self.comb += self.slow_chain.limit.x.eq(analog_out) + # ds0 apparently has 16 bit, but only allowing positive values --> "15 bit"? + slow_out_shifted = Signal(15) + self.sync += slow_out_shifted.eq((self.slow_chain.limit.y << 1) + (1 << 14)) + self.comb += soc.ds0.data.eq(slow_out_shifted) + + # connect other analog outputs + self.comb += [ + soc.ds1.data.eq(self.logic.analog_out_1.storage), + soc.ds2.data.eq(self.logic.analog_out_2.storage), + soc.ds3.data.eq(self.logic.analog_out_3.storage), + ] # ------------------------------------------------------------------------------ @@ -395,7 +365,7 @@ class LinienModule(Module, AutoCSR): self.comb += [ self.logic.autolock.robust.at_start.eq(self.logic.sweep.sweep.trigger), - self.scopegen.gpio_trigger.eq(self.gpio_p.i[0]), + self.scopegen.gpio_trigger.eq(soc.gpio_p.i[0]), self.scopegen.sweep_trigger.eq(self.logic.sweep.sweep.trigger), self.scopegen.automatically_rearm.eq( self.logic.autolock.request_lock.storage @@ -404,8 +374,8 @@ class LinienModule(Module, AutoCSR): self.scopegen.automatically_trigger.eq( self.logic.autolock.lock_running.status ), - self.analog.dac_a.eq(self.logic.limit_fast1.y), - self.analog.dac_b.eq(self.logic.limit_fast2.y), + soc.analog.dac_a.eq(self.logic.limit_fast1.y), + soc.analog.dac_b.eq(self.logic.limit_fast2.y), ] # Having this in a comb statement caused errors. See PR #251. @@ -428,23 +398,4 @@ class DummyHK(Module, AutoCSR): self.submodules.csrcon = csr_bus.Interconnect( self.sys2csr.csr, self.csrbanks.get_buses() ) - self.sys = self.sys2csr.sys - - -class RootModule(Module): - def __init__(self, platform): - self.submodules.ps = PitayaPS(platform.request("cpu")) - self.submodules.crg = CRG( - platform.request("clk125"), self.ps.fclk[0], ~self.ps.frstn[0] - ) - self.submodules.linien = LinienModule(platform) - - self.submodules.hk = ClockDomainsRenamer("sys_ps")(DummyHK()) - - self.submodules.ic = SysInterconnect( - self.ps.axi.sys, - self.hk.sys, - self.linien.scopegen.scope_sys, - self.linien.scopegen.asg_sys, - self.linien.syscdc.source, - ) + self.sys = self.sys2csr.sys \ No newline at end of file diff --git a/gateware/targets/__init__.py b/gateware/targets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gateware/targets/fast_servo.py b/gateware/targets/fast_servo.py new file mode 100644 index 0000000..da5bf3b --- /dev/null +++ b/gateware/targets/fast_servo.py @@ -0,0 +1,85 @@ +# Copyright 2023 Jakub Matyas +# Warsaw University of Technology +# +# This file is part of Linien and provides support for Linien on +# Fast Servo platform. +# +# Linien is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Linien is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Linien. If not, see . + +from fast_servo.gateware.fast_servo_soc import BaseSoC +from migen import * +from misoc.interconnect import csr_bus + +from gateware.linien_module import DummyHK, LinienModule +from gateware.lowlevel.dna import DNA +from gateware.lowlevel.gpio import Gpio +from gateware.lowlevel.pitaya_ps import SysInterconnect + + +class FastServoAnalog(Module): + def __init__(self, adc, dac): + size = 14 # length of DAC + + self.adc_a = Signal(size) + self.adc_b = Signal(size) + self.dac_a = Signal(size) + self.dac_b = Signal(size) + + self.comb += [ + self.adc_a.eq(adc.data_out[0][2:]), + self.adc_b.eq(adc.data_out[1][2:]), + ] + + self.sync += [ + dac.data_in[0].eq(self.dac_a), + dac.data_in[1].eq(self.dac_b), + ] + + +class LinienFastServo(BaseSoC): + def __init__(self, platform): + super().__init__(platform) + + self.submodules.dna = DNA(version=2) + + self.submodules.analog = FastServoAnalog(self.adc, self.dac) + gpios = platform.request("gpio") + self.submodules.gpio_n = Gpio(gpios.n) + # self.csr_devices.append("gpio_n") + self.submodules.gpio_p = Gpio(gpios.p) + # self.csr_devices.append("gpio_p") + self.csr_map.update({ + "dna": 28, + "gpio_n": 30, + "gpio_p": 31, + }) + + # --------------------------------------------- + # + # FIXME - passing self to LinienModule + self.submodules.linien = LinienModule(self) + + def soc_finalize(self): + self.add_interconnect_slave(self.syscdc.source) + self.submodules.csrbanks = csr_bus.CSRBankArray(self, + self.get_csr_dev_address) + self.submodules.csrcon = csr_bus.Interconnect( + self.sys2csr.csr, [*self.csrbanks.get_buses(), *self.linien.csrbanks.get_buses()] + ) + self.submodules.hk = DummyHK() + self.submodules.interconnect = SysInterconnect( + self.axi2sys.sys, + self.hk.sys, + *self.interconnect_slaves + ) \ No newline at end of file diff --git a/gateware/targets/red_pitaya.py b/gateware/targets/red_pitaya.py new file mode 100644 index 0000000..c029e81 --- /dev/null +++ b/gateware/targets/red_pitaya.py @@ -0,0 +1,103 @@ +# Copyright 2014-2015 Robert Jördens +# Copyright 2018-2022 Benjamin Wiegand +# Copyright 2021-2023 Bastian Leykauf +# Copyright 2022 Christian Freier +# Copyright 2023 Jakub Matyas +# +# This file is part of Linien and based on redpid. +# +# Linien is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Linien is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Linien. If not, see . + +from migen import * +from misoc.interconnect import csr_bus +from misoc.interconnect.csr import AutoCSR + +from gateware.linien_module import DummyHK, LinienModule +from gateware.logic.delta_sigma import DeltaSigma +from gateware.lowlevel.analog import PitayaAnalog +from gateware.lowlevel.crg import CRG +from gateware.lowlevel.dna import DNA +from gateware.lowlevel.gpio import Gpio +from gateware.lowlevel.pitaya_ps import PitayaPS, Sys2CSR, SysCDC, SysInterconnect +from gateware.lowlevel.xadc import XADC + + +class PitayaSoC(Module, AutoCSR): + def __init__(self, platform): + self.csr_map = { + "gpio_n": 30, + "gpio_p": 31, + "dna": 28, + "xadc": 29, + } + self.soc_name = "RedPitaya" + self.interconnect_slaves = [] + + self.submodules.ps = PitayaPS(platform.request("cpu")) + self.submodules.crg = CRG( + platform.request("clk125"), self.ps.fclk[0], ~self.ps.frstn[0] + ) + self.submodules.sys2csr = Sys2CSR() + self.submodules.syscdc = SysCDC() + self.comb += self.syscdc.target.connect(self.sys2csr.sys) + + self.submodules.xadc = XADC(platform.request("xadc")) + self.submodules.analog = PitayaAnalog(platform.request("adc"), platform.request("dac")) + + for i in range(4): + pwm = platform.request("pwm", i) + ds = ClockDomainsRenamer("sys_double")(DeltaSigma(width=15)) + self.comb += pwm.eq(ds.out) + setattr(self.submodules, f"ds{i}", ds) + + exp = platform.request("exp") + self.submodules.gpio_n = Gpio(exp.n) + self.submodules.gpio_p = Gpio(exp.p) + + leds = Cat(*(platform.request("user_led", i) for i in range(8))) + self.comb += leds.eq(self.gpio_n.o) + + self.submodules.dna = DNA(version=2) + + # --------------------------------------------- + # + # FIXME - passing self to LinienModule + self.submodules.linien = LinienModule(self) + self.add_interconnect_slave(self.syscdc.source) + self.run_finalize() + + + def add_interconnect_slave(self, slave): + self.interconnect_slaves.append(slave) + + def get_csr_dev_address(self, name, memory): + if memory is not None: + name = name + "_" + memory.name_override + try: + return self.csr_map[name] + except KeyError: + return None + + def run_finalize(self): + self.submodules.csrbanks = csr_bus.CSRBankArray(self, + self.get_csr_dev_address) + self.submodules.csrcon = csr_bus.Interconnect( + self.sys2csr.csr, [*self.csrbanks.get_buses(), *self.linien.csrbanks.get_buses()] + ) + self.submodules.hk = DummyHK() + self.submodules.interconnect = SysInterconnect( + self.ps.axi.sys, + self.hk.sys, + *self.interconnect_slaves + ) \ No newline at end of file