diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e27b6a23d..52d3f4f0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,6 @@ jobs: - name: Setup CCache uses: hendrikmuhs/ccache-action@v1.2 - - uses: ghdl/setup-ghdl-ci@master - with: - backend: llvm - # Install Tools - name: Install Tools run: | @@ -47,6 +43,16 @@ jobs: sudo python3 litex_setup.py --gcc=openrisc sudo python3 litex_setup.py --gcc=powerpc + # Build / Install GHDL + - name: Build GHDL + run: | + sudo apt-get install gnat llvm + git clone https://github.com/ghdl/ghdl.git + cd ghdl + ./configure --with-llvm-config + make + sudo make install + # Build / Install Verilator - name: Build Verilator run: | diff --git a/CHANGES.md b/CHANGES.md index 413a92d40..e2c6f4bf2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,41 @@ -[> Changes since 2023.12 +[> Changes since 2024.04 ------------------------ + [> Fixed + -------- + - cpu/zynq7000 : Fixed AXI version to AXI3. + - build/vhd2v_converter : Fixed instance replace robustness. + - tools/litex_json2renode : Corrected VexRiscv variants (#1984). + - software/liblitespi : Fixed xor-used-pow bug (#2001). + - soc : Fixed AHB2Wishbone bridge creation (#1998). + - soc : Fixed parameters propagation for AXI data-width conversion (#1997). + + [> Added + -------- + - cpu/vexiiriscv : Added initial support (#1923). + - builder : Added default generation of exports with default names to output_dir (#1978). + - litex.gen : Added byte size definitions and use them in targets/json2dts. + - litepcie : Added external QPLL support/sharing for Xilinx Artix7. + - cores/zynq7000/mp : Improved integration, added peripherals supports (#1994). + - software/bios : Generalized IRQ handling approach between CPUs. + - cores/video : Added fifo_depth parameter to add_video_framebuffer (#1931). + - gen/common : Added byte size definitions (KILOBYTE, MEGABYTE, GIGABYTE). + - tools/litex_json2dts_linux : Simplified CPU architecture/RISC-V ISA. + - soc : Added add_spi_master method (#1985). + - tools/litex_json2dts_zephyr : Added spimaster/spiflash handlers (#1985). + - tools/litex_json2renode : Added .elf bios option (#1984). + - cores : Added Watchdog core and Zephyr support (#1996). + - soc : Added add_spi_ram method (#2028). + - build : Added initial Apicula (Gowin) Platform support (#2036). + - build : Added initial Agilex5 support. + - liteeth/mac : Improved broadcast filtering logic in Hybrid Mode (https://github.com/enjoy-digital/liteeth/pull/165). + + [> Changed + ---------- + - integration/builder : Changed export behavior to now generate csr.csv and csr.json by default to output_dir. + - csr_bus : Added .re signal (#1999). + +[> 2024.04, released on June 5th 2024 +------------------------------------- [> Fixed -------- - integration/soc : Fixed typo in cpu mem_bus axi-via-wb downconvert @@ -9,6 +45,10 @@ - litespi/software: : Fixed SPI Flash Clk Divider computation when with L2 Cache. - litepcie/us(p)pciephy : Fixed x8 / 256-bit wide case. - litex_sim/serial2console : Fixed RX backpressure handling. + - litedram/frontend/avalon : Fixed and cleaned-up. + - litex_sim/video : Fixed pixel format to RGBA. + - build/xilinx/common : Fixed missing clk parameter on XilinxSDRTristateImpl. + - soc/interconnect : Fixed CSR/LiteXModule issue on WishboneSRAM/AXILiteSRAM. [> Added -------- @@ -27,6 +67,22 @@ - litex_sim : Added jtagremote support. - soc/add_master : Added region support to allow/limit access to a specific region. - litex_json2dts_linux : Added ip= bootarg when local/remote ips are defined. + - cores/jtag : Added JTAGBone support for Zynq. + - cores/ram/lattice_nx : Improved timings. + - liteeth_gen : Added QPLL/BUFH/BUFG parameters for A7 1000BaseX PHY. + - litex_sim : Added Video Color Bar support. + - cpu/neorv32 : Updated to v1.9.7. + - cores/hyperbus : Added latency configuration and variable latency support. + - cpu/cv32e41p : Added ISR support. + - litesdcard : Improved SDPHYClocker (Timings). + - cpu/vexriscv_smp : Added baremetal IRQ support. + - cpu/naxriscv : Added baremetal IRQ support. + - cpu/zynqmp : Added Ethernet, UART, I2C support and improved AXI Master. + - build/efinix : Added reconfiguration interface support. + - build/efinix : Added tx_output_load configuration support. + - cpu/eos_s3 : Updated qlal4s3b_cell_macro clock and reset signals. + - build/quicklogic : Updated f4pga Makefile. + - build/microsemi : Updated libero_soc toolchain. [> Changed ---------- diff --git a/README.md b/README.md index becbbe4de..05cb4a61e 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ The framework is also far from perfect and we'll be happy to have your [feedback Have fun! :wink: -**Moral precisions**: The project is shared with a permissive BSD 2-Clause License and we are encouraged to continue sharing it this way thanks to the awesome community and clients willing to support the project! -If the projet is useful for your research, hobby or commercial projects, we are just asking you to be coherent and behave with integrity: Don't expect free support or that the community will be welcoming if your spent your time complaining about the project (and then direspect developers) or don't pay the custom developments you asked for... (While it's probably natural for 99% of users/clients, it does seems useful to add this for the 1% remaining that are eating lots of our energy/time). +We share this project under a permissive BSD 2-Clause License, inspired by our fantastic community and supportive clients. If LiteX benefits your research, hobby, or commercial projects, we kindly ask for your positive collaboration and respect for the effort involved. +Thank you for helping us improve LiteX and being part of our community! # Typical LiteX design flow: ``` diff --git a/litex/__init__.py b/litex/__init__.py index 5e2790c06..41b48dadf 100644 --- a/litex/__init__.py +++ b/litex/__init__.py @@ -19,4 +19,4 @@ pythondata-{dt}-{dn} module not installed! Unable to use {dn} {dt}. You can install this by running; pip3 install git+https://github.com/litex-hub/pythondata-{dt}-{dn}.git -""".format(dt=data_type, dn=data_name, e=e)) +""".format(dt=data_type, dn=data_name, e=e)) from None diff --git a/litex/build/altera/common.py b/litex/build/altera/common.py index 3ed0947ba..d4a2c3c77 100644 --- a/litex/build/altera/common.py +++ b/litex/build/altera/common.py @@ -99,7 +99,6 @@ class AlteraDDROutputImpl(Module): o_dataout = o, ) - class AlteraDDROutput: @staticmethod def lower(dr): @@ -147,3 +146,103 @@ altera_special_overrides = { SDROutput: AlteraSDROutput, SDRInput: AlteraSDRInput, } + +# Agilex5 DDROutput -------------------------------------------------------------------------------- + +class Agilex5DDROutputImpl(Module): + def __init__(self, i1, i2, o, clk): + self.specials += Instance("tennm_ph2_ddio_out", + p_mode = "MODE_DDR", + p_asclr_ena = "ASCLR_ENA_NONE", + p_sclr_ena = "SCLR_ENA_NONE", + o_dataout = o, + i_datainlo = i2, + i_datainhi = i1, + i_clk = clk, + i_ena = Constant(1, 1), + i_areset = Constant(1, 1), + i_sreset = Constant(1, 1), + ) + +class Agilex5DDROutput: + @staticmethod + def lower(dr): + return Agilex5DDROutputImpl(dr.i1, dr.i2, dr.o, dr.clk) + +# Agilex5 DDRInput --------------------------------------------------------------------------------- + +class Agilex5DDRInputImpl(Module): + def __init__(self, i, o1, o2, clk): + self.specials += Instance("tennm_ph2_ddio_in", + p_mode = "MODE_DDR", + p_asclr_ena = "ASCLR_ENA_NONE", + p_sclr_ena = "SCLR_ENA_NONE", + i_clk = clk, + i_datain = i, + o_regouthi = o1, + o_regoutlo = o2, + i_ena = Constant(1, 1), + i_areset = Constant(1, 1), + i_sreset = Constant(1, 1), + ) + +class Agilex5DDRInput: + @staticmethod + def lower(dr): + return Agilex5DDRInputImpl(dr.i, dr.o1, dr.o2, dr.clk) + +# Agilex5 SDROutput -------------------------------------------------------------------------------- + +class Agilex5SDROutput: + @staticmethod + def lower(dr): + return Agilex5DDROutputImpl(dr.i, dr.i, dr.o, dr.clk) + +# Agilex5 SDRInput --------------------------------------------------------------------------------- + +class Agilex5SDRInput: + @staticmethod + def lower(dr): + return Agilex5DDRInputImpl(dr.i, dr.o, Signal(), dr.clk) + +# Agilex5 SDRTristate ------------------------------------------------------------------------------ + +class Agilex5SDRTristateImpl(Module): + def __init__(self, io, o, oe, i, clk): + _i = Signal() + _o = Signal() + _oe = Signal() + self.specials += [ + SDRIO(o, _o, clk), + SDRIO(oe, _oe, clk), + SDRIO(_i, i, clk), + Instance("tennm_ph2_io_ibuf", + p_bus_hold = "BUS_HOLD_OFF", + io_i = io, # FIXME: its an input but io is needed to have correct dir at top module + o_o = _i, + ), + Instance("tennm_ph2_io_obuf", + p_open_drain = "OPEN_DRAIN_OFF", + i_i = _o, + i_oe = _oe, + io_o = io, # FIXME: its an output but io is needed to have correct dir at top module + ), + ] + +class Agilex5SDRTristate(Module): + @staticmethod + def lower(dr): + return Agilex5SDRTristateImpl(dr.io, dr.o, dr.oe, dr.i, dr.clk) + +# Agilex5 Special Overrides ------------------------------------------------------------------------ + +agilex5_special_overrides = { + AsyncResetSynchronizer: AlteraAsyncResetSynchronizer, + DifferentialInput: AlteraDifferentialInput, + DifferentialOutput: AlteraDifferentialOutput, + DDROutput: Agilex5DDROutput, + DDRInput: Agilex5DDRInput, + SDROutput: Agilex5SDROutput, + SDRInput: Agilex5SDRInput, + SDRTristate: Agilex5SDRTristate, +} diff --git a/litex/build/altera/platform.py b/litex/build/altera/platform.py index bfcbba829..f7b3b0a8e 100644 --- a/litex/build/altera/platform.py +++ b/litex/build/altera/platform.py @@ -34,6 +34,8 @@ class AlteraPlatform(GenericPlatform): def get_verilog(self, *args, special_overrides=dict(), **kwargs): so = dict(common.altera_special_overrides) + if self.device[:3] == "A5E": + so.update(common.agilex5_special_overrides) so.update(special_overrides) return GenericPlatform.get_verilog(self, *args, special_overrides = so, @@ -62,3 +64,11 @@ class AlteraPlatform(GenericPlatform): for pad in common.altera_reserved_jtag_pads: r[pad] = self.request(pad) return r + + @classmethod + def fill_args(cls, toolchain, parser): + quartus.fill_args(parser) + + @classmethod + def get_argdict(cls, toolchain, args): + return quartus.get_argdict(args) diff --git a/litex/build/altera/quartus.py b/litex/build/altera/quartus.py index f85f3a4c0..5be426c29 100644 --- a/litex/build/altera/quartus.py +++ b/litex/build/altera/quartus.py @@ -28,10 +28,19 @@ class AlteraQuartusToolchain(GenericToolchain): def __init__(self): super().__init__() + self._synth_tool = "quartus_map" self.additional_sdc_commands = [] self.additional_qsf_commands = [] self.cst = [] + def build(self, platform, fragment, + synth_tool = "quartus_map", + **kwargs): + + self._synth_tool = synth_tool + + return GenericToolchain.build(self, platform, fragment, **kwargs) + # IO/Placement Constraints (.qsf) -------------------------------------------------------------- def _format_constraint(self, c, signame, fmt_r): @@ -143,7 +152,8 @@ class AlteraQuartusToolchain(GenericToolchain): # Add IPs for filename in self.platform.ips: - qsf.append("set_global_assignment -name QSYS_FILE " + filename.replace("\\", "/")) + file_ext = os.path.splitext(filename)[1][1:].upper() + qsf.append(f"set_global_assignment -name {file_ext}_FILE " + filename.replace("\\", "/")) # Add include paths for path in self.platform.verilog_include_paths: @@ -178,7 +188,7 @@ class AlteraQuartusToolchain(GenericToolchain): script_contents += "# Autogenerated by LiteX / git: " + tools.get_litex_git_revision() + "\n" script_contents += "set -e -u -x -o pipefail\n" script_contents += """ -quartus_map --read_settings_files=on --write_settings_files=off {build_name} -c {build_name} +{synth_tool} --read_settings_files=on --write_settings_files=off {build_name} -c {build_name} quartus_fit --read_settings_files=off --write_settings_files=off {build_name} -c {build_name} quartus_asm --read_settings_files=off --write_settings_files=off {build_name} -c {build_name} quartus_sta {build_name} -c {build_name}""" @@ -196,7 +206,7 @@ then quartus_cpf -c {build_name}.sof {build_name}.rbf fi """ - script_contents = script_contents.format(build_name=build_name) + script_contents = script_contents.format(build_name=build_name, synth_tool=self._synth_tool) tools.write_to_file(script_file, script_contents, force_unix=True) return script_file @@ -207,10 +217,19 @@ fi else: shell = ["bash"] - if which("quartus_map") is None: + if which(self._synth_tool) is None: msg = "Unable to find Quartus toolchain, please:\n" msg += "- Add Quartus toolchain to your $PATH." raise OSError(msg) if subprocess.call(shell + [script]) != 0: raise OSError("Error occured during Quartus's script execution.") + +def fill_args(parser): + toolchain_group = parser.add_argument_group(title="Quartus toolchain options") + toolchain_group.add_argument("--synth-tool", default="quartus_map", help="Synthesis mode (quartus_map or quartus_syn).") + +def get_argdict(args): + return { + "synth_tool" : args.synth_tool, + } diff --git a/litex/build/efinix/common.py b/litex/build/efinix/common.py index 0197e2911..c52db5ee9 100644 --- a/litex/build/efinix/common.py +++ b/litex/build/efinix/common.py @@ -162,21 +162,21 @@ class EfinixSDRTristate(Module): class EfinixDifferentialOutputImpl(Module): def __init__(self, platform, i, o_p, o_n): - # only keep _p + # only keep _p io_name = platform.get_pin_name(o_p) io_pad = platform.get_pad_name(o_p) # need real pad name io_prop = platform.get_pin_properties(o_p) if platform.family == "Titanium": # _p has _P_ and _n has _N_ followed by an optional function - # lvds block needs _PN_ + # lvds block needs _PN_ pad_split = io_pad.split('_') assert pad_split[1] == 'P' io_pad = f"{pad_split[0]}_PN_{pad_split[2]}" else: assert "TXP" in io_pad # diff output pins are TXPYY and TXNYY - # lvds block needs TXYY + # lvds block needs TXYY io_pad = io_pad.replace("TXP", "TX") platform.add_extension([(io_name, 0, Pins(1))]) @@ -206,21 +206,21 @@ class EfinixDifferentialOutput: class EfinixDifferentialInputImpl(Module): def __init__(self, platform, i_p, i_n, o): - # only keep _p + # only keep _p io_name = platform.get_pin_name(i_p) io_pad = platform.get_pad_name(i_p) # need real pad name io_prop = platform.get_pin_properties(i_p) if platform.family == "Titanium": # _p has _P_ and _n has _N_ followed by an optional function - # lvds block needs _PN_ + # lvds block needs _PN_ pad_split = io_pad.split('_') assert pad_split[1] == 'P' io_pad = f"{pad_split[0]}_PN_{pad_split[2]}" else: assert "RXP" in io_pad # diff input pins are RXPYY and RXNYY - # lvds block needs RXYY + # lvds block needs RXYY io_pad = io_pad.replace("RXP", "RX") platform.add_extension([ diff --git a/litex/build/efinix/dbparser.py b/litex/build/efinix/dbparser.py index 411acc4bf..223fcd2ad 100644 --- a/litex/build/efinix/dbparser.py +++ b/litex/build/efinix/dbparser.py @@ -107,9 +107,9 @@ class EfinixDbParser: peri = root.findall('efxpt:periphery_instance', namespaces) for p in peri: # T20/T120 have instance attribute in single_conn - # not true for T4/T8 -> search in dependency subnode + # not true for T4/T8 (except for TQFP144 package) -> search in dependency subnode if p.get('block') == 'pll': - if self.device[0:2] not in ['T4', 'T8']: + if self.device[0:2] not in ['T4', 'T8'] or self.device[0:6] == "T8Q144": conn = p.findall('efxpt:single_conn', namespaces) for c in conn: i = c.get('instance') diff --git a/litex/build/efinix/efinity.py b/litex/build/efinix/efinity.py index 2e0b79963..0f782ac6b 100644 --- a/litex/build/efinix/efinity.py +++ b/litex/build/efinix/efinity.py @@ -50,7 +50,23 @@ class EfinityToolchain(GenericToolchain): if self.platform.verilog_include_paths: self.options["includ_path"] = "{" + ";".join(self.platform.verilog_include_paths) + "}" - def build(self, platform, fragment, **kwargs): + def build(self, platform, fragment, + synth_mode = "speed", + infer_clk_enable = "3", + bram_output_regs_packing = "1", + retiming = "1", + seq_opt = "1", + mult_input_regs_packing = "1", + mult_output_regs_packing = "1", + **kwargs): + + self._synth_mode = synth_mode + self._infer_clk_enable = infer_clk_enable + self._bram_output_regs_packing = bram_output_regs_packing + self._retiming = retiming + self._seq_opt = seq_opt + self._mult_input_regs_packing = mult_input_regs_packing + self._mult_output_regs_packing = mult_output_regs_packing # Apply FullMemoryWE on Design (Efiniy does not infer memories correctly otherwise). FullMemoryWE()(fragment) @@ -270,11 +286,11 @@ class EfinityToolchain(GenericToolchain): # Some IO blocks don't have Python API so we need to configure them # directly in the peri.xml file - # We also need to configure the bank voltage here + # We also need to configure the bank voltage here if self.ifacewriter.xml_blocks or self.platform.iobank_info: self.ifacewriter.generate_xml_blocks() - # Because the Python API is sometimes bugged, we need to tweak the generated xml + # Because the Python API is sometimes bugged, we need to tweak the generated xml if self.ifacewriter.fix_xml: self.ifacewriter.fix_xml_values() @@ -295,18 +311,18 @@ class EfinityToolchain(GenericToolchain): "--binary-db", f"{self._build_name}.vdb", "--family", self.platform.family, "--device", self.platform.device, - "--mode", "speed", + "--mode", self._synth_mode, "--max_ram", "-1", "--max_mult", "-1", - "--infer-clk-enable", "3", + "--infer-clk-enable", self._infer_clk_enable, "--infer-sync-set-reset", "1", "--fanout-limit", "0", - "--bram_output_regs_packing", "1", - "--retiming", "1", - "--seq_opt", "1", + "--bram_output_regs_packing", self._bram_output_regs_packing, + "--retiming", self._retiming, + "--seq_opt", self._seq_opt, "--blast_const_operand_adders", "1", - "--mult_input_regs_packing", "1", - "--mult_output_regs_packing", "1", + "--mult_input_regs_packing", self._mult_input_regs_packing, + "--mult_output_regs_packing", self._mult_output_regs_packing, "--veri_option", "verilog_mode=verilog_2k,vhdl_mode=vhdl_2008", "--work-dir", "work_syn", "--output-dir", "outflow", @@ -377,3 +393,33 @@ class EfinityToolchain(GenericToolchain): ], common.colors) if r != 0: raise OSError("Error occurred during export_bitstream execution.") + +def build_args(parser): + toolchain = parser.add_argument_group(title="Efinity toolchain options") + toolchain.add_argument("--synth-mode", default="speed", help="Synthesis optimization mode.", + choices=["speed", "area", "area2"]) + toolchain.add_argument("--infer-clk-enable", default="3", help="infers the flip-flop clock enable signal.", + choices=["0", "1", "2", "3", "4",]) + toolchain.add_argument("--infer-sync-set-reset", default="1", help="Infer synchronous set/reset signals.", + choices=["0", "1"]) + toolchain.add_argument("--bram-output-regs-packing", default="1", help="Pack registers into the output of BRAM.", + choices=["0", "1"]) + toolchain.add_argument("--retiming", default="1", help="Perform retiming optimization.", + choices=["0", "1", "2"]) + toolchain.add_argument("--seq-opt", default="1", help="Turn on sequential optimization.", + choices=["0", "1"]) + toolchain.add_argument("--mult-input-regs-packing", default="1", help="Allow packing of multiplier input registers.", + choices=["0", "1"]), + toolchain.add_argument("--mult-output-regs-packing", default="1", help="Allow packing of multiplier output registers.", + choices=["0", "1"]) + +def build_argdict(args): + return { + "synth_mode" : args.synth_mode, + "infer_clk_enable" : args.infer_clk_enable, + "bram_output_regs_packing" : args.bram_output_regs_packing, + "retiming" : args.retiming, + "seq_opt" : args.seq_opt, + "mult_input_regs_packing" : args.mult_input_regs_packing, + "mult_output_regs_packing" : args.mult_output_regs_packing, + } diff --git a/litex/build/efinix/ifacewriter.py b/litex/build/efinix/ifacewriter.py index 5ec2a3b88..95cd66c8a 100644 --- a/litex/build/efinix/ifacewriter.py +++ b/litex/build/efinix/ifacewriter.py @@ -289,7 +289,7 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) elif block["input_clock"] == "EXTERNAL": # PLL V1 has a different configuration - if partnumber[0:2] in ["T4", "T8"]: + if partnumber[0:2] in ["T4", "T8"] and partnumber != "T8Q144": cmd += 'design.gen_pll_ref_clock("{}", pll_res="{}", refclk_res="{}", refclk_name="{}", ext_refclk_no="{}")\n\n' \ .format(name, block["resource"], block["input_clock_pad"], block["input_clock_name"], block["clock_no"]) else: @@ -329,7 +329,7 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) cmd += 'design.set_property("{}", "FEEDBACK_MODE", "{}", "PLL")\n'.format(name, "LOCAL" if feedback_clk < 1 else "CORE") cmd += 'design.set_property("{}", "FEEDBACK_CLK", "CLK{}", "PLL")\n'.format(name, 0 if feedback_clk < 1 else feedback_clk) - # auto_calc_pll_clock is always working with Titanium and only working when feedback is unused for Trion + # auto_calc_pll_clock is always working with Titanium and only working when feedback is unused for Trion if block["feedback"] == -1 or block["version"] == "V3": cmd += "target_freq = {\n" for i, clock in enumerate(block["clk_out"]): @@ -425,6 +425,7 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) fast_clk = block.get("fast_clk", "") slow_clk = block.get("slow_clk", "") half_rate= block.get("half_rate", "0") + tx_output_load=block.get("output_load", "3") if mode == "OUTPUT": block_type = "LVDS_TX" @@ -443,7 +444,7 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) cmd.append('design.set_property("{}", "TX_PRE_EMP", "MEDIUM_LOW", "{}")'.format(name, block_type)) cmd.append('design.set_property("{}", "TX_VOD", "TYPICAL", "{}")'.format(name, block_type)) else: - cmd.append('design.set_property("{}", "TX_OUTPUT_LOAD", "3", "{}")'.format(name, block_type)) + cmd.append('design.set_property("{}", "TX_OUTPUT_LOAD", "{}", "{}")'.format(name, tx_output_load, block_type)) cmd.append('design.set_property("{}", "TX_REDUCED_SWING", "0", "{}")'.format(name, block_type)) cmd.append('design.set_property("{}", "TX_SLOWCLK_DIV", "1", "{}")'.format(name, block_type)) cmd.append('design.set_property("{}", "TX_SER", "{}", "{}")'.format(name, size, block_type)) @@ -602,6 +603,31 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) return '\n'.join(cmd) + '\n' + def generate_remote_update(self, block, verbose=True): + name = block["name"] + pins = block["pins"] + clock = block["clock"] + invert_clk = block["invert_clock"] + enable = block["enable"] + + def get_pin_name(pin): + return pin.backtrace[-1][0] + + cmds = [] + cmds.append(f"# ---------- REMOTE UPDATE ---------") + cmds.append(f'design.set_device_property("ru", "RECONFIG_EN", "{enable}", "RU")') + if enable: + cmds.append(f'design.set_device_property("ru", "CBSEL_PIN", "{get_pin_name(pins.CBSEL)}", "RU")') + cmds.append(f'design.set_device_property("ru", "CLK_PIN", "{clock}", "RU")') + cmds.append(f'design.set_device_property("ru", "CONFIG_PIN", "{get_pin_name(pins.CONFIG)}", "RU")') + cmds.append(f'design.set_device_property("ru", "ENA_PIN", "{get_pin_name(pins.ENA)}", "RU")') + cmds.append(f'design.set_device_property("ru", "ERROR_PIN", "{get_pin_name(pins.ERROR)}", "RU")') + if hasattr(pins, 'IN_USER'): + cmds.append(f'design.set_device_property("ru", "IN_USER_PIN", "{get_pin_name(pins.IN_USER)}", "RU")') + cmds.append(f'design.set_device_property("ru", "INVERT_CLK_EN", "{invert_clk}", "RU")') + cmds.append(f"# ---------- END REMOTE UPDATE ---------\n") + return "\n".join(cmds) + def generate(self, partnumber): output = "" for block in self.blocks: @@ -624,6 +650,8 @@ design.create("{2}", "{3}", "./../gateware", overwrite=True) output += self.generate_jtag(block) if block["type"] == "SPI_FLASH": output += self.generate_spiflash(block) + if block["type"] == "REMOTE_UPDATE": + output += self.generate_remote_update(block) return output def footer(self): diff --git a/litex/build/efinix/platform.py b/litex/build/efinix/platform.py index 52d1fc4cc..9808369ef 100644 --- a/litex/build/efinix/platform.py +++ b/litex/build/efinix/platform.py @@ -125,26 +125,24 @@ class EfinixPlatform(GenericPlatform): sig = sig.value return sig - def get_pin_name(self, sig, without_index=False): + def get_pin_name(self, sig): if sig is None: return None assert len(sig) == 1 idx = 0 slc = False while isinstance(sig, _Slice) and hasattr(sig, "value"): - slc = True idx = sig.start sig = sig.value + slc = hasattr(sig, "nbits") and sig.nbits > 1 sc = self.constraint_manager.get_sig_constraints() for s, pins, others, resource in sc: if s == sig: + name = resource[0] + (f"{resource[1]}" if resource[1] is not None else "") if resource[2]: - name = resource[0] + "_" + resource[2] - if without_index is False: - name = name + (f"{idx}" if slc else "") - return name - else: - return resource[0] + (f"{idx}" if slc else "") + name = name + "_" + resource[2] + name = name + (f"{idx}" if slc else "") + return name return None def get_pad_name(self, sig): @@ -187,3 +185,34 @@ class EfinixPlatform(GenericPlatform): pll = self.pll_available[0] self.get_pll_resource(pll) return pll + + @classmethod + def fill_args(cls, toolchain, parser): + """ + pass parser to the specific toolchain to + fill this with toolchain args + + Parameters + ========== + toolchain: str + toolchain name + parser: argparse.ArgumentParser + parser to be filled + """ + efinity.build_args(parser) + + @classmethod + def get_argdict(cls, toolchain, args): + """ + return a dict of args + + Parameters + ========== + toolchain: str + toolchain name + + Return + ====== + a dict of key/value for each args or an empty dict + """ + return efinity.build_argdict(args) diff --git a/litex/build/gowin/__init__.py b/litex/build/gowin/__init__.py index e69de29bb..5af6e2b0b 100644 --- a/litex/build/gowin/__init__.py +++ b/litex/build/gowin/__init__.py @@ -0,0 +1,5 @@ +# Platforms. +from litex.build.gowin.platform import GowinPlatform + +# Programmers. +from litex.build.gowin.programmer import GowinProgrammer diff --git a/litex/build/gowin/apicula.py b/litex/build/gowin/apicula.py new file mode 100644 index 000000000..fa802b6c4 --- /dev/null +++ b/litex/build/gowin/apicula.py @@ -0,0 +1,54 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2024 Mai Lapyst +# SPDX-License-Identifier: BSD-2-Clause + +from litex.build.generic_platform import * +from litex.build import tools +from litex.build.gowin.gowin import _build_cst +from litex.build.yosys_nextpnr_toolchain import YosysNextPNRToolchain + +class GowinApiculaToolchain(YosysNextPNRToolchain): + family = "gowin" + synth_fmt = "json" + pnr_fmt = "report" + packer_cmd = "gowin_pack" + + def __init__(self): + super().__init__() + self.options = {} + self.additional_cst_commands = [] + + def build_io_constraints(self): + _build_cst(self.named_sc, self.named_pc, self.additional_cst_commands, self._build_name) + return (self._build_name + ".cst", "CST") + + def finalize(self): + pnr_opts = "--write {top}_routed.json --top {top} --device {device}" + \ + " --vopt family={devicename} --vopt cst={top}.cst" + self._pnr_opts += pnr_opts.format( + top = self._build_name, + device = self.platform.device, + devicename = self.platform.devicename + ) + + self._packer_opts += "-d {devicename} -o {top}.fs {top}_routed.json".format( + devicename = self.platform.devicename, + top = self._build_name + ) + + # use_mspi_as_gpio and friends + for option, value in self.options.items(): + if option.startswith("use_") and value: + self._packer_opts += " --" + option[4:] + + YosysNextPNRToolchain.finalize(self) + + # family is gowin but NextPNRWrapper needs to call 'nextpnr-himbaechel' not 'nextpnr-gowin' + self._nextpnr.name = "nextpnr-himbaechel" + + def build(self, platform, fragment, **kwargs): + self.platform = platform + + return YosysNextPNRToolchain.build(self, platform, fragment, **kwargs) diff --git a/litex/build/gowin/common.py b/litex/build/gowin/common.py index 1869385af..6dd7ff081 100644 --- a/litex/build/gowin/common.py +++ b/litex/build/gowin/common.py @@ -100,9 +100,19 @@ class GowinDifferentialOutput: def lower(dr): return GowinDifferentialOutputImpl(dr.i, dr.o_p, dr.o_n) -# Gowin Tristate ----------------------------------------------------------------------------------- +# Gowin Special Overrides -------------------------------------------------------------------------- -class GowinTristateImpl(Module): +gowin_special_overrides = { + AsyncResetSynchronizer: GowinAsyncResetSynchronizer, + DDRInput: GowinDDRInput, + DDROutput: GowinDDROutput, + DifferentialInput: GowinDifferentialInput, + DifferentialOutput: GowinDifferentialOutput, +} + +# Gw5A Tristate ------------------------------------------------------------------------------------ + +class Gw5ATristateImpl(Module): def __init__(self, io, o, oe, i): nbits, _ = value_bits_sign(io) for bit in range(nbits): @@ -113,18 +123,74 @@ class GowinTristateImpl(Module): i_OEN = ~oe, ) -class GowinTristate: +class Gw5ATristate: @staticmethod def lower(dr): - return GowinTristateImpl(dr.target, dr.o, dr.oe, dr.i) + return Gw5ATristateImpl(dr.target, dr.o, dr.oe, dr.i) -# Gowin Special Overrides -------------------------------------------------------------------------- +# Gw5A SDROutput ----------------------------------------------------------------------------------- -gowin_special_overrides = { - AsyncResetSynchronizer: GowinAsyncResetSynchronizer, - DDRInput: GowinDDRInput, - DDROutput: GowinDDROutput, - DifferentialInput: GowinDifferentialInput, - DifferentialOutput: GowinDifferentialOutput, - #Tristate: GowinTristate, # FIXME: issue with tangNano9k hyperram +class Gw5ASDROutputImpl(Module): + def __init__(self, i, o, clk): + self.specials += Instance("DFFSE", + i_D = i, + o_Q = o, + i_CLK = clk, + i_SET = Constant(0,1), + i_CE = Constant(1,1), + ) + +class Gw5ASDROutput: + @staticmethod + def lower(dr): + return Gw5ASDROutputImpl(dr.i, dr.o, dr.clk) + +# Gw5A SDRInput ------------------------------------------------------------------------------------ + +class Gw5ASDRInputImpl(Module): + def __init__(self, i, o, clk): + self.specials += Instance("DFFSE", + i_D = i, + o_Q = o, + i_CLK = clk, + i_SET = Constant(0,1), + i_CE = Constant(1,1), + ) + +class Gw5ASDRInput: + @staticmethod + def lower(dr): + return Gw5ASDRInputImpl(dr.i, dr.o, dr.clk) + +# Gw5A SDRTristate --------------------------------------------------------------------------------- + +class Gw5ASDRTristateImpl(Module): + def __init__(self, io, o, oe, i, clk): + _o = Signal() + _oe_n = Signal() + _i = Signal() + self.specials += [ + SDROutput(o, _o, clk), + SDROutput(~oe, _oe_n, clk), + SDRInput(_i, i, clk), + Instance("IOBUF", + io_IO = io, + o_O = _i, + i_I = _o, + i_OEN = _oe_n, + ), + ] + +class Gw5ASDRTristate: + @staticmethod + def lower(dr): + return Gw5ASDRTristateImpl(dr.io, dr.o, dr.oe, dr.i, dr.clk) + +# Gw5A Special Overrides --------------------------------------------------------------------------- + +gw5a_special_overrides = { + SDRTristate: Gw5ASDRTristate, + SDROutput: Gw5ASDROutput, + SDRInput: Gw5ASDRInput, + Tristate: Gw5ATristate, } diff --git a/litex/build/gowin/gowin.py b/litex/build/gowin/gowin.py index 7129db38e..51d423fe0 100644 --- a/litex/build/gowin/gowin.py +++ b/litex/build/gowin/gowin.py @@ -17,6 +17,61 @@ from litex.build.generic_toolchain import GenericToolchain from litex.build.generic_platform import * from litex.build import tools +# Constraints (.cst) ------------------------------------------------------------------------------- + +def _build_cst(named_sc, named_pc, additional_cst_commands, build_name): + cst = [] + + flat_sc = [] + for name, pins, other, resource in named_sc: + if len(pins) > 1: + for i, p in enumerate(pins): + flat_sc.append((f"{name}[{i}]", p, other)) + else: + flat_sc.append((name, pins[0], other)) + + def _search_pin_entry(pin_lst, pin_name): + for name, pin, other in pin_lst: + if pin_name == name: + return (name, pin, other) + return (None, None, None) + + for name, pin, other in flat_sc: + if pin != "X": + t_name = name.split('[') # avoid index pins + tmp_name = t_name[0] + if tmp_name[-2:] == "_p": + pn = tmp_name[:-2] + "_n" + if len(t_name) > 1: + pn += '[' + t_name[1] + (_, n_pin, _) = _search_pin_entry(flat_sc, pn) + if n_pin is not None: + pin = f"{pin},{n_pin}" + elif tmp_name[-2:] == "_n": + pp = tmp_name[:-2] + "_p" + if len(t_name) > 1: + pp += '[' + t_name[1] + (p_name, _, _) = _search_pin_entry(flat_sc, pp) + if p_name is not None: + continue + cst.append(f"IO_LOC \"{name}\" {pin};") + + other_cst = [] + for c in other: + if isinstance(c, IOStandard): + other_cst.append(f"IO_TYPE={c.name}") + elif isinstance(c, Misc): + other_cst.append(f"{c.misc}") + if len(other_cst): + t = " ".join(other_cst) + cst.append(f"IO_PORT \"{name}\" {t};") + + if named_pc: + cst.extend(named_pc) + + cst.extend(additional_cst_commands) + + tools.write_to_file(build_name + ".cst", "\n".join(cst)) # GowinToolchain ----------------------------------------------------------------------------------- @@ -60,58 +115,7 @@ class GowinToolchain(GenericToolchain): # Constraints (.cst ) -------------------------------------------------------------------------- def build_io_constraints(self): - cst = [] - - flat_sc = [] - for name, pins, other, resource in self.named_sc: - if len(pins) > 1: - for i, p in enumerate(pins): - flat_sc.append((f"{name}[{i}]", p, other)) - else: - flat_sc.append((name, pins[0], other)) - - def _search_pin_entry(pin_lst, pin_name): - for name, pin, other in pin_lst: - if pin_name == name: - return (name, pin, other) - return (None, None, None) - - for name, pin, other in flat_sc: - if pin != "X": - t_name = name.split('[') # avoid index pins - tmp_name = t_name[0] - if tmp_name[-2:] == "_p": - pn = tmp_name[:-2] + "_n" - if len(t_name) > 1: - pn += '[' + t_name[1] - (_, n_pin, _) = _search_pin_entry(flat_sc, pn) - if n_pin is not None: - pin = f"{pin},{n_pin}" - elif tmp_name[-2:] == "_n": - pp = tmp_name[:-2] + "_p" - if len(t_name) > 1: - pp += '[' + t_name[1] - (p_name, _, _) = _search_pin_entry(flat_sc, pp) - if p_name is not None: - continue - cst.append(f"IO_LOC \"{name}\" {pin};") - - other_cst = [] - for c in other: - if isinstance(c, IOStandard): - other_cst.append(f"IO_TYPE={c.name}") - elif isinstance(c, Misc): - other_cst.append(f"{c.misc}") - if len(other_cst): - t = " ".join(other_cst) - cst.append(f"IO_PORT \"{name}\" {t};") - - if self.named_pc: - cst.extend(self.named_pc) - - cst.extend(self.additional_cst_commands) - - tools.write_to_file(f"{self._build_name}.cst", "\n".join(cst)) + _build_cst(self.named_sc, self.named_pc, self.additional_cst_commands, self._build_name) return (f"{self._build_name}.cst", "CST") # Timing Constraints (.sdc ) ------------------------------------------------------------------- diff --git a/litex/build/gowin/platform.py b/litex/build/gowin/platform.py index f21d11ee7..6706a8608 100644 --- a/litex/build/gowin/platform.py +++ b/litex/build/gowin/platform.py @@ -8,7 +8,7 @@ import os from litex.build.generic_platform import GenericPlatform -from litex.build.gowin import common, gowin +from litex.build.gowin import common, gowin, apicula # GowinPlatform ------------------------------------------------------------------------------------ @@ -28,12 +28,14 @@ class GowinPlatform(GenericPlatform): if toolchain == "gowin": self.toolchain = gowin.GowinToolchain() elif toolchain == "apicula": - raise ValueError("Apicula toolchain needs more work") + self.toolchain = apicula.GowinApiculaToolchain() else: raise ValueError(f"Unknown toolchain {toolchain}") def get_verilog(self, *args, special_overrides=dict(), **kwargs): so = dict(common.gowin_special_overrides) + if self.device[:4] == "GW5A": + so.update(common.gw5a_special_overrides) so.update(special_overrides) return GenericPlatform.get_verilog(self, *args, special_overrides = so, diff --git a/litex/build/lattice/radiant.py b/litex/build/lattice/radiant.py index 53b595d95..938f899e2 100644 --- a/litex/build/lattice/radiant.py +++ b/litex/build/lattice/radiant.py @@ -83,9 +83,10 @@ class LatticeRadiantToolchain(GenericToolchain): def __init__(self): super().__init__() - self._timingstrict = False - self._synth_mode = "radiant" - self._yosys = None + self._timingstrict = False + self._synth_mode = "radiant" + self._yosys = None + self._prj_strategy_opts = {} def build(self, platform, fragment, timingstrict = False, @@ -176,6 +177,10 @@ class LatticeRadiantToolchain(GenericToolchain): # Set top level tcl.append("prj_set_impl_opt top \"{}\"".format(self._build_name)) + # Set user project extra configurations + for k,v in self._prj_strategy_opts.items(): + tcl.append(f"prj_set_strategy_value {k}={v}") + # Save project tcl.append("prj_save") @@ -283,6 +288,16 @@ class LatticeRadiantToolchain(GenericToolchain): return raise Exception("Failed to meet timing") + """ + Set optional configuration for syn, par, bit. + Attributes + ========== + strategy_opts: dict + keys/values to inject at script creation time + """ + def set_prj_strategy_opts(self, strategy_opts={}): + self._prj_strategy_opts.update(strategy_opts) + def radiant_build_args(parser): toolchain_group = parser.add_argument_group(title="Radiant toolchain options") diff --git a/litex/build/microsemi/libero_soc.py b/litex/build/microsemi/libero_soc.py index 87c7949d2..2e73fd6e9 100644 --- a/litex/build/microsemi/libero_soc.py +++ b/litex/build/microsemi/libero_soc.py @@ -8,6 +8,7 @@ import os import sys import subprocess import shutil +from shutil import which from migen.fhdl.structure import _Fragment @@ -83,6 +84,7 @@ class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain): def build_project(self): tcl = [] + die, package, speed = self.platform.device.split("-") # Create project tcl.append(" ".join([ "new_project", @@ -96,49 +98,33 @@ class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain): "-use_enhanced_constraint_flow 1", "-hdl {VERILOG}", "-family {PolarFire}", - "-die {}", - "-package {}", - "-speed {}", - "-die_voltage {}", - "-part_range {}", - "-adv_options {}" - ])) - - die, package, speed = self.platform.device.split("-") - tcl.append(" ".join([ - "set_device", - "-family {PolarFire}", "-die {}".format(self.tcl_name(die)), "-package {}".format(self.tcl_name(package)), "-speed {}".format(self.tcl_name("-" + speed)), - # FIXME: common to all PolarFire devices? "-die_voltage {1.0}", - "-part_range {EXT}", - "-adv_options {IO_DEFT_STD:LVCMOS 1.8V}", - "-adv_options {RESTRICTPROBEPINS:1}", - "-adv_options {RESTRICTSPIPINS:0}", - "-adv_options {TEMPR:EXT}", - "-adv_options {UNUSED_MSS_IO_RESISTOR_PULL:None}", - "-adv_options {VCCI_1.2_VOLTR:EXT}", - "-adv_options {VCCI_1.5_VOLTR:EXT}", - "-adv_options {VCCI_1.8_VOLTR:EXT}", - "-adv_options {VCCI_2.5_VOLTR:EXT}", - "-adv_options {VCCI_3.3_VOLTR:EXT}", - "-adv_options {VOLTR:EXT} " - ])) + "-part_range {IND}", + "-adv_options {VCCI_1.2_VOLTR:IND}", + "-adv_options {VCCI_1.5_VOLTR:IND}", + "-adv_options {VCCI_1.8_VOLTR:IND}", + "-adv_options {VCCI_2.5_VOLTR:IND}", + "-adv_options {VCCI_3.3_VOLTR:IND}" + ])) # Add sources for filename, language, library, *copy in self.platform.sources: filename_tcl = "{" + filename + "}" tcl.append("import_files -hdl_source " + filename_tcl) + # Building the design Hierarchy + tcl.append("build_design_hierarchy") # Set top level - tcl.append("set_root -module {}".format(self.tcl_name(self._build_name))) + tcl.append("set_root -module {}".format(self.tcl_name(self._build_name + "::work"))) # Copy init files FIXME: support for include path on LiberoSoC? - for file in os.listdir(self._build_dir): - if file.endswith(".init"): - tcl.append("file copy -- {} impl/synthesis".format(file)) + # Commenting out copy init file as this breaks the LiberoSoC flow. + #for file in os.listdir(self._build_dir): + # if file.endswith(".init"): + # tcl.append("file copy -- {} impl/synthesis".format(file)) # Import io constraints tcl.append("import_files -io_pdc {}".format(self.tcl_name(self._build_name + "_io.pdc"))) @@ -177,6 +163,23 @@ class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain): tcl.append("run_tool -name {PLACEROUTE}") tcl.append("run_tool -name {GENERATEPROGRAMMINGDATA}") tcl.append("run_tool -name {GENERATEPROGRAMMINGFILE}") + + # Export the FPExpress programming file to Libero SoC default location + tcl.append(" export_prog_job \ + -job_file_name {top} \ + -export_dir {./impl/designer/top/export} \ + -bitstream_file_type {TRUSTED_FACILITY} \ + -bitstream_file_components {FABRIC SNVM} \ + -zeroization_likenew_action 0 \ + -zeroization_unrecoverable_action 0 \ + -program_design 1 \ + -program_spi_flash 0 \ + -include_plaintext_passkey 0 \ + -design_bitstream_format {PPD} \ + -prog_optional_procedures {} \ + -skip_recommended_procedures {} \ + -sanitize_snvm 0 ") + # Generate tcl tools.write_to_file(self._build_name + ".tcl", "\n".join(tcl)) @@ -221,6 +224,7 @@ class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain): copy_stmt = "cp" fail_stmt = " || exit 1" + script_contents += "libero script:" + self._build_name + ".tcl\n" script_file = "build_" + self._build_name + script_ext tools.write_to_file(script_file, script_contents, force_unix=False) @@ -236,8 +240,12 @@ class MicrosemiLiberoSoCPolarfireToolchain(GenericToolchain): else: shell = ["bash"] + if which("libero") is None: + msg = "Unable to find or source Libero SoC toolchain, please make sure libero has been installed corectly.\n" + raise OSError(msg) + if subprocess.call(shell + [script]) != 0: - raise OSError("Subprocess failed") + raise OSError("Subprocess failed") def add_false_path_constraint(self, platform, from_, to): if (to, from_) not in self.false_paths: diff --git a/litex/build/nextpnr_wrapper.py b/litex/build/nextpnr_wrapper.py index 451e2ec0e..18112013d 100644 --- a/litex/build/nextpnr_wrapper.py +++ b/litex/build/nextpnr_wrapper.py @@ -83,9 +83,10 @@ class NextPNRWrapper(): ======= str containing instruction and/or rule """ - cmd = "{pnr_name} --{in_fmt} {build_name}.{in_fmt} --{constr_fmt}" + \ - " {build_name}.{constr_fmt}" + \ - " --{out_fmt} {build_name}.{out_ext} {pnr_opts}" + cmd = "{pnr_name} --{in_fmt} {build_name}.{in_fmt}" + if self._constr_format != "": + cmd += " --{constr_fmt} {build_name}.{constr_fmt}" + cmd += " --{out_fmt} {build_name}.{out_ext} {pnr_opts}" base_cmd = cmd.format( pnr_name = self.name, build_name = self._build_name, diff --git a/litex/build/openfpgaloader.py b/litex/build/openfpgaloader.py index 8b41d3093..431035559 100644 --- a/litex/build/openfpgaloader.py +++ b/litex/build/openfpgaloader.py @@ -41,6 +41,7 @@ class OpenFPGALoader(GenericProgrammer): cmd = self.cmd + ["--bitstream", bitstream_file] # Execute command. + print(" ".join(cmd)) self.call(cmd) def flash(self, address, data_file, external=False, unprotect_flash=False, verify=False, **kwargs): @@ -72,6 +73,7 @@ class OpenFPGALoader(GenericProgrammer): # Execute Command. try: + print(" ".join(cmd)) self.call(cmd) except OSError as e: print(' '.join(cmd)) diff --git a/litex/build/openocd.py b/litex/build/openocd.py index 166fb2e26..62644da5f 100644 --- a/litex/build/openocd.py +++ b/litex/build/openocd.py @@ -27,12 +27,13 @@ class OpenOCD(GenericProgrammer): ]) self.call(["openocd", "-f", config, "-c", script]) - def flash(self, address, data, set_qe=False): + def flash(self, address, data, set_qe=False, init_commands=[]): config = self.find_config() flash_proxy = self.find_flash_proxy() script = "; ".join([ "init", - "jtagspi_init 0 {{{}}}".format(flash_proxy), + "jtagspi_init 0 {{{}}}".format(flash_proxy) + ] + init_commands + [ "jtagspi set_qe 0 1" if set_qe else "", "jtagspi_program {{{}}} 0x{:x}".format(data, address), "fpga_program", @@ -192,7 +193,7 @@ proc jtagstream_serve {tap port} { write_to_file("stream.cfg", cfg) script = "; ".join([ "init", - "poll off", + #"poll off", # FIXME: not supported for ECP5 "irscan {} {:d}".format(tap_name, ir), "jtagstream_serve {} {:d}".format(tap_name, port), "exit", diff --git a/litex/build/parser.py b/litex/build/parser.py index 31c15308e..ee2b4fb27 100644 --- a/litex/build/parser.py +++ b/litex/build/parser.py @@ -217,13 +217,21 @@ class LiteXArgumentParser(argparse.ArgumentParser): self._platform.fill_args(self._toolchain, self) # Intercept selected CPU to fill arguments. - cpu_cls = cpu.CPUS.get(self.get_value_from_key("--cpu-type"), None) + default_cpu_type = self._args_default.get("cpu_type", None) + cpu_cls = cpu.CPUS.get(self.get_value_from_key("--cpu-type", default_cpu_type)) if cpu_cls is not None and hasattr(cpu_cls, "args_fill"): cpu_cls.args_fill(self) # Injects arguments default values if len(self._args_default): argparse.ArgumentParser.set_defaults(self, **self._args_default) + # Catch defaults which do not match any arguments - typos? + remaining = list(self._args_default.keys()) + for action in self._actions: + if action.dest in remaining: + remaining.remove(action.dest) + if len(remaining) > 0: + raise ValueError(f"set_default() for invalid argument(s): {remaining}") # Parse args. self._args = argparse.ArgumentParser.parse_args(self, args, namespace) diff --git a/litex/build/quicklogic/f4pga.py b/litex/build/quicklogic/f4pga.py index 44969fd02..f91cef798 100644 --- a/litex/build/quicklogic/f4pga.py +++ b/litex/build/quicklogic/f4pga.py @@ -5,9 +5,13 @@ # Copyright (c) 2021 Gwenhael Goavec-Merou # SPDX-License-Identifier: BSD-2-Clause +# See: https://f4pga-examples.readthedocs.io/en/latest/getting.html#toolchain-installation +# To install toolchain + import os import sys import subprocess +import json from shutil import which from migen.fhdl.structure import _Fragment @@ -47,48 +51,57 @@ class F4PGAToolchain(GenericToolchain): tools.write_to_file(self._build_name + ".pcf", pcf) return (self._build_name + ".pcf", "PCF") - # Build Makefile ------------------------------------------------------------------------------- + # Timing constraints (.sdc) -------------------------------------------------------------------- + + def build_timing_constraints(self, vns): + sdc = [] + for clk, [period, name] in sorted(self.clocks.items(), key=lambda x: x[0].duid): + clk_sig = vns.get_name(clk) + sdc.append("create_clock -period {} {}".format(str(period), clk_sig)) + tools.write_to_file(self._build_name + "_in.sdc", "\n".join(sdc)) + return (self._build_name + "_in.sdc", "SDC") + + # Build flow.json ------------------------------------------------------------------------------ def build_script(self): - makefile = [] + part = {"ql-eos-s3": "PU64"}.get(self.platform.device) + flow = { + "default_part": "EOS3FF512-PDN64", + "values": { + "top": self._build_name + }, + "dependencies": { + "sources": [ + f"{self._build_name}.v" + ], + "synth_log": "synth.log", + "pack_log": "pack.log", + "analysis_log": "analysis.log" + }, + "EOS3FF512-PDN64": { + "default_target": "bitstream", + "dependencies": { + "build_dir": self._build_dir, + "pcf": f"{self._build_name}.pcf", + "sdc-in": f"{self._build_name}_in.sdc" + }, + "values": { + "part": self.platform.device, + "package": part + } + } + }; - # Define Paths. - makefile.append("mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))") - makefile.append("current_dir := $(patsubst %/,%,$(dir $(mkfile_path)))") - # bit -> h and bit -> bin requires TOP_F - makefile.append(f"TOP_F={self._build_name}") - - # Create Project. - # FIXME: Only use top file for now and ignore .init files. - makefile.append("all: {top}_bit.h {top}.bin build/{top}.bit".format(top=self._build_name)) - # build bit file (default) - makefile.append(f"build/{self._build_name}.bit:") - makefile.append("\tql_symbiflow -compile -d {device} -P {part} -v {verilog} -t {top} -p {pcf}".format( - device = self.platform.device, - part = {"ql-eos-s3": "PU64"}.get(self.platform.device), - verilog = f"{self._build_name}.v", - top = self._build_name, - pcf = f"{self._build_name}.pcf" - )) - # build header to include in CPU firmware - makefile.append("{top}_bit.h: build/{top}.bit".format(top=self._build_name)) - makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_bitheader)") - # build binary to write in dedicated FLASH area - makefile.append("{top}.bin: build/{top}.bit".format(top=self._build_name)) - makefile.append(f"\t(cd build; TOP_F=$(TOP_F) symbiflow_write_binary)") - - # Generate Makefile. - tools.write_to_file("Makefile", "\n".join(makefile)) - - return "Makefile" + tools.write_to_file("flow.json", json.dumps(flow)) + return "flow.json" def run_script(self, script): - make_cmd = ["make", "-j1"] - - if which("ql_symbiflow") is None: - msg = "Unable to find QuickLogic Symbiflow toolchain, please:\n" - msg += "- Add QuickLogic Symbiflow toolchain to your $PATH." - raise OSError(msg) + make_cmd = ["f4pga", "-vvv", "build", "--flow", "flow.json"] if subprocess.call(make_cmd) != 0: raise OSError("Error occured during QuickLogic Symbiflow's script execution.") + + make_cmd.append("--target") + for target in ["bitstream_openocd", "bitstream_jlink", "bitstream_bitheader", "bitstream_binary"]: + if subprocess.call(make_cmd + [target]) != 0: + raise OSError(f"Error occured during QuickLogic Symbiflow's script execution for step {target}.") diff --git a/litex/build/quicklogic/platform.py b/litex/build/quicklogic/platform.py index 193f45262..45c7cda49 100644 --- a/litex/build/quicklogic/platform.py +++ b/litex/build/quicklogic/platform.py @@ -19,10 +19,13 @@ class QuickLogicPlatform(GenericPlatform): def __init__(self, *args, toolchain="f4pga", **kwargs): GenericPlatform.__init__(self, *args, **kwargs) - if toolchain == "symbiflow" or toolchain == "f4pga": - self.toolchain = f4pga.F4PGAToolchain() + if isinstance(toolchain, str): + if toolchain == "symbiflow" or toolchain == "f4pga": + self.toolchain = f4pga.F4PGAToolchain() + else: + raise ValueError(f"Unknown toolchain {toolchain}") else: - raise ValueError(f"Unknown toolchain {toolchain}") + self.toolchain = toolchain def get_verilog(self, *args, special_overrides=dict(), **kwargs): so = dict(common.quicklogic_special_overrides) diff --git a/litex/build/sim/core/modules/jtagremote/jtagremote.c b/litex/build/sim/core/modules/jtagremote/jtagremote.c index 1b0fa4e25..29b4df1e7 100644 --- a/litex/build/sim/core/modules/jtagremote/jtagremote.c +++ b/litex/build/sim/core/modules/jtagremote/jtagremote.c @@ -15,6 +15,7 @@ struct session_s { char *tdo; char *tck; char *tms; + char *ntrst; char *sys_clk; struct event *ev; char databuf[2048]; @@ -199,6 +200,7 @@ static int jtagremote_add_pads(void *sess, struct pad_list_s *plist) litex_sim_module_pads_get(pads, "tdi", (void**)&s->tdi); litex_sim_module_pads_get(pads, "tdo", (void**)&s->tdo); litex_sim_module_pads_get(pads, "tms", (void**)&s->tms); + litex_sim_module_pads_get(pads, "ntrst", (void**)&s->ntrst); } if(!strcmp(plist->name, "sys_clk")) @@ -227,19 +229,34 @@ static int jtagremote_tick(void *sess, uint64_t time_ps) { c = s->databuf[s->data_start]; - if((c >= '0') && (c <= '7')){ - *s->tck = ((c - '0') >> 2) & 1; - *s->tms = ((c - '0') >> 1) & 1; - *s->tdi = (c - '0') & 1; - } - if(c == 'R'){ - val = *s->tdo + '0'; - if(-1 == write(s->fd, &val, 1)) { - eprintf("Error writing on socket\n"); - ret = RC_ERROR; - goto out; - } + switch(c) { + case '0'...'7': + *s->tck = ((c - '0') >> 2) & 1; + *s->tms = ((c - '0') >> 1) & 1; + *s->tdi = (c - '0') & 1; + break; + case 'r': + case 's': + /* Deassert reset */ + *s->ntrst = 1; + break; + case 't': + case 'u': + /* Assert reset */ + *s->ntrst = 0; + break; + case 'R': + val = *s->tdo + '0'; + if(write(s->fd, &val, 1) == -1) { + eprintf("Error writing on socket\n"); + ret = RC_ERROR; + goto out; + } + break; + default: + break; } + s->data_start = (s->data_start + 1) % 2048; s->datalen--; } diff --git a/litex/build/sim/core/modules/video/sim_fb.c b/litex/build/sim/core/modules/video/sim_fb.c index 79c5c10f5..3535985a7 100644 --- a/litex/build/sim/core/modules/video/sim_fb.c +++ b/litex/build/sim/core/modules/video/sim_fb.c @@ -17,7 +17,7 @@ bool fb_init(unsigned width, unsigned height, bool vsync, fb_handle_t *handle) if (!handle->renderer) return false; - handle->texture = SDL_CreateTexture(handle->renderer, SDL_PIXELFORMAT_BGRA32, SDL_TEXTUREACCESS_TARGET, width, height); + handle->texture = SDL_CreateTexture(handle->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, width, height); if (!handle->texture) return false; diff --git a/litex/build/sim/core/modules/video/video.c b/litex/build/sim/core/modules/video/video.c index 7549b5545..264845569 100644 --- a/litex/build/sim/core/modules/video/video.c +++ b/litex/build/sim/core/modules/video/video.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "error.h" #include #include @@ -29,6 +30,7 @@ struct session_s { unsigned frame, stride; uint8_t *buf, *pbuf; fb_handle_t fb; + char render_on_vsync; }; static int litex_sim_module_pads_get(struct pad_s *pads, char *name, void **signal) @@ -56,6 +58,30 @@ out: return ret; } +static int videosim_parse_args(struct session_s *s, const char *args) +{ + int ret = RC_OK; + json_object *args_json = NULL; + json_object *render_on_vsync_json = NULL; + + args_json = json_tokener_parse(args); + if (!args_json) { + ret = RC_JSERROR; + fprintf(stderr, "[video] Could not parse args: %s\n", args); + goto out; + } + + if(json_object_object_get_ex(args_json, "render_on_vsync", &render_on_vsync_json)) { + s->render_on_vsync = json_object_get_boolean(render_on_vsync_json); + } else { + s->render_on_vsync = false; + } + +out: + if(args_json) json_object_put(args_json); + return ret; +} + static int videosim_start(void *b) { printf("[video] loaded (%p)\n", (struct event_base *)b); @@ -79,6 +105,7 @@ static int videosim_new(void **sess, char *args) } memset(s, 0, sizeof(struct session_s)); + if (args) videosim_parse_args(s, args); out: *sess = (void*) s; return ret; @@ -143,6 +170,14 @@ static int videosim_tick(void *sess, uint64_t time_ps) { fb_init(s->hres, s->vres, false, &s->fb); s->stride = s->hres*sizeof(uint32_t); } + if (s->render_on_vsync) { + if(fb_should_quit()) + { + fb_deinit(&s->fb); + exit(1); //FIXME: end gracefully + } + fb_update(&s->fb, s->buf, s->stride); + } s->y = 0; s->pbuf = s->buf; ++s->frame; @@ -164,12 +199,14 @@ static int videosim_tick(void *sess, uint64_t time_ps) { { if(s->buf) //update each horizontal line { - if(fb_should_quit()) - { - fb_deinit(&s->fb); - exit(1); //FIXME: end gracefully + if (!s->render_on_vsync) { + if(fb_should_quit()) + { + fb_deinit(&s->fb); + exit(1); //FIXME: end gracefully + } + fb_update(&s->fb, s->buf, s->stride); } - fb_update(&s->fb, s->buf, s->stride); s->pbuf = s->buf + s->y*s->stride; } diff --git a/litex/build/tools.py b/litex/build/tools.py index 410d280c4..bada20978 100644 --- a/litex/build/tools.py +++ b/litex/build/tools.py @@ -114,9 +114,15 @@ def get_litex_git_revision(): os.chdir(d) return r -def generated_banner(line_comment="//"): +def generated_separator(line_comment="//", msg=""): r = line_comment + "-"*80 + "\n" - r += line_comment + " Auto-generated by LiteX ({}) on ".format(get_litex_git_revision()) - r += "{}\n".format(datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S")) + r += line_comment + " " + msg + "\n" r += line_comment + "-"*80 + "\n" return r + +def generated_banner(line_comment="//"): + msg = "Auto-generated by LiteX ({}) on {}".format( + get_litex_git_revision(), + datetime.datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d %H:%M:%S"), + ) + return generated_separator(line_comment, msg) diff --git a/litex/build/vhd2v_converter.py b/litex/build/vhd2v_converter.py index f6c129835..83326a730 100644 --- a/litex/build/vhd2v_converter.py +++ b/litex/build/vhd2v_converter.py @@ -61,6 +61,7 @@ class VHD2VConverter(Module): self._params = params self._force_convert = force_convert self._add_instance = add_instance + self._work_package = work_package self._ghdl_opts = ["--std=08", "--no-formal"] @@ -103,7 +104,7 @@ class VHD2VConverter(Module): if self._platform.support_mixed_language and not self._force_convert: ip_params = self._params for file in self._sources: - self._platform.add_source(file) + self._platform.add_source(file, library=self._work_package) else: # platform is only able to synthesis verilog -> convert vhdl to verilog # check if more than one core is instanciated # if so -> append with _X @@ -141,7 +142,8 @@ class VHD2VConverter(Module): # more than one instance of this core? rename top entity to avoid conflict if inst_name != self._top_entity: - tools.replace_in_file(verilog_out, f"module {self._top_entity}(", f"module {inst_name}(") + tools.replace_in_file(verilog_out, f"module {self._top_entity}", f"module {inst_name}") + tools.replace_in_file(verilog_out, f"\\", f"ghdl_") # FIXME: GHDL synth workaround, improve. self._platform.add_source(verilog_out) if self._add_instance: diff --git a/litex/build/xilinx/common.py b/litex/build/xilinx/common.py index 6d7e1e435..d0d6bf0f6 100644 --- a/litex/build/xilinx/common.py +++ b/litex/build/xilinx/common.py @@ -141,9 +141,9 @@ class XilinxSDRTristateImpl(Module): _o = Signal() _oe_n = Signal() _i = Signal() - self.specials += SDROutput(o, _o) - self.specials += SDROutput(~oe, _oe_n) - self.specials += SDRInput(_i, i) + self.specials += SDROutput(o, _o, clk) + self.specials += SDROutput(~oe, _oe_n, clk) + self.specials += SDRInput(_i, i, clk) self.specials += Instance("IOBUF", io_IO = io, o_O = _i, diff --git a/litex/build/xilinx/yosys_nextpnr.py b/litex/build/xilinx/yosys_nextpnr.py index c8a017eb7..abde181bb 100644 --- a/litex/build/xilinx/yosys_nextpnr.py +++ b/litex/build/xilinx/yosys_nextpnr.py @@ -58,6 +58,13 @@ class XilinxYosysNextpnrToolchain(YosysNextPNRToolchain): "z": "zynq7" } + @property + def device(self): + return { + "xc7a35ticsg324-1L": "xc7a35tcsg324-1", + "xc7a200t-sbg484-1": "xc7a200tsbg484-1", + }.get(self.platform.device, self.platform.device) + def _check_properties(self): pattern = re.compile("xc7([aksz])([0-9]+)(.*)-([0-9])") g = pattern.search(self.platform.device) @@ -119,13 +126,13 @@ class XilinxYosysNextpnrToolchain(YosysNextPNRToolchain): if nextpnr_xilinx_python_dir is None or nextpnr_xilinx_python_dir == "": nextpnr_xilinx_python_dir = "/snap/openxc7/current/opt/nextpnr-xilinx/python" bba = self.dbpart + ".bba" - bbaexport = [pypy3, os.path.join(nextpnr_xilinx_python_dir, "bbaexport.py"), "--device", self.platform.device, "--bba", bba] + bbaexport = [pypy3, os.path.join(nextpnr_xilinx_python_dir, "bbaexport.py"), "--device", self.device, "--bba", bba] print(str(bbaexport)) subprocess.run(bbaexport) subprocess.run(["bbasm", "-l", bba, chipdb]) os.remove(bba) else: - print("Chip database file '{chipdb}' not found. Please check your toolchain installation!") + print(f"Chip database file '{chipdb}' not found. Please check your toolchain installation!") exit(1) # pnr options @@ -149,14 +156,14 @@ class XilinxYosysNextpnrToolchain(YosysNextPNRToolchain): # pre packer options self._pre_packer_opts[self._pre_packer_cmd[0]] = "--part {part} --db-root {db_root} {top}.fasm > {top}.frames".format( - part = self.platform.device, + part = self.device, db_root = os.path.join(prjxray_db_dir, self._xc7family), top = self._build_name ) # packer options self._packer_opts += "--part_file {db_dir}/{part}/part.yaml --part_name {part} --frm_file {top}.frames --output_file {top}.bit".format( db_dir = os.path.join(prjxray_db_dir, self._xc7family), - part = self.platform.device, + part = self.device, top = self._build_name ) diff --git a/litex/gen/common.py b/litex/gen/common.py index 101243038..a89d1a2f1 100644 --- a/litex/gen/common.py +++ b/litex/gen/common.py @@ -20,6 +20,18 @@ def colorer(s, color="bright", enable=True): trailer = "\x1b[0m" return (header + str(s) + trailer) if enable else str(s) +# Byte Size Definitions ---------------------------------------------------------------------------- + +# Short. +KB = 1024 +MB = KB * 1024 +GB = MB * 1024 + +# Long. +KILOBYTE = 1024 +MEGABYTE = KILOBYTE * 1024 +GIGABYTE = MEGABYTE * 1024 + # Bit/Bytes Reversing ------------------------------------------------------------------------------ def reverse_bits(s): diff --git a/litex/gen/fhdl/hierarchy.py b/litex/gen/fhdl/hierarchy.py index 720fe0207..663dc6e1b 100644 --- a/litex/gen/fhdl/hierarchy.py +++ b/litex/gen/fhdl/hierarchy.py @@ -41,7 +41,7 @@ class LiteXHierarchyExplorer: r += self.get_tree(mod, ident + 1) # Instances. - for s in module._fragment.specials: + for s in sorted(module._fragment.specials, key=lambda x: str(x)): if (self.depth is None) or (ident <= self.depth): if isinstance(s, Instance): show = with_instances diff --git a/litex/soc/cores/can/__init__.py b/litex/soc/cores/can/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/litex/soc/cores/can/ctu_can_fd.py b/litex/soc/cores/can/ctu_can_fd.py new file mode 100644 index 000000000..4a535647f --- /dev/null +++ b/litex/soc/cores/can/ctu_can_fd.py @@ -0,0 +1,175 @@ +# +# CTU CAN-FD Core Wrapper for LiteX. +# +# Copyright (c) 2021 Andrew Dennison +# Copyright (c) 2021-2024 Florent Kermarrec +# Copyright (c) 2024 Gwenhael Goavec-Merou +# SPDX-License-Identifier: BSD-2-Clause + +# Documentation at https://canbus.pages.fel.cvut.cz/ + +import os +import subprocess + +from migen import * + +from litex.gen import * + +from litex.build import tools +from litex.build.vhd2v_converter import VHD2VConverter + +from litex.soc.interconnect import wishbone +from litex.soc.interconnect.csr_eventmanager import * + +# CTU CAN-FD --------------------------------------------------------------------------------------- + +class CTUCANFD(LiteXModule, EventManager): + def __init__(self, platform, pads, timestamp=0, force_convert=False): + # Parameters. + self.platform = platform + self.pads = pads + self._force_convert = force_convert + + # Wishbone Bus. + self.bus = wishbone.Interface(data_width=32) + + # CSRs. + self.control = CSRStorage(32, fields=[ + CSRField("reset", size=1, values=[ + ("``0b0``", "Normal Operation."), + ("``0b1`", "Hold the Core in Reset."), + ], reset=0b0), + CSRField("reserved",size=31), + ]) + self.status = CSRStatus(32, fields=[ + CSRField("ready", size=1, values=[ + ("``0b0``", "Core in Reset."), + ("``0b1`", "Core Ready."), + ]), + ]) + + # IRQs. + self.irq = Signal() + + # CTU CAN-FD Instance ---------------------------------------------------------------------- + self.core_params = dict() + + # Wishbone to CTU CAN-FD Memory Bus adaptation. + self.mem_scs = mem_scs = Signal() + self.mem_srd = mem_srd = Signal() + self.mem_swr = mem_swr = Signal() + self.mem_sbe = mem_sbe = Signal(4) + self.mem_adress = mem_adress = Signal(16) + self.mem_data_in = mem_data_in = Signal(32) + self.mem_data_out = mem_data_out = Signal(32) + + self.comb += [ + # On Wishbone Access cycle... + mem_scs.eq(self.bus.cyc & self.bus.stb), + mem_srd.eq(mem_scs & ~self.bus.we & ~self.bus.ack), + mem_swr.eq(mem_scs & self.bus.we), + mem_sbe.eq(self.bus.sel), + # Connect data_in/out. + mem_data_in.eq(self.bus.dat_w), + self.bus.dat_r.eq(mem_data_out), + # Convert 32-bit word addressing to bytes addressing. + mem_adress.eq(Cat(Signal(2), self.bus.adr)), + ] + self.sync += [ + self.bus.ack.eq(0), + If(mem_scs & ~self.bus.ack, self.bus.ack.eq(1)), + ] + + # CTU CAN-FD Parameters. + self.core_params.update( + # Target technology (ASIC or FPGA) + #p_target_technology = C_TECH_FPGA + + # TX/RX Buffers. + p_txt_buffer_count = 4, # Number of TX Buffers. + p_rx_buffer_size = 32, # RX Buffer size (in 32-bit words). + + # Filter A-C. + p_sup_filtA = False, + p_sup_filtB = False, + p_sup_filtC = False, + + # Range Filter. + #p_sup_filtV = False, + + # Synthesize Range Filter + p_sup_range = False, + + # Test registers. + p_sup_test_registers = True, # True to have access to 0x9XX Tests registers + + # Traffic counters. + p_sup_traffic_ctrs = False, + + # Add parity bit to TXT Buffer and RX Buffer RAMs + p_sup_parity = False, + + # Number of active timestamp bits + p_active_timestamp_bits = 63, + + # Reset TXT / RX Buffer RAMs + p_reset_buffer_rams = False, + ) + + # CTU CAN-FD Signals. + self.core_params.update( + # Clk / Rst. + i_clk_sys = ClockSignal("sys"), + i_res_n = ~(ResetSignal("sys") | self.control.fields.reset), + o_res_n_out = self.status.fields.ready, + + # DFT support (ASIC only). + i_scan_enable = 0, + + # Timestamp (For time based transmission / reception). + i_timestamp = timestamp, + + # Memory interface. + i_scs = mem_scs, + i_srd = mem_srd, + i_swr = mem_swr, + i_sbe = mem_sbe, + i_adress = mem_adress, + i_data_in = mem_data_in, + o_data_out = mem_data_out, + + # Interrupt. + o_int = self.irq, + + # CAN Bus. + o_can_tx = pads.tx, + i_can_rx = pads.rx, + + # Debug. + #o_test_probe = , + ) + + def add_sources(self, platform): + sources = [] + sdir = "CTU-CAN-FD" + cdir = os.path.dirname(__file__) + if not os.path.exists(sdir): + os.system(f"git clone https://github.com/enjoy-digital/CTU-CAN-FD") + with open(os.path.join(cdir, 'rtl_lst.txt')) as f: + for line in f: + srcfile = os.path.join(sdir, line.strip().replace('rtl', 'src')) + self.vhd2v_converter.add_source(srcfile) + + def do_finalize(self): + # CAN Core instance + self.vhd2v_converter = VHD2VConverter(self.platform, + top_entity = "can_top_level", + build_dir = os.path.abspath(os.path.dirname(__file__)), + work_package = "ctu_can_fd_rtl", + force_convert = self._force_convert, + params = self.core_params, + add_instance = True, + ) + + # Add Sources. + self.add_sources(self.platform) diff --git a/litex/soc/cores/can/rtl_lst.txt b/litex/soc/cores/can/rtl_lst.txt new file mode 100644 index 000000000..19d92a25d --- /dev/null +++ b/litex/soc/cores/can/rtl_lst.txt @@ -0,0 +1,88 @@ +rtl/packages/unary_ops_pkg.vhd +rtl/packages/can_fd_register_map.vhd +rtl/packages/can_fd_frame_format.vhd +rtl/packages/id_transfer_pkg.vhd +rtl/packages/can_constants_pkg.vhd +rtl/packages/can_config_pkg.vhd +rtl/packages/can_types_pkg.vhd +rtl/txt_buffer/txt_buffer_fsm.vhd +rtl/tx_arbitrator/tx_arbitrator_fsm.vhd +rtl/tx_arbitrator/priority_decoder.vhd +rtl/tx_arbitrator/tx_arbitrator.vhd +rtl/rx_buffer/rx_buffer_pointers.vhd +rtl/rx_buffer/rx_buffer_fsm.vhd +rtl/prescaler/trigger_generator.vhd +rtl/prescaler/synchronisation_checker.vhd +rtl/prescaler/segment_end_detector.vhd +rtl/prescaler/bit_time_fsm.vhd +rtl/prescaler/bit_time_counters.vhd +rtl/prescaler/bit_time_cfg_capture.vhd +rtl/prescaler/bit_segment_meter.vhd +rtl/prescaler/prescaler.vhd +rtl/memory_registers/generated/memory_reg_rw_lock.vhd +rtl/memory_registers/generated/memory_reg_rw.vhd +rtl/memory_registers/generated/memory_reg_os_lock.vhd +rtl/memory_registers/generated/memory_reg_os.vhd +rtl/memory_registers/generated/memory_bus.vhd +rtl/memory_registers/generated/data_mux.vhd +rtl/memory_registers/generated/cmn_reg_map_pkg.vhd +rtl/memory_registers/generated/can_registers_pkg.vhd +rtl/memory_registers/generated/test_registers_reg_map.vhd +rtl/memory_registers/generated/control_registers_reg_map.vhd +rtl/memory_registers/generated/address_decoder.vhd +rtl/memory_registers/generated/access_signaler.vhd +rtl/interrupt_manager/int_module.vhd +rtl/interface/apb_ifc.vhd +rtl/interface/ahb_ifc.vhd +rtl/frame_filters/range_filter.vhd +rtl/frame_filters/bit_filter.vhd +rtl/frame_filters/frame_filters.vhd +rtl/common_blocks/sig_sync.vhd +rtl/common_blocks/shift_reg_preload.vhd +rtl/common_blocks/shift_reg_byte.vhd +rtl/common_blocks/shift_reg.vhd +rtl/common_blocks/parity_calculator.vhd +rtl/common_blocks/rst_sync.vhd +rtl/common_blocks/rst_reg.vhd +rtl/common_blocks/mux2.vhd +rtl/common_blocks/majority_decoder_3.vhd +rtl/common_blocks/inf_ram_wrapper.vhd +rtl/txt_buffer/txt_buffer_ram.vhd +rtl/rx_buffer/rx_buffer_ram.vhd +rtl/common_blocks/endian_swapper.vhd +rtl/common_blocks/dlc_decoder.vhd +rtl/common_blocks/dff_arst_ce.vhd +rtl/common_blocks/dff_arst.vhd +rtl/interrupt_manager/int_manager.vhd +rtl/common_blocks/clk_gate.vhd +rtl/txt_buffer/txt_buffer.vhd +rtl/rx_buffer/rx_buffer.vhd +rtl/memory_registers/memory_registers.vhd +rtl/can_core/tx_shift_reg.vhd +rtl/can_core/trigger_mux.vhd +rtl/can_core/rx_shift_reg.vhd +rtl/can_core/retransmitt_counter.vhd +rtl/can_core/reintegration_counter.vhd +rtl/can_core/protocol_control_fsm.vhd +rtl/can_core/operation_control.vhd +rtl/can_core/fault_confinement_rules.vhd +rtl/can_core/fault_confinement_fsm.vhd +rtl/can_core/err_detector.vhd +rtl/can_core/err_counters.vhd +rtl/can_core/fault_confinement.vhd +rtl/can_core/crc_calc.vhd +rtl/can_core/control_counter.vhd +rtl/can_core/protocol_control.vhd +rtl/can_core/can_crc.vhd +rtl/can_core/bus_traffic_counters.vhd +rtl/can_core/bit_stuffing.vhd +rtl/can_core/bit_destuffing.vhd +rtl/can_core/can_core.vhd +rtl/bus_sampling/tx_data_cache.vhd +rtl/bus_sampling/trv_delay_meas.vhd +rtl/bus_sampling/ssp_generator.vhd +rtl/bus_sampling/sample_mux.vhd +rtl/bus_sampling/data_edge_detector.vhd +rtl/bus_sampling/bit_err_detector.vhd +rtl/bus_sampling/bus_sampling.vhd +rtl/can_top_level.vhd diff --git a/litex/soc/cores/clock/efinix.py b/litex/soc/cores/clock/efinix.py index 63cca6839..ba2a9e70d 100644 --- a/litex/soc/cores/clock/efinix.py +++ b/litex/soc/cores/clock/efinix.py @@ -158,10 +158,10 @@ class EFINIXPLL(LiteXModule): # Pre-Divider (N). # ----------------- # F_PFD is between 10e6 and 100e6 - # so limit search to only acceptable factors + # so limit search to only acceptable factors N_min = int(math.ceil(clk_in_freq / pfd_range[1])) N_max = int(math.floor(clk_in_freq / pfd_range[0])) - ## limit + ## limit ### when fin is below FPLL_MAX min is < 1 if N_min < 1: N_min = 1 @@ -200,7 +200,7 @@ class EFINIXPLL(LiteXModule): params_list = [] for n in range(N_min, N_max + 1): fpfd_tmp = clk_in_freq / n - # limit range using FVCO_MAX & FVCO_MIN + # limit range using FVCO_MAX & FVCO_MIN # fVCO = fPFD * M * O * Cfbk # so: # fVCO diff --git a/litex/soc/cores/clock/lattice_nx.py b/litex/soc/cores/clock/lattice_nx.py index 6702461f1..8d8126c9d 100644 --- a/litex/soc/cores/clock/lattice_nx.py +++ b/litex/soc/cores/clock/lattice_nx.py @@ -32,7 +32,7 @@ class NXOSCA(LiteXModule): clk_hf_freq_range = (1.76, 450e6) clk_hf_freq = 450e6 - def __init__(self): + def __init__(self, platform=None): self.logger = logging.getLogger("NXOSCA") self.logger.info("Creating NXOSCA.") @@ -40,6 +40,7 @@ class NXOSCA(LiteXModule): self.hfsdc_clk_out = {} self.lf_clk_out = None self.params = {} + self.platform = platform def create_hf_clk(self, cd, freq, margin=.05): """450 - 1.7 Mhz Clk""" @@ -86,21 +87,29 @@ class NXOSCA(LiteXModule): def do_finalize(self): if self.hf_clk_out: - divisor = self.compute_divisor(self.hf_clk_out[1], self.hf_clk_out[2]) + clk_freq = self.hf_clk_out[1] + divisor = self.compute_divisor(clk_freq, self.hf_clk_out[2]) self.params["i_HFOUTEN"] = 0b1 self.params["p_HF_CLK_DIV"] = divisor self.params["o_HFCLKOUT"] = self.hf_clk_out[0] self.params["p_HF_OSC_EN"] = "ENABLED" + if self.platform: + self.platform.add_platform_command("create_clock -period {} -name OSCA_HFCLKOUT [get_pins OSCA.OSCA_inst/HFCLKOUT]".format(str(1e9/clk_freq))) if self.hfsdc_clk_out: - divisor = self.compute_divisor(self.hfsdc_clk_out[1], self.hfsdc_clk_out[2]) + clk_freq = self.hf_clk_out[1] + divisor = self.compute_divisor(clk_freq, self.hfsdc_clk_out[2]) self.params["i_HFSDSCEN"] = 0b1 self.params["p_HF_SED_SEC_DIV"] = divisor self.params["o_HFSDCOUT"] = self.hfsdc_clk_out[0] + if self.platform: + self.platform.add_platform_command("create_clock -period {} -name OSCA_HFSDCOUT [get_pins OSCA.OSCA_inst/HFSDCOUT]".format(str(1e9/clk_freq))) if self.lf_clk_out is not None: self.params["o_LFCLKOUT"] = self.lf_clk_out[0] self.params["p_LF_OUTPUT_EN"] = "ENABLED" + if self.platform: + self.platform.add_platform_command("create_clock -period {} -name OSCA_LF_OUTPUT_EN [get_pins OSCA.OSCA_inst/LF_OUTPUT_EN]".format(str(1e9/128e3))) self.specials += Instance("OSCA", **self.params) diff --git a/litex/soc/cores/cpu/__init__.py b/litex/soc/cores/cpu/__init__.py index 1d816b3dd..93e76e774 100644 --- a/litex/soc/cores/cpu/__init__.py +++ b/litex/soc/cores/cpu/__init__.py @@ -79,9 +79,13 @@ CPU_GCC_TRIPLE_RISCV64 = ( ) CPU_GCC_TRIPLE_RISCV32 = CPU_GCC_TRIPLE_RISCV64 + ( + "riscv32-pc-linux-musl", "riscv32-unknown-elf", "riscv32-unknown-linux-gnu", "riscv32-elf", + "riscv32-linux", + "riscv32-linux-gnu", + "riscv32-none-elf", "riscv-none-embed", "riscv-none-elf", ) diff --git a/litex/soc/cores/cpu/cv32e40p/core.py b/litex/soc/cores/cpu/cv32e40p/core.py index 79dfbeb1d..682635f40 100644 --- a/litex/soc/cores/cpu/cv32e40p/core.py +++ b/litex/soc/cores/cpu/cv32e40p/core.py @@ -19,7 +19,7 @@ from litex.soc.cores.cpu import CPU, CPU_GCC_TRIPLE_RISCV32 # Variants ----------------------------------------------------------------------------------------- -CPU_VARIANTS = ["standard", "full"] +CPU_VARIANTS = ["standard", "standard+fpu"] # GCC Flags ---------------------------------------------------------------------------------------- @@ -32,7 +32,7 @@ GCC_FLAGS = { # | ||||/-- Double-Precision Floating-Point # i macfd "standard": "-march=rv32i2p0_mc -mabi=ilp32 ", - "full": "-march=rv32i2p0_mfc -mabi=ilp32 ", + "standard+fpu": "-march=rv32i2p0_mfc -mabi=ilp32 ", } # OBI / APB / Trace Layouts ------------------------------------------------------------------------ @@ -59,18 +59,6 @@ apb_layout = [ ("pslverr", 1), ] -trace_layout = [ - ("ivalid", 1), - ("iexception", 1), - ("interrupt", 1), - ("cause", 5), - ("tval", 32), - ("priv", 3), - ("iaddr", 32), - ("instr", 32), - ("compressed", 1), -] - # Helpers ------------------------------------------------------------------------------------------ def add_manifest_sources(platform, manifest): @@ -182,102 +170,6 @@ class Wishbone2APB(LiteXModule): wb.dat_r.eq(apb.prdata), ] -# Trace Collector ---------------------------------------------------------------------------------- - -class TraceCollector(LiteXModule): - def __init__(self, trace_depth=16384): - self.bus = bus = wishbone.Interface(data_width=32, address_width=32, addressing="word") - self.sink = sink = stream.Endpoint([("data", 32)]) - - clear = Signal() - enable = Signal() - pointer = Signal(32) - - self._enable = CSRStorage() - self._clear = CSRStorage() - self._pointer = CSRStatus(32) - - mem = Memory(32, trace_depth) - rd_port = mem.get_port() - wr_port = mem.get_port(write_capable=True) - - self.specials += rd_port, wr_port, mem - - self.sync += [ - # wishbone - bus.ack.eq(0), - If(bus.cyc & bus.stb & ~bus.ack, bus.ack.eq(1)), - # trace core - If(clear, pointer.eq(0)).Else( - If(sink.ready & sink.valid, pointer.eq(pointer+1)), - ), - ] - - self.comb += [ - # wishbone - rd_port.adr.eq(bus.adr), - bus.dat_r.eq(rd_port.dat_r), - # trace core - wr_port.adr.eq(pointer), - wr_port.dat_w.eq(sink.data), - wr_port.we.eq(sink.ready & sink.valid), - sink.ready.eq(enable & (pointer < trace_depth)), - # csrs - enable.eq(self._enable.storage), - clear.eq(self._clear.storage), - self._pointer.status.eq(pointer), - ] - -# Trace Debugger ----------------------------------------------------------------------------------- - -class TraceDebugger(LiteXModule): - def __init__(self): - self.bus = wishbone.Interface(data_width=32, address_width=32, addressing="word") - self.source = source = stream.Endpoint([("data", 32)]) - self.trace_if = trace_if = Record(trace_layout) - - apb = Record(apb_layout) - - self.bus_conv = Wishbone2APB(self.bus, apb) - - self.trace_params = dict( - # Clk / Rst. - i_clk_i = ClockSignal("sys"), - i_rst_ni = ~ResetSignal("sys"), - i_test_mode_i = 0, - - # CPU Interface. - i_ivalid_i = trace_if.ivalid, - i_iexception_i = trace_if.iexception, - i_interrupt_i = trace_if.interrupt, - i_cause_i = trace_if.cause, - i_tval_i = trace_if.tval, - i_priv_i = trace_if.priv, - i_iaddr_i = trace_if.iaddr, - i_instr_i = trace_if.instr, - i_compressed_i = trace_if.compressed, - - # APB Interface. - i_paddr_i = apb.paddr, - i_pwdata_i = apb.pwdata, - i_pwrite_i = apb.pwrite, - i_psel_i = apb.psel, - i_penable_i = apb.penable, - o_prdata_o = apb.prdata, - o_pready_o = apb.pready, - o_pslverr_o = apb.pslverr, - - # Data Output. - o_packet_word_o = source.data, - o_packet_word_valid_o = source.valid, - i_grant_i = source.ready, - ) - self.specials += Instance("trace_debugger", **self.trace_params) - - @staticmethod - def add_sources(platform): - add_manifest_sources(platform, "cv32e40p_trace_manifest.flist") - # Debug Module ------------------------------------------------------------------------------------- class DebugModule(LiteXModule): @@ -350,10 +242,6 @@ class DebugModule(LiteXModule): self.specials += Instance("dm_wrap", **self.dm_params) - @staticmethod - def add_sources(platform): - add_manifest_sources(platform, "cv32e40p_dm_manifest.flist") - # CV32E40P ----------------------------------------------------------------------------------------- class CV32E40P(CPU): @@ -368,8 +256,7 @@ class CV32E40P(CPU): linker_output_format = "elf32-littleriscv" nop = "nop" io_regions = {0x80000000: 0x80000000} # Origin, Length. - - has_fpu = ["full"] + has_fpu = ["standard+fpu"] # GCC Flags. @property @@ -379,14 +266,15 @@ class CV32E40P(CPU): return flags def __init__(self, platform, variant="standard"): - self.platform = platform - self.variant = variant - self.reset = Signal() - self.ibus = wishbone.Interface(data_width=32, address_width=32, addressing="word") - self.dbus = wishbone.Interface(data_width=32, address_width=32, addressing="word") - self.periph_buses = [self.ibus, self.dbus] - self.memory_buses = [] - self.interrupt = Signal(15) + self.platform = platform + self.variant = variant + self.reset = Signal() + self.ibus = wishbone.Interface(data_width=32, address_width=32, addressing="word") + self.dbus = wishbone.Interface(data_width=32, address_width=32, addressing="word") + self.periph_buses = [self.ibus, self.dbus] + self.memory_buses = [] + self.interrupt = Signal(16) + self.interrupt_padding = Signal(16) ibus = Record(obi_layout) dbus = Record(obi_layout) @@ -406,11 +294,12 @@ class CV32E40P(CPU): i_rst_ni = ~ResetSignal("sys"), # Controls. - i_clock_en_i = 1, - i_test_en_i = 0, - i_fregfile_disable_i = 0, - i_core_id_i = 0, - i_cluster_id_i = 0, + i_pulp_clock_en_i = 1, + i_scan_cg_en_i = 0, + i_mtvec_addr_i = 0, + i_dm_halt_addr_i = 0, + i_hart_id_i = 0, + i_dm_exception_addr_i = 0, # IBus. o_instr_req_o = ibus.req, @@ -429,56 +318,32 @@ class CV32E40P(CPU): o_data_wdata_o = dbus.wdata, i_data_rdata_i = dbus.rdata, - # APU. - i_apu_master_gnt_i = 0, - i_apu_master_valid_i = 0, - # IRQ. - i_irq_sec_i = 0, - i_irq_software_i = 0, - i_irq_external_i = 0, - i_irq_fast_i = self.interrupt, - i_irq_nmi_i = 0, - i_irq_fastx_i = 0, + i_irq_i = Cat(self.interrupt_padding, self.interrupt), # Debug. - i_debug_req_i = 0, + i_debug_req_i = 0, # CPU Control. - i_fetch_enable_i = 1, + i_fetch_enable_i = 1, ) # Add Verilog sources. - add_manifest_sources(platform, 'cv32e40p_manifest.flist') - - # Specific FPU variant parameters/files. if variant in self.has_fpu: + # Specific FPU variant parameters/files. self.cpu_params.update(p_FPU=1) add_manifest_sources(platform, 'cv32e40p_fpu_manifest.flist') + else: + add_manifest_sources(platform, 'cv32e40p_manifest.flist') def add_debug_module(self, dm): self.cpu_params.update(i_debug_req_i=dm.debug_req) self.cpu_params.update(i_rst_ni=~(ResetSignal("sys") | dm.ndmreset)) - def add_trace_core(self, trace): - trace_if = trace.trace_if - - self.cpu_params.update( - o_ivalid_o = trace_if.ivalid, - o_iexception_o = trace_if.iexception, - o_interrupt_o = trace_if.interrupt, - o_cause_o = trace_if.cause, - o_tval_o = trace_if.tval, - o_priv_o = trace_if.priv, - o_iaddr_o = trace_if.iaddr, - o_instr_o = trace_if.instr, - o_compressed_o = trace_if.compressed, - ) - def set_reset_address(self, reset_address): self.reset_address = reset_address self.cpu_params.update(i_boot_addr_i=Signal(32, reset=reset_address)) def do_finalize(self): assert hasattr(self, "reset_address") - self.specials += Instance("riscv_core", **self.cpu_params) + self.specials += Instance("cv32e40p_core", **self.cpu_params) diff --git a/litex/soc/cores/cpu/cv32e40p/crt0.S b/litex/soc/cores/cpu/cv32e40p/crt0.S index 904cecde8..0773c4b5a 100644 --- a/litex/soc/cores/cpu/cv32e40p/crt0.S +++ b/litex/soc/cores/cpu/cv32e40p/crt0.S @@ -46,7 +46,7 @@ vector_table: j trap_entry # 28 firq12 j trap_entry # 29 firq13 j trap_entry # 30 firq14 - j trap_entry # 31 unused + j trap_entry # 31 firq15 .global trap_entry trap_entry: @@ -116,10 +116,8 @@ bss_loop: add a0,a0,4 j bss_loop bss_done: - - li a0, 0x7FFF0880 //7FFF0880 enable timer + external interrupt + fast interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt) + li a0, 0xFFFF0880 //FFFF0880 enable timer + external interrupt + fast interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt) csrw mie,a0 - j main infinit_loop: j infinit_loop diff --git a/litex/soc/cores/cpu/cv32e40p/csr-defs.h b/litex/soc/cores/cpu/cv32e40p/csr-defs.h index d98e8dfb7..0f1907be7 100644 --- a/litex/soc/cores/cpu/cv32e40p/csr-defs.h +++ b/litex/soc/cores/cpu/cv32e40p/csr-defs.h @@ -1,11 +1,14 @@ #ifndef CSR_DEFS__H #define CSR_DEFS__H +/*Reference : https://docs.openhwgroup.org/projects/cv32e40p-user-manual/en/latest/control_status_registers.html */ + #define CSR_MSTATUS_MIE 0x8 -#define CSR_IRQ_MASK 0xBC0 -#define CSR_IRQ_PENDING 0xFC0 - +#define CSR_IRQ_MASK 0x304 +#define CSR_IRQ_PENDING 0x344 +#define FIRQ_OFFSET 16 #define CSR_DCACHE_INFO 0xCC0 + #endif /* CSR_DEFS__H */ diff --git a/litex/soc/cores/cpu/cv32e40p/irq.h b/litex/soc/cores/cpu/cv32e40p/irq.h index f1dd4c285..c11829bfc 100644 --- a/litex/soc/cores/cpu/cv32e40p/irq.h +++ b/litex/soc/cores/cpu/cv32e40p/irq.h @@ -20,17 +20,21 @@ static inline void irq_setie(unsigned int ie) static inline unsigned int irq_getmask(void) { - return 0; // FIXME + unsigned int mask; + asm volatile ("csrr %0, %1" : "=r"(mask) : "i"(CSR_IRQ_MASK)); + return (mask >> FIRQ_OFFSET); } static inline void irq_setmask(unsigned int mask) { - // FIXME + asm volatile ("csrw %0, %1" :: "i"(CSR_IRQ_MASK), "r"(mask << FIRQ_OFFSET)); } static inline unsigned int irq_pending(void) { - return 0;// FIXME + unsigned int pending; + asm volatile ("csrr %0, %1" : "=r"(pending) : "i"(CSR_IRQ_PENDING)); + return (pending >> FIRQ_OFFSET); } #ifdef __cplusplus diff --git a/litex/soc/cores/cpu/cv32e40p/system.h b/litex/soc/cores/cpu/cv32e40p/system.h index fa134fc38..4ac36faa7 100644 --- a/litex/soc/cores/cpu/cv32e40p/system.h +++ b/litex/soc/cores/cpu/cv32e40p/system.h @@ -7,17 +7,8 @@ extern "C" { #endif -__attribute__((unused)) static void flush_cpu_icache(void) -{ - // FIXME - asm volatile("nop"); -} - -__attribute__((unused)) static void flush_cpu_dcache(void) -{ - // FIXME - asm volatile("nop"); -} +__attribute__((unused)) static void flush_cpu_icache(void){}; /* No instruction cache */ +__attribute__((unused)) static void flush_cpu_dcache(void){}; /* No instruction cache */ void flush_l2_cache(void); diff --git a/litex/soc/cores/cpu/cv32e41p/crt0.S b/litex/soc/cores/cpu/cv32e41p/crt0.S index 7ad687836..0773c4b5a 100644 --- a/litex/soc/cores/cpu/cv32e41p/crt0.S +++ b/litex/soc/cores/cpu/cv32e41p/crt0.S @@ -116,10 +116,8 @@ bss_loop: add a0,a0,4 j bss_loop bss_done: - - li a0, 0x7FFF0880 //7FFF0880 enable timer + external interrupt + fast interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt) + li a0, 0xFFFF0880 //FFFF0880 enable timer + external interrupt + fast interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt) csrw mie,a0 - j main infinit_loop: j infinit_loop diff --git a/litex/soc/cores/cpu/cv32e41p/csr-defs.h b/litex/soc/cores/cpu/cv32e41p/csr-defs.h index 7738c5d94..85c09e48f 100644 --- a/litex/soc/cores/cpu/cv32e41p/csr-defs.h +++ b/litex/soc/cores/cpu/cv32e41p/csr-defs.h @@ -4,8 +4,8 @@ #define CSR_MSTATUS_MIE 0x8 -#define CSR_IRQ_MASK 0x344 -#define CSR_IRQ_PENDING 0x304 +#define CSR_IRQ_MASK 0x304 +#define CSR_IRQ_PENDING 0x344 #define FIRQ_OFFSET 16 #define CSR_DCACHE_INFO 0xCC0 @@ -13,7 +13,7 @@ /* -For CV32E41P from https://docs.openhwgroup.org/projects/openhw-group-cv32e41p/control_status_registers.html -Machine Interrupt Pending Register (mip): CSR_IRQ_MASK: 0x344 -Machine Interrupt Enable Register (mie): CSR_IRQ_PENDING: 0x304 +For CV32E41P from https://docs.openhwgroup.org/projects/cv32e41p-user-manual/control_status_registers.html +Machine Interrupt Pending Register (mip): CSR_IRQ_PENDING: 0x344 +Machine Interrupt Enable Register (mie): CSR_IRQ_MASK: 0x304 */ diff --git a/litex/soc/cores/cpu/cva6/core.py b/litex/soc/cores/cpu/cva6/core.py index abb9882d9..d69791a63 100644 --- a/litex/soc/cores/cpu/cva6/core.py +++ b/litex/soc/cores/cpu/cva6/core.py @@ -90,6 +90,7 @@ class CVA6(CPU): def gcc_flags(self): flags = GCC_FLAGS[self.variant] flags += "-D__cva6__ " + flags += "-D__riscv_plic__ " #flags += f" -DUART_POLLING" return flags diff --git a/litex/soc/cores/cpu/cva6/irq.h b/litex/soc/cores/cpu/cva6/irq.h index 96d9c8464..2555d6e1c 100644 --- a/litex/soc/cores/cpu/cva6/irq.h +++ b/litex/soc/cores/cpu/cva6/irq.h @@ -9,15 +9,16 @@ extern "C" { #include #include -#define PLIC_SOURCE_0 0x0c000004L // source 0 priority -#define PLIC_SOURCE_1 0x0c000008L // source 1 priority -#define PLIC_PENDING 0x0c001000L // start of pending array -#define PLIC_M_ENABLE 0x0c002000L // Start of Hart 0 M-mode enables -#define PLIC_S_ENABLE 0x0c002100L // Start of Hart 0 S-mode enables -#define PLIC_M_THRESHOLD 0x0c200000L // hart 0 M-mode priority threshold -#define PLIC_M_CLAIM 0x0c200004L // hart 0 M-mode priority claim/complete -#define PLIC_S_THRESHOLD 0x0c200100L // hart 0 S-mode priority threshold -#define PLIC_S_CLAIM 0x0c200104L // hart 0 S-mode priority claim/complete +// The CVA6 uses a Platform-Level Interrupt Controller (PLIC) which +// is programmed and queried via a set of MMIO registers. + +#define PLIC_BASE 0x0c000000L // Base address and per-pin priority array +#define PLIC_PENDING 0x0c001000L // Bit field matching currently pending pins +#define PLIC_ENABLED 0x0c002000L // Bit field corresponding to the current mask +#define PLIC_THRSHLD 0x0c200000L // Per-pin priority must be >= this to trigger +#define PLIC_CLAIM 0x0c200004L // Claim & completion register address + +#define PLIC_EXT_IRQ_BASE 1 static inline unsigned int irq_getie(void) { @@ -31,12 +32,12 @@ static inline void irq_setie(unsigned int ie) static inline unsigned int irq_getmask(void) { - return *((unsigned int *)PLIC_M_ENABLE) >> 1; + return *((unsigned int *)PLIC_ENABLED) >> 1; } static inline void irq_setmask(unsigned int mask) { - *((unsigned int *)PLIC_M_ENABLE) = mask << 1; + *((unsigned int *)PLIC_ENABLED) = mask << 1; } static inline unsigned int irq_pending(void) diff --git a/litex/soc/cores/cpu/eos_s3/core.py b/litex/soc/cores/cpu/eos_s3/core.py index 7cc208b08..9d3551912 100644 --- a/litex/soc/cores/cpu/eos_s3/core.py +++ b/litex/soc/cores/cpu/eos_s3/core.py @@ -110,10 +110,10 @@ class EOS_S3(CPU): # Clocking. # --------- - o_Sys_Clk0 = eos_s3_0_clk, - o_Sys_Clk0_Rst = eos_s3_0_rst, - o_Sys_Clk1 = eos_s3_1_clk, - o_Sys_Clk1_Rst = eos_s3_1_rst, + o_Clk_C16 = eos_s3_0_clk, + o_Clk_C16_Rst = eos_s3_0_rst, + o_Clk_C21 = eos_s3_1_clk, + o_Clk_C21_Rst = eos_s3_1_rst, # Packet FIFO. # ------------ diff --git a/litex/soc/cores/cpu/naxriscv/core.py b/litex/soc/cores/cpu/naxriscv/core.py index 884f91bf1..e362fbf0a 100755 --- a/litex/soc/cores/cpu/naxriscv/core.py +++ b/litex/soc/cores/cpu/naxriscv/core.py @@ -93,7 +93,7 @@ class NaxRiscv(CPU): def gcc_flags(self): flags = f" -march={NaxRiscv.get_arch()} -mabi={NaxRiscv.get_abi()}" flags += f" -D__NaxRiscv__" - flags += f" -DUART_POLLING" + flags += f" -D__riscv_plic__" return flags # Reserved Interrupts. @@ -294,7 +294,11 @@ class NaxRiscv(CPU): @staticmethod - def git_setup(name, dir, repo, branch, hash): + def git_setup(name, dir, repo, branch, hash, update): + if update == "no": + return + if "recommended" not in update: + hash = "" if not os.path.exists(dir): # Clone Repo. print(f"Cloning {name} Git repository...") @@ -306,7 +310,7 @@ class NaxRiscv(CPU): print(f"Updating {name} Git repository...") cwd = os.getcwd() os.chdir(os.path.join(dir)) - wipe_cmd = "&& git clean --force -d -x && git reset --hard" if "wipe" in NaxRiscv.update_repo else "" + wipe_cmd = "&& git clean --force -d -x && git reset --hard" if "wipe" in update else "" checkout_cmd = f"&& git checkout {hash}" if hash is not None else "" subprocess.check_call(f"cd {dir} {wipe_cmd} && git checkout {branch} && git submodule init && git pull --recurse-submodules {checkout_cmd}", shell=True) os.chdir(cwd) @@ -316,10 +320,8 @@ class NaxRiscv(CPU): def generate_netlist(reset_address): vdir = get_data_mod("cpu", "naxriscv").data_location ndir = os.path.join(vdir, "ext", "NaxRiscv") - sdir = os.path.join(vdir, "ext", "SpinalHDL") - if NaxRiscv.update_repo != "no": - NaxRiscv.git_setup("NaxRiscv", ndir, "https://github.com/SpinalHDL/NaxRiscv.git", "main", "ec3ee4dc" if NaxRiscv.update_repo=="recommended" else None) + NaxRiscv.git_setup("NaxRiscv", ndir, "https://github.com/SpinalHDL/NaxRiscv.git", "main", "ba63ee6d", NaxRiscv.update_repo) gen_args = [] gen_args.append(f"--netlist-name={NaxRiscv.netlist_name}") @@ -365,6 +367,7 @@ class NaxRiscv(CPU): # Add RAM. # By default, use Generic RAM implementation. ram_filename = "Ram_1w_1rs_Generic.v" + lutram_filename = "Ram_1w_1ra_Generic.v" # On Altera/Intel platforms, use specific implementation. from litex.build.altera import AlteraPlatform if isinstance(platform, AlteraPlatform): @@ -374,6 +377,8 @@ class NaxRiscv(CPU): if isinstance(platform, EfinixPlatform): ram_filename = "Ram_1w_1rs_Efinix.v" platform.add_source(os.path.join(vdir, ram_filename), "verilog") + platform.add_source(os.path.join(vdir, lutram_filename), "verilog") + # Add Cluster. platform.add_source(os.path.join(vdir, self.netlist_name + ".v"), "verilog") @@ -399,13 +404,13 @@ class NaxRiscv(CPU): if NaxRiscv.jtag_tap: self.jtag_tms = Signal() - self.jtag_tck = Signal() + self.jtag_clk = Signal() self.jtag_tdi = Signal() self.jtag_tdo = Signal() self.cpu_params.update( i_jtag_tms = self.jtag_tms, - i_jtag_tck = self.jtag_tck, + i_jtag_tck = self.jtag_clk, i_jtag_tdi = self.jtag_tdi, o_jtag_tdo = self.jtag_tdo, ) @@ -452,10 +457,21 @@ class NaxRiscv(CPU): # Reset SoC's CRG when debug_ndmreset rising edge. self.sync.debug_por += debug_ndmreset_last.eq(debug_ndmreset) self.comb += debug_ndmreset_rise.eq(debug_ndmreset & ~debug_ndmreset_last) - self.comb += If(debug_ndmreset_rise, soc.crg.rst.eq(1)) + if soc.get_build_name() == "sim": + self.comb += If(debug_ndmreset_rise, soc.crg.cd_sys.rst.eq(1)) + else: + self.comb += If(debug_ndmreset_rise, soc.crg.rst.eq(1)) self.soc_bus = soc.bus # FIXME: Save SoC Bus instance to retrieve the final mem layout on finalization. + def add_jtag(self, pads): + self.comb += [ + self.jtag_tms.eq(pads.tms), + self.jtag_clk.eq(pads.tck), + self.jtag_tdi.eq(pads.tdi), + pads.tdo.eq(self.jtag_tdo), + ] + def add_memory_buses(self, address_width, data_width): NaxRiscv.litedram_width = data_width nax_data_width = 64 @@ -470,6 +486,16 @@ class NaxRiscv(CPU): ) self.memory_buses.append(mbus) + self.comb += mbus.aw.cache.eq(0xF) + self.comb += mbus.aw.lock.eq(0) + self.comb += mbus.aw.prot.eq(1) + self.comb += mbus.aw.qos.eq(0) + + self.comb += mbus.ar.cache.eq(0xF) + self.comb += mbus.ar.lock.eq(0) + self.comb += mbus.ar.prot.eq(1) + self.comb += mbus.ar.qos.eq(0) + self.cpu_params.update( # Memory Bus (Master). # -------------------- diff --git a/litex/soc/cores/cpu/naxriscv/crt0.S b/litex/soc/cores/cpu/naxriscv/crt0.S index b27df7f47..6e6dc1cb2 100644 --- a/litex/soc/cores/cpu/naxriscv/crt0.S +++ b/litex/soc/cores/cpu/naxriscv/crt0.S @@ -12,6 +12,16 @@ #define MSTATUS_FS_DIRTY (3 << 13) #define MSTATUS_FS_MASK (3 << 13) +#if __riscv_xlen == 64 +#define STORE sd +#define LOAD ld +#define WORD 8 +#else +#define STORE sw +#define LOAD lw +#define WORD 4 +#endif + _start: j crt_init nop @@ -24,41 +34,41 @@ _start: .global trap_entry trap_entry: - sw x1, - 1*4(sp) - sw x5, - 2*4(sp) - sw x6, - 3*4(sp) - sw x7, - 4*4(sp) - sw x10, - 5*4(sp) - sw x11, - 6*4(sp) - sw x12, - 7*4(sp) - sw x13, - 8*4(sp) - sw x14, - 9*4(sp) - sw x15, -10*4(sp) - sw x16, -11*4(sp) - sw x17, -12*4(sp) - sw x28, -13*4(sp) - sw x29, -14*4(sp) - sw x30, -15*4(sp) - sw x31, -16*4(sp) - addi sp,sp,-16*4 + STORE x1, - 1*WORD(sp) + STORE x5, - 2*WORD(sp) + STORE x6, - 3*WORD(sp) + STORE x7, - 4*WORD(sp) + STORE x10, - 5*WORD(sp) + STORE x11, - 6*WORD(sp) + STORE x12, - 7*WORD(sp) + STORE x13, - 8*WORD(sp) + STORE x14, - 9*WORD(sp) + STORE x15, -10*WORD(sp) + STORE x16, -11*WORD(sp) + STORE x17, -12*WORD(sp) + STORE x28, -13*WORD(sp) + STORE x29, -14*WORD(sp) + STORE x30, -15*WORD(sp) + STORE x31, -16*WORD(sp) + addi sp,sp,-16*WORD call isr - lw x1 , 15*4(sp) - lw x5, 14*4(sp) - lw x6, 13*4(sp) - lw x7, 12*4(sp) - lw x10, 11*4(sp) - lw x11, 10*4(sp) - lw x12, 9*4(sp) - lw x13, 8*4(sp) - lw x14, 7*4(sp) - lw x15, 6*4(sp) - lw x16, 5*4(sp) - lw x17, 4*4(sp) - lw x28, 3*4(sp) - lw x29, 2*4(sp) - lw x30, 1*4(sp) - lw x31, 0*4(sp) - addi sp,sp,16*4 + LOAD x1 , 15*WORD(sp) + LOAD x5, 14*WORD(sp) + LOAD x6, 13*WORD(sp) + LOAD x7, 12*WORD(sp) + LOAD x10, 11*WORD(sp) + LOAD x11, 10*WORD(sp) + LOAD x12, 9*WORD(sp) + LOAD x13, 8*WORD(sp) + LOAD x14, 7*WORD(sp) + LOAD x15, 6*WORD(sp) + LOAD x16, 5*WORD(sp) + LOAD x17, 4*WORD(sp) + LOAD x28, 3*WORD(sp) + LOAD x29, 2*WORD(sp) + LOAD x30, 1*WORD(sp) + LOAD x31, 0*WORD(sp) + addi sp,sp,16*WORD mret .text @@ -113,8 +123,10 @@ bss_loop: j bss_loop bss_done: - li a0, 0x880 //880 enable timer + external interrupt sources (until mstatus.MIE is set, they will never trigger an interrupt) - csrw mie,a0 + call plic_init // initialize external interrupt controller + li t0, 0x800 // external interrupt sources only (using LiteX timer); + // NOTE: must still enable mstatus.MIE! + csrw mie,t0 call main infinit_loop: diff --git a/litex/soc/cores/cpu/naxriscv/irq.h b/litex/soc/cores/cpu/naxriscv/irq.h index 558adc4f1..a8325ec46 100644 --- a/litex/soc/cores/cpu/naxriscv/irq.h +++ b/litex/soc/cores/cpu/naxriscv/irq.h @@ -9,30 +9,40 @@ extern "C" { #include #include +// NaxRiscv uses a Platform-Level Interrupt Controller (PLIC) which +// is programmed and queried via a set of MMIO registerss + +#define PLIC_BASE 0xf0c00000L // Base address and per-pin priority array +#define PLIC_PENDING 0xf0c01000L // Bit field matching currently pending pins +#define PLIC_ENABLED 0xf0c02000L // Bit field corresponding to the current mask +#define PLIC_THRSHLD 0xf0e00000L // Per-pin priority must be >= this to trigger +#define PLIC_CLAIM 0xf0e00004L // Claim & completion register address + +#define PLIC_EXT_IRQ_BASE 0 + static inline unsigned int irq_getie(void) { - return 0; + return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0; } static inline void irq_setie(unsigned int ie) { - + if(ie) csrs(mstatus,CSR_MSTATUS_MIE); else csrc(mstatus,CSR_MSTATUS_MIE); } static inline unsigned int irq_getmask(void) { - - return 0; + return *((unsigned int *)PLIC_ENABLED) >> PLIC_EXT_IRQ_BASE; } static inline void irq_setmask(unsigned int mask) { - + *((unsigned int *)PLIC_ENABLED) = mask << PLIC_EXT_IRQ_BASE; } static inline unsigned int irq_pending(void) { - return 0; + return *((unsigned int *)PLIC_PENDING) >> PLIC_EXT_IRQ_BASE; } #ifdef __cplusplus diff --git a/litex/soc/cores/cpu/neorv32/core.py b/litex/soc/cores/cpu/neorv32/core.py index a3f953048..174f39c51 100644 --- a/litex/soc/cores/cpu/neorv32/core.py +++ b/litex/soc/cores/cpu/neorv32/core.py @@ -171,11 +171,14 @@ class NEORV32(CPU): "neorv32_application_image.vhd", "neorv32_bootloader_image.vhd", "neorv32_boot_rom.vhd", + "neorv32_cache.vhd", "neorv32_cfs.vhd", + "neorv32_clockgate.vhd", "neorv32_cpu_alu.vhd", "neorv32_cpu_control.vhd", "neorv32_cpu_cp_bitmanip.vhd", "neorv32_cpu_cp_cfu.vhd", + "neorv32_cpu_cp_cond.vhd", "neorv32_cpu_cp_fpu.vhd", "neorv32_cpu_cp_muldiv.vhd", "neorv32_cpu_cp_shifter.vhd", @@ -185,7 +188,6 @@ class NEORV32(CPU): "neorv32_cpu_regfile.vhd", "neorv32_cpu.vhd", "neorv32_crc.vhd", - "neorv32_dcache.vhd", "neorv32_debug_dm.vhd", "neorv32_debug_dtm.vhd", "neorv32_dma.vhd", @@ -193,7 +195,6 @@ class NEORV32(CPU): "neorv32_fifo.vhd", "neorv32_gpio.vhd", "neorv32_gptmr.vhd", - "neorv32_icache.vhd", "neorv32_imem.entity.vhd", "neorv32_intercon.vhd", "neorv32_mtime.vhd", @@ -210,7 +211,7 @@ class NEORV32(CPU): "neorv32_twi.vhd", "neorv32_uart.vhd", "neorv32_wdt.vhd", - "neorv32_wishbone.vhd", + "neorv32_xbus.vhd", "neorv32_xip.vhd", "neorv32_xirq.vhd", ], @@ -226,8 +227,8 @@ class NEORV32(CPU): } # Download VHDL sources (if not already present). - # Version 1.8.9 - sha1 = "fdb00a5d24e256ac9a9cb29410f2653c95068c91" + # Version 1.9.7 + sha1 = "ed17ae4df64e6a5221562e4adf4de378eaf0c2e8" for directory, vhds in sources.items(): for vhd in vhds: self.vhd2v_converter.add_source(os.path.join(cdir, vhd)) diff --git a/litex/soc/cores/cpu/openc906/core.py b/litex/soc/cores/cpu/openc906/core.py index 06d32e998..b8144b81b 100644 --- a/litex/soc/cores/cpu/openc906/core.py +++ b/litex/soc/cores/cpu/openc906/core.py @@ -89,6 +89,7 @@ class OpenC906(CPU): flags = "-mno-save-restore " flags += "-march=rv64gc -mabi=lp64d " flags += "-D__openc906__ " + flags += "-D__riscv_plic__ " flags += "-mcmodel=medany" return flags diff --git a/litex/soc/cores/cpu/openc906/irq.h b/litex/soc/cores/cpu/openc906/irq.h index 9d9e4af17..8f4580b13 100644 --- a/litex/soc/cores/cpu/openc906/irq.h +++ b/litex/soc/cores/cpu/openc906/irq.h @@ -18,6 +18,8 @@ extern "C" { #define PLIC_THRSHLD 0x90200000L // Per-pin priority must be >= this to trigger #define PLIC_CLAIM 0x90200004L // Claim & completion register address +#define PLIC_EXT_IRQ_BASE 16 + static inline unsigned int irq_getie(void) { return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0; diff --git a/litex/soc/cores/cpu/picorv32/core.py b/litex/soc/cores/cpu/picorv32/core.py index d7d5f4a1b..03f7a31cb 100644 --- a/litex/soc/cores/cpu/picorv32/core.py +++ b/litex/soc/cores/cpu/picorv32/core.py @@ -168,8 +168,13 @@ class PicoRV32(CPU): self.comb += [ idbus.adr.eq(mem_addr), idbus.dat_w.eq(mem_wdata), - idbus.we.eq(mem_wstrb != 0), - idbus.sel.eq(mem_wstrb), + If(mem_wstrb != 0, + idbus.we.eq(1), + idbus.sel.eq(mem_wstrb), + ).Else( + idbus.we.eq(0), + idbus.sel.eq(0b1111), + ), idbus.cyc.eq(mem_valid), idbus.stb.eq(mem_valid), idbus.cti.eq(0), diff --git a/litex/soc/cores/cpu/rocket/core.py b/litex/soc/cores/cpu/rocket/core.py index 16d9ffdcd..31e6e30b9 100644 --- a/litex/soc/cores/cpu/rocket/core.py +++ b/litex/soc/cores/cpu/rocket/core.py @@ -113,6 +113,7 @@ class Rocket(CPU): flags = "-mno-save-restore " flags += f"-march={self.get_arch(self.variant)} -mabi=lp64 " flags += "-D__rocket__ " + flags += "-D__riscv_plic__ " flags += "-mcmodel=medany" return flags diff --git a/litex/soc/cores/cpu/rocket/irq.h b/litex/soc/cores/cpu/rocket/irq.h index 9548b01fd..83f854c86 100644 --- a/litex/soc/cores/cpu/rocket/irq.h +++ b/litex/soc/cores/cpu/rocket/irq.h @@ -18,6 +18,8 @@ extern "C" { #define PLIC_THRSHLD 0x0c200000L // Per-pin priority must be >= this to trigger #define PLIC_CLAIM 0x0c200004L // Claim & completion register address +#define PLIC_EXT_IRQ_BASE 1 + static inline unsigned int irq_getie(void) { return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0; diff --git a/litex/soc/cores/cpu/vexiiriscv/__init__.py b/litex/soc/cores/cpu/vexiiriscv/__init__.py new file mode 100644 index 000000000..bc2c8b55a --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/__init__.py @@ -0,0 +1 @@ +from litex.soc.cores.cpu.vexiiriscv.core import VexiiRiscv diff --git a/litex/soc/cores/cpu/vexiiriscv/boot-helper.S b/litex/soc/cores/cpu/vexiiriscv/boot-helper.S new file mode 100644 index 000000000..3a530dd1f --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/boot-helper.S @@ -0,0 +1,15 @@ +.section .text, "ax", @progbits +.global boot_helper +.global smp_lottery_target +.global smp_lottery_lock +.global smp_lottery_args + +boot_helper: + sw x10, smp_lottery_args , x14 + sw x11, smp_lottery_args+4, x14 + sw x12, smp_lottery_args+8, x14 + sw x13, smp_lottery_target, x14 + fence w, w + li x15, 1 + sw x15, smp_lottery_lock, x14 + jr x13 diff --git a/litex/soc/cores/cpu/vexiiriscv/core.py b/litex/soc/cores/cpu/vexiiriscv/core.py new file mode 100755 index 000000000..27593d7e3 --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/core.py @@ -0,0 +1,582 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2020-2022 Florent Kermarrec +# Copyright (c) 2020-2022 Dolu1990 +# SPDX-License-Identifier: BSD-2-Clause + +import os +import hashlib +import subprocess +import re + +from migen import * + +from litex.gen import * + +from litex import get_data_mod +from litex.soc.cores.cpu.naxriscv import NaxRiscv + +from litex.soc.interconnect import axi +from litex.soc.interconnect.csr import * +from litex.soc.integration.soc import SoCRegion + +from litex.soc.cores.cpu import CPU, CPU_GCC_TRIPLE_RISCV32, CPU_GCC_TRIPLE_RISCV64 + +# Variants ----------------------------------------------------------------------------------------- + +CPU_VARIANTS = ["cached", "linux", "debian"] + +# VexiiRiscv ----------------------------------------------------------------------------------------- + +class VexiiRiscv(CPU): + category = "softcore" + family = "riscv" + name = "vexiiriscv" + human_name = "VexiiRiscv" + variants = CPU_VARIANTS + data_width = 32 + endianness = "little" + gcc_triple = CPU_GCC_TRIPLE_RISCV32 + linker_output_format = "elf32-littleriscv" + nop = "nop" + io_regions = {0x8000_0000: 0x8000_0000} # Origin, Length. + + # Default parameters. + netlist_name = None + xlen = 32 + internal_bus_width = 32 + litedram_width = 32 + l2_bytes = 0 + l2_ways = 4 + l2_self_flush = None + with_rvc = False + with_rvm = False + with_rvf = False + with_rvd = False + with_rva = False + with_dma = False + with_axi3 = False + jtag_tap = False + jtag_instruction = False + vexii_args = "" + + + # ABI. + @staticmethod + def get_abi(): + abi = "lp64" if VexiiRiscv.xlen == 64 else "ilp32" + if VexiiRiscv.with_rvd: + abi +="d" + elif VexiiRiscv.with_rvf: + abi +="f" + return abi + + # Arch. + @staticmethod + def get_arch(): + arch = f"rv{VexiiRiscv.xlen}i2p0_" + if VexiiRiscv.with_rvm: + arch += "m" + if VexiiRiscv.with_rva: + arch += "a" + if VexiiRiscv.with_rvf: + arch += "f" + if VexiiRiscv.with_rvd: + arch += "d" + if VexiiRiscv.with_rvc: + arch += "c" + # arch += "zicntr" + # arch += "zicsr" + # arch += "zifencei" + # arch += "zihpm" + # arch += "sscofpmf" + return arch + + # Memory Mapping. + @property + def mem_map(self): + return { + "rom": 0x0000_0000, + "sram": 0x1000_0000, + "main_ram": 0x4000_0000, + "csr": 0xf000_0000, + "clint": 0xf001_0000, + "plic": 0xf0c0_0000, + } + + # GCC Flags. + @property + def gcc_flags(self): + flags = f" -march={VexiiRiscv.get_arch()} -mabi={VexiiRiscv.get_abi()}" + flags += f" -D__VexiiRiscv__" + flags += f" -D__riscv_plic__" + return flags + + # Reserved Interrupts. + @property + def reserved_interrupts(self): + return {"noirq": 0} + + # Command line configuration arguments. + @staticmethod + def args_fill(parser): + cpu_group = parser.add_argument_group(title="CPU options") + + cpu_group.add_argument("--vexii-args", default="", help="Specify the CPU configuration") + # cpu_group.add_argument("--xlen", default=32, help="Specify the RISC-V data width.") + cpu_group.add_argument("--cpu-count", default=1, help="How many VexiiRiscv CPU.") + cpu_group.add_argument("--with-coherent-dma", action="store_true", help="Enable coherent DMA accesses.") + cpu_group.add_argument("--with-jtag-tap", action="store_true", help="Add a embedded JTAG tap for debugging.") + cpu_group.add_argument("--with-jtag-instruction", action="store_true", help="Add a JTAG instruction port which implement tunneling for debugging (TAP not included).") + cpu_group.add_argument("--update-repo", default="recommended", choices=["latest","wipe+latest","recommended","wipe+recommended","no"], help="Specify how the VexiiRiscv & SpinalHDL repo should be updated (latest: update to HEAD, recommended: Update to known compatible version, no: Don't update, wipe+*: Do clean&reset before checkout)") + cpu_group.add_argument("--no-netlist-cache", action="store_true", help="Always (re-)build the netlist.") + # cpu_group.add_argument("--with-fpu", action="store_true", help="Enable the F32/F64 FPU.") + # cpu_group.add_argument("--with-rvc", action="store_true", help="Enable the Compress ISA extension.") + cpu_group.add_argument("--l2-bytes", default=0, help="VexiiRiscv L2 bytes, default 128 KB.") + cpu_group.add_argument("--l2-ways", default=0, help="VexiiRiscv L2 ways, default 8.") + cpu_group.add_argument("--l2-self-flush", default=None, help="VexiiRiscv L2 ways will self flush on from,to,cycles") + cpu_group.add_argument("--with-axi3", action="store_true", help="mbus will be axi3 instead of axi4") + + + + + @staticmethod + def args_read(args): + print(args) + + vdir = get_data_mod("cpu", "vexiiriscv").data_location + ndir = os.path.join(vdir, "ext", "VexiiRiscv") + + NaxRiscv.git_setup("VexiiRiscv", ndir, "https://github.com/SpinalHDL/VexiiRiscv.git", "dev", "36dad634", args.update_repo) + + if not args.cpu_variant: + args.cpu_variant = "standard" + + VexiiRiscv.vexii_args += " --with-mul --with-div --allow-bypass-from=0 --performance-counters=0" + VexiiRiscv.vexii_args += " --fetch-l1 --fetch-l1-ways=2" + VexiiRiscv.vexii_args += " --lsu-l1 --lsu-l1-ways=2 --with-lsu-bypass" + VexiiRiscv.vexii_args += " --relaxed-branch" + + if args.cpu_variant in ["linux", "debian"]: + VexiiRiscv.vexii_args += " --with-rva --with-supervisor" + VexiiRiscv.vexii_args += " --fetch-l1-ways=4 --fetch-l1-mem-data-width-min=64" + VexiiRiscv.vexii_args += " --lsu-l1-ways=4 --lsu-l1-mem-data-width-min=64" + + if args.cpu_variant in ["debian"]: + VexiiRiscv.vexii_args += " --xlen=64 --with-rvc --with-rvf --with-rvd --fma-reduced-accuracy --fpu-ignore-subnormal" + + if args.cpu_variant in ["linux", "debian"]: + VexiiRiscv.vexii_args += " --with-btb --with-ras --with-gshare" + + + + VexiiRiscv.jtag_tap = args.with_jtag_tap + VexiiRiscv.jtag_instruction = args.with_jtag_instruction + VexiiRiscv.with_dma = args.with_coherent_dma + VexiiRiscv.with_axi3 = args.with_axi3 + VexiiRiscv.update_repo = args.update_repo + VexiiRiscv.no_netlist_cache = args.no_netlist_cache + VexiiRiscv.vexii_args += " " + args.vexii_args + + md5_hash = hashlib.md5() + md5_hash.update(VexiiRiscv.vexii_args.encode('utf-8')) + vexii_args_hash = md5_hash.hexdigest() + ppath = os.path.join(vdir, str(vexii_args_hash) + ".py") + if VexiiRiscv.no_netlist_cache or not os.path.exists(ppath): + cmd = f"""cd {ndir} && sbt "runMain vexiiriscv.soc.litex.PythonArgsGen {VexiiRiscv.vexii_args} --python-file={str(ppath)}\"""" + subprocess.check_call(cmd, shell=True) + with open(ppath) as file: + exec(file.read()) + + if VexiiRiscv.xlen == 64: + VexiiRiscv.gcc_triple = CPU_GCC_TRIPLE_RISCV64 + VexiiRiscv.linker_output_format = f"elf{VexiiRiscv.xlen}-littleriscv" + if args.cpu_count: + VexiiRiscv.cpu_count = args.cpu_count + if args.l2_bytes: + VexiiRiscv.l2_bytes = args.l2_bytes + if args.l2_ways: + VexiiRiscv.l2_ways = args.l2_ways + if args.l2_self_flush: + VexiiRiscv.l2_self_flush = args.l2_self_flush + + + def __init__(self, platform, variant): + self.platform = platform + self.variant = "standard" + self.reset = Signal() + self.interrupt = Signal(32) + self.pbus = pbus = axi.AXILiteInterface(address_width=32, data_width=32) + + self.periph_buses = [pbus] # Peripheral buses (Connected to main SoC's bus). + self.memory_buses = [] # Memory buses (Connected directly to LiteDRAM). + + # # # + + self.tracer_valid = Signal() + self.tracer_payload = Signal(8) + + # CPU Instance. + self.cpu_params = dict( + # Clk/Rst. + i_system_clk = ClockSignal("sys"), + i_system_reset = ResetSignal("sys") | self.reset, + + # Patcher/Tracer. + # o_patcher_tracer_valid = self.tracer_valid, + # o_patcher_tracer_payload = self.tracer_payload, + + # Interrupt. + i_peripheral_externalInterrupts_port = self.interrupt, + + # Peripheral Memory Bus (AXI Lite Slave). + o_pBus_awvalid = pbus.aw.valid, + i_pBus_awready = pbus.aw.ready, + o_pBus_awaddr = pbus.aw.addr, + o_pBus_awprot = Open(), + o_pBus_wvalid = pbus.w.valid, + i_pBus_wready = pbus.w.ready, + o_pBus_wdata = pbus.w.data, + o_pBus_wstrb = pbus.w.strb, + i_pBus_bvalid = pbus.b.valid, + o_pBus_bready = pbus.b.ready, + i_pBus_bresp = pbus.b.resp, + o_pBus_arvalid = pbus.ar.valid, + i_pBus_arready = pbus.ar.ready, + o_pBus_araddr = pbus.ar.addr, + o_pBus_arprot = Open(), + i_pBus_rvalid = pbus.r.valid, + o_pBus_rready = pbus.r.ready, + i_pBus_rdata = pbus.r.data, + i_pBus_rresp = pbus.r.resp, + ) + + if VexiiRiscv.with_dma: + self.dma_bus = dma_bus = axi.AXIInterface(data_width=VexiiRiscv.internal_bus_width, address_width=32, id_width=4) + + self.cpu_params.update( + # DMA Bus. + # -------- + # AW Channel. + o_dma_bus_awready = dma_bus.aw.ready, + i_dma_bus_awvalid = dma_bus.aw.valid, + i_dma_bus_awid = dma_bus.aw.id, + i_dma_bus_awaddr = dma_bus.aw.addr, + i_dma_bus_awlen = dma_bus.aw.len, + i_dma_bus_awsize = dma_bus.aw.size, + i_dma_bus_awburst = dma_bus.aw.burst, + i_dma_bus_awlock = dma_bus.aw.lock, + i_dma_bus_awcache = dma_bus.aw.cache, + i_dma_bus_awprot = dma_bus.aw.prot, + i_dma_bus_awqos = dma_bus.aw.qos, + + # W Channel. + o_dma_bus_wready = dma_bus.w.ready, + i_dma_bus_wvalid = dma_bus.w.valid, + i_dma_bus_wdata = dma_bus.w.data, + i_dma_bus_wstrb = dma_bus.w.strb, + i_dma_bus_wlast = dma_bus.w.last, + + # B Channel. + i_dma_bus_bready = dma_bus.b.ready, + o_dma_bus_bvalid = dma_bus.b.valid, + o_dma_bus_bid = dma_bus.b.id, + o_dma_bus_bresp = dma_bus.b.resp, + + # AR Channel. + o_dma_bus_arready = dma_bus.ar.ready, + i_dma_bus_arvalid = dma_bus.ar.valid, + i_dma_bus_arid = dma_bus.ar.id, + i_dma_bus_araddr = dma_bus.ar.addr, + i_dma_bus_arlen = dma_bus.ar.len, + i_dma_bus_arsize = dma_bus.ar.size, + i_dma_bus_arburst = dma_bus.ar.burst, + i_dma_bus_arlock = dma_bus.ar.lock, + i_dma_bus_arcache = dma_bus.ar.cache, + i_dma_bus_arprot = dma_bus.ar.prot, + i_dma_bus_arqos = dma_bus.ar.qos, + + # R Channel. + i_dma_bus_rready = dma_bus.r.ready, + o_dma_bus_rvalid = dma_bus.r.valid, + o_dma_bus_rid = dma_bus.r.id, + o_dma_bus_rdata = dma_bus.r.data, + o_dma_bus_rresp = dma_bus.r.resp, + o_dma_bus_rlast = dma_bus.r.last, + ) + + def set_reset_address(self, reset_address): + VexiiRiscv.reset_address = reset_address + VexiiRiscv.vexii_args += f" --reset-vector {reset_address}" + + # Cluster Name Generation. + @staticmethod + def generate_netlist_name(): + md5_hash = hashlib.md5() + md5_hash.update(str(VexiiRiscv.reset_address).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.litedram_width).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.xlen).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.cpu_count).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.l2_bytes).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.l2_ways).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.l2_self_flush).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.jtag_tap).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.jtag_instruction).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.with_dma).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.with_axi3).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.memory_regions).encode('utf-8')) + md5_hash.update(str(VexiiRiscv.vexii_args).encode('utf-8')) + # md5_hash.update(str(VexiiRiscv.internal_bus_width).encode('utf-8')) + + + digest = md5_hash.hexdigest() + VexiiRiscv.netlist_name = "VexiiRiscvLitex_" + digest + + # Netlist Generation. + @staticmethod + def generate_netlist(): + vdir = get_data_mod("cpu", "vexiiriscv").data_location + ndir = os.path.join(vdir, "ext", "VexiiRiscv") + sdir = os.path.join(vdir, "ext", "SpinalHDL") + + gen_args = [] + gen_args.append(f"--netlist-name={VexiiRiscv.netlist_name}") + gen_args.append(f"--netlist-directory={vdir}") + gen_args.append(VexiiRiscv.vexii_args) + gen_args.append(f"--cpu-count={VexiiRiscv.cpu_count}") + gen_args.append(f"--l2-bytes={VexiiRiscv.l2_bytes}") + gen_args.append(f"--l2-ways={VexiiRiscv.l2_ways}") + if VexiiRiscv.l2_self_flush: + gen_args.append(f"--l2-self-flush={VexiiRiscv.l2_self_flush}") + gen_args.append(f"--litedram-width={VexiiRiscv.litedram_width}") + # gen_args.append(f"--internal_bus_width={VexiiRiscv.internal_bus_width}") + for region in VexiiRiscv.memory_regions: + gen_args.append(f"--memory-region={region[0]},{region[1]},{region[2]},{region[3]}") + if(VexiiRiscv.jtag_tap) : + gen_args.append(f"--with-jtag-tap") + if(VexiiRiscv.jtag_instruction) : + gen_args.append(f"--with-jtag-instruction") + if(VexiiRiscv.with_dma) : + gen_args.append(f"--with-dma") + if(VexiiRiscv.with_axi3) : + gen_args.append(f"--with-axi3") + + cmd = f"""cd {ndir} && sbt "runMain vexiiriscv.soc.litex.SocGen {" ".join(gen_args)}\"""" + print("VexiiRiscv generation command :") + print(cmd) + subprocess.check_call(cmd, shell=True) + + + def add_sources(self, platform): + vdir = get_data_mod("cpu", "vexiiriscv").data_location + print(f"VexiiRiscv netlist : {self.netlist_name}") + + if VexiiRiscv.no_netlist_cache or not os.path.exists(os.path.join(vdir, self.netlist_name + ".v")): + self.generate_netlist() + + # Add RAM. + # By default, use Generic RAM implementation. + ram_filename = "Ram_1w_1rs_Generic.v" + lutram_filename = "Ram_1w_1ra_Generic.v" + # On Altera/Intel platforms, use specific implementation. + from litex.build.altera import AlteraPlatform + if isinstance(platform, AlteraPlatform): + ram_filename = "Ram_1w_1rs_Intel.v" + # On Efinix platforms, use specific implementation. + from litex.build.efinix import EfinixPlatform + if isinstance(platform, EfinixPlatform): + ram_filename = "Ram_1w_1rs_Efinix.v" + platform.add_source(os.path.join(vdir, ram_filename), "verilog") + platform.add_source(os.path.join(vdir, lutram_filename), "verilog") + + # Add Cluster. + platform.add_source(os.path.join(vdir, self.netlist_name + ".v"), "verilog") + + def add_soc_components(self, soc): + # Set Human-name. + self.human_name = f"{self.human_name} {self.xlen}-bit" + + # Set UART/Timer0 CSRs to the ones used by OpenSBI. + soc.csr.add("uart", n=2) + soc.csr.add("timer0", n=3) + + # Add OpenSBI region. + soc.bus.add_region("opensbi", SoCRegion(origin=self.mem_map["main_ram"] + 0x00f0_0000, size=0x8_0000, cached=True, linker=True)) + + # Define ISA. + soc.add_config("CPU_COUNT", VexiiRiscv.cpu_count) + soc.add_config("CPU_ISA", VexiiRiscv.get_arch()) + soc.add_config("CPU_MMU", {32 : "sv32", 64 : "sv39"}[VexiiRiscv.xlen]) + + soc.bus.add_region("plic", SoCRegion(origin=soc.mem_map.get("plic"), size=0x40_0000, cached=False, linker=True)) + soc.bus.add_region("clint", SoCRegion(origin=soc.mem_map.get("clint"), size= 0x1_0000, cached=False, linker=True)) + + if VexiiRiscv.jtag_tap: + self.jtag_tms = Signal() + self.jtag_clk = Signal() + self.jtag_tdi = Signal() + self.jtag_tdo = Signal() + + self.cpu_params.update( + i_debug_tap_jtag_tms = self.jtag_tms, + i_debug_tap_jtag_tck = self.jtag_clk, + i_debug_tap_jtag_tdi = self.jtag_tdi, + o_debug_tap_jtag_tdo = self.jtag_tdo, + ) + + if VexiiRiscv.jtag_instruction: + self.jtag_clk = Signal() + self.jtag_enable = Signal() + self.jtag_capture = Signal() + self.jtag_shift = Signal() + self.jtag_update = Signal() + self.jtag_reset = Signal() + self.jtag_tdo = Signal() + self.jtag_tdi = Signal() + self.cpu_params.update( + i_debug_tck = self.jtag_clk, + i_debug_instruction_instruction_enable = self.jtag_enable, + i_debug_instruction_instruction_capture = self.jtag_capture, + i_debug_instruction_instruction_shift = self.jtag_shift, + i_debug_instruction_instruction_update = self.jtag_update, + i_debug_instruction_instruction_reset = self.jtag_reset, + i_debug_instruction_instruction_tdi = self.jtag_tdi, + o_debug_instruction_instruction_tdo = self.jtag_tdo, + ) + + if VexiiRiscv.jtag_instruction or VexiiRiscv.jtag_tap: + # Create PoR Clk Domain for debug_reset. + self.cd_debug_por = ClockDomain() + self.comb += self.cd_debug_por.clk.eq(ClockSignal("sys")) + + # Create PoR debug_reset. + debug_reset = Signal(reset=1) + self.sync.debug_por += debug_reset.eq(0) + + # Debug resets. + debug_ndmreset = Signal() + debug_ndmreset_last = Signal() + debug_ndmreset_rise = Signal() # debug_ndmreset_rise is necessary because the PLL which generate the clock will be reseted aswell, so we need to sneak in a single cycle reset :( + self.cpu_params.update( + i_debugReset = debug_reset, + o_debug_dm_ndmreset = debug_ndmreset, + ) + + # Reset SoC's CRG when debug_ndmreset rising edge. + self.sync.debug_por += debug_ndmreset_last.eq(debug_ndmreset) + self.comb += debug_ndmreset_rise.eq(debug_ndmreset & ~debug_ndmreset_last) + if soc.get_build_name() == "sim": + self.comb += If(debug_ndmreset_rise, soc.crg.cd_sys.rst.eq(1)) + else: + self.comb += If(debug_ndmreset_rise, soc.crg.rst.eq(1)) + + self.soc_bus = soc.bus # FIXME: Save SoC Bus instance to retrieve the final mem layout on finalization. + + def add_memory_buses(self, address_width, data_width): + VexiiRiscv.litedram_width = data_width + + mbus = axi.AXIInterface( + data_width = VexiiRiscv.litedram_width, + address_width = 32, + id_width = 8, + version = "axi3" if VexiiRiscv.with_axi3 else "axi4" + ) + self.memory_buses.append(mbus) + + self.comb += mbus.aw.cache.eq(0xF) + self.comb += mbus.aw.lock.eq(0) + self.comb += mbus.aw.prot.eq(1) + self.comb += mbus.aw.qos.eq(0) + #self.comb += mbus.aw.region.eq(0) + + self.comb += mbus.ar.cache.eq(0xF) + self.comb += mbus.ar.lock.eq(0) + self.comb += mbus.ar.prot.eq(1) + self.comb += mbus.ar.qos.eq(0) + #self.comb += mbus.ar.region.eq(0) + + self.cpu_params.update( + # Memory Bus (Master). + # -------------------- + # AW Channel. + o_mBus_awvalid = mbus.aw.valid, + i_mBus_awready = mbus.aw.ready, + o_mBus_awaddr = mbus.aw.addr, + o_mBus_awid = mbus.aw.id, + o_mBus_awlen = mbus.aw.len, + o_mBus_awsize = mbus.aw.size, + o_mBus_awburst = mbus.aw.burst, + o_mBus_awallStrb = Open(), + # W Channel. + o_mBus_wvalid = mbus.w.valid, + i_mBus_wready = mbus.w.ready, + o_mBus_wdata = mbus.w.data, + o_mBus_wstrb = mbus.w.strb, + o_mBus_wlast = mbus.w.last, + # B Channel. + i_mBus_bvalid = mbus.b.valid, + o_mBus_bready = mbus.b.ready, + i_mBus_bid = mbus.b.id, + i_mBus_bresp = mbus.b.resp, + # AR Channel. + o_mBus_arvalid = mbus.ar.valid, + i_mBus_arready = mbus.ar.ready, + o_mBus_araddr = mbus.ar.addr, + o_mBus_arid = mbus.ar.id, + o_mBus_arlen = mbus.ar.len, + o_mBus_arsize = mbus.ar.size, + o_mBus_arburst = mbus.ar.burst, + # R Channel. + i_mBus_rvalid = mbus.r.valid, + o_mBus_rready = mbus.r.ready, + i_mBus_rdata = mbus.r.data, + i_mBus_rid = mbus.r.id, + i_mBus_rresp = mbus.r.resp, + i_mBus_rlast = mbus.r.last, + ) + + if VexiiRiscv.with_axi3: + self.cpu_params.update( + o_mBus_wid=mbus.w.id + ) + + def add_jtag(self, pads): + self.comb += [ + self.jtag_tms.eq(pads.tms), + self.jtag_clk.eq(pads.tck), + self.jtag_tdi.eq(pads.tdi), + pads.tdo.eq(self.jtag_tdo), + ] + + def do_finalize(self): + assert hasattr(self, "reset_address") + + # Generate memory map from CPU perspective + # vexiiriscv modes: + # r,w,x,c : readable, writeable, executable, caching allowed + # io : IO region (Implies P bus, preserve memory order, no dcache) + # vexiiriscv bus: + # p : peripheral + # m : memory + VexiiRiscv.memory_regions = [] + # for name, region in self.soc_bus.io_regions.items(): + # VexiiRiscv.memory_regions.append( (region.origin, region.size, "io", "p") ) # IO is only allowed on the p bus + for name, region in self.soc_bus.regions.items(): + if region.linker: # Remove virtual regions. + continue + if len(self.memory_buses) and name == 'main_ram': # m bus + bus = "m" + else: + bus = "p" + mode = region.mode + mode += "c" if region.cached else "" + VexiiRiscv.memory_regions.append( (region.origin, region.size, mode, bus) ) + + self.generate_netlist_name() + + # Do verilog instance. + self.specials += Instance(self.netlist_name, **self.cpu_params) + + # Add verilog sources. + self.add_sources(self.platform) diff --git a/litex/soc/cores/cpu/vexiiriscv/crt0.S b/litex/soc/cores/cpu/vexiiriscv/crt0.S new file mode 100644 index 000000000..09007caee --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/crt0.S @@ -0,0 +1,141 @@ +.global main +.global isr +.global _start + +.global smp_lottery_target +.global smp_lottery_lock +.global smp_lottery_args +.global smp_slave + +#define MSTATUS_FS_INITIAL (1 << 13) +#define MSTATUS_FS_CLEAN (2 << 13) +#define MSTATUS_FS_DIRTY (3 << 13) +#define MSTATUS_FS_MASK (3 << 13) + + +#if __riscv_xlen == 64 +#define STORE sd +#define LOAD ld +#define WORD 8 +#else +#define STORE sw +#define LOAD lw +#define WORD 4 +#endif + +_start: + j crt_init + nop + nop + nop + nop + nop + nop + nop + +.global trap_entry +trap_entry: + STORE x1, - 1*WORD(sp) + STORE x5, - 2*WORD(sp) + STORE x6, - 3*WORD(sp) + STORE x7, - 4*WORD(sp) + STORE x10, - 5*WORD(sp) + STORE x11, - 6*WORD(sp) + STORE x12, - 7*WORD(sp) + STORE x13, - 8*WORD(sp) + STORE x14, - 9*WORD(sp) + STORE x15, -10*WORD(sp) + STORE x16, -11*WORD(sp) + STORE x17, -12*WORD(sp) + STORE x28, -13*WORD(sp) + STORE x29, -14*WORD(sp) + STORE x30, -15*WORD(sp) + STORE x31, -16*WORD(sp) + addi sp,sp,-16*WORD + call isr + LOAD x1 , 15*WORD(sp) + LOAD x5, 14*WORD(sp) + LOAD x6, 13*WORD(sp) + LOAD x7, 12*WORD(sp) + LOAD x10, 11*WORD(sp) + LOAD x11, 10*WORD(sp) + LOAD x12, 9*WORD(sp) + LOAD x13, 8*WORD(sp) + LOAD x14, 7*WORD(sp) + LOAD x15, 6*WORD(sp) + LOAD x16, 5*WORD(sp) + LOAD x17, 4*WORD(sp) + LOAD x28, 3*WORD(sp) + LOAD x29, 2*WORD(sp) + LOAD x30, 1*WORD(sp) + LOAD x31, 0*WORD(sp) + addi sp,sp,16*WORD + mret + .text + + +crt_init: + la sp, _fstack + la a0, trap_entry + csrw mtvec, a0 + +enable_fpu: + li x1, MSTATUS_FS_INITIAL + csrs mstatus, x1 + + sw x0, smp_lottery_lock, a1 +smp_tyranny: + csrr a0, mhartid + beqz a0, data_init + +smp_slave: + lw a0, smp_lottery_lock + beqz a0, smp_slave + fence r, r + + .word(0x100F) //i$ flush + lw x10, smp_lottery_args + lw x11, smp_lottery_args+4 + lw x12, smp_lottery_args+8 + lw x13, smp_lottery_target + jr x13 + + +data_init: + la a0, _fdata + la a1, _edata + la a2, _fdata_rom +data_loop: + beq a0,a1,data_done + lw a3,0(a2) + sw a3,0(a0) + add a0,a0,4 + add a2,a2,4 + j data_loop +data_done: + +bss_init: + la a0, _fbss + la a1, _ebss +bss_loop: + beq a0,a1,bss_done + sw zero,0(a0) + add a0,a0,4 + j bss_loop +bss_done: + + call plic_init // initialize external interrupt controller + li t0, 0x800 // external interrupt sources only (using LiteX timer); + // NOTE: must still enable mstatus.MIE! + csrw mie,t0 + + call main +infinit_loop: + j infinit_loop + +//Initialized to avoid having them set to zero by BSS clear +.bss + smp_lottery_target: .word 0 + smp_lottery_args: .word 0; .word 0; .word 0 + smp_lottery_lock: .word 0 + diff --git a/litex/soc/cores/cpu/vexiiriscv/csr-defs.h b/litex/soc/cores/cpu/vexiiriscv/csr-defs.h new file mode 100644 index 000000000..54370b1ed --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/csr-defs.h @@ -0,0 +1,9 @@ +#ifndef CSR_DEFS__H +#define CSR_DEFS__H + +#define CSR_MSTATUS_MIE 0x8 + +#define CSR_IRQ_MASK 0xBC0 +#define CSR_IRQ_PENDING 0xFC0 + +#endif /* CSR_DEFS__H */ diff --git a/litex/soc/cores/cpu/vexiiriscv/irq.h b/litex/soc/cores/cpu/vexiiriscv/irq.h new file mode 100644 index 000000000..a29729ba2 --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/irq.h @@ -0,0 +1,52 @@ +#ifndef __IRQ_H +#define __IRQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +// VexiiRiscv uses a Platform-Level Interrupt Controller (PLIC) which +// is programmed and queried via a set of MMIO registerss + +#define PLIC_BASE 0xf0c00000L // Base address and per-pin priority array +#define PLIC_PENDING 0xf0c01000L // Bit field matching currently pending pins +#define PLIC_ENABLED 0xf0c02000L // Bit field corresponding to the current mask +#define PLIC_THRSHLD 0xf0e00000L // Per-pin priority must be >= this to trigger +#define PLIC_CLAIM 0xf0e00004L // Claim & completion register address + +#define PLIC_EXT_IRQ_BASE 0 + +static inline unsigned int irq_getie(void) +{ + return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0; +} + +static inline void irq_setie(unsigned int ie) +{ + if(ie) csrs(mstatus,CSR_MSTATUS_MIE); else csrc(mstatus,CSR_MSTATUS_MIE); +} + +static inline unsigned int irq_getmask(void) +{ + return *((unsigned int *)PLIC_ENABLED) >> PLIC_EXT_IRQ_BASE; +} + +static inline void irq_setmask(unsigned int mask) +{ + *((unsigned int *)PLIC_ENABLED) = mask << PLIC_EXT_IRQ_BASE; +} + +static inline unsigned int irq_pending(void) +{ + return *((unsigned int *)PLIC_PENDING) >> PLIC_EXT_IRQ_BASE; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __IRQ_H */ diff --git a/litex/soc/cores/cpu/vexiiriscv/system.h b/litex/soc/cores/cpu/vexiiriscv/system.h new file mode 100644 index 000000000..38c7abe9a --- /dev/null +++ b/litex/soc/cores/cpu/vexiiriscv/system.h @@ -0,0 +1,55 @@ +#ifndef __SYSTEM_H +#define __SYSTEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +__attribute__((unused)) static void flush_cpu_icache(void) +{ + asm volatile( + "fence.i\n" + ); +} + +__attribute__((unused)) static void flush_cpu_dcache(void) +{ + //asm volatile(".word(0x500F)\n"); +} + +void flush_l2_cache(void); + +void busy_wait(unsigned int ms); +void busy_wait_us(unsigned int us); + +#include + +#define csrr(reg) ({ unsigned long __tmp; \ + asm volatile ("csrr %0, " #reg : "=r"(__tmp)); \ + __tmp; }) + +#define csrw(reg, val) ({ \ + if (__builtin_constant_p(val) && (unsigned long)(val) < 32) \ + asm volatile ("csrw " #reg ", %0" :: "i"(val)); \ + else \ + asm volatile ("csrw " #reg ", %0" :: "r"(val)); }) + +#define csrs(reg, bit) ({ \ + if (__builtin_constant_p(bit) && (unsigned long)(bit) < 32) \ + asm volatile ("csrrs x0, " #reg ", %0" :: "i"(bit)); \ + else \ + asm volatile ("csrrs x0, " #reg ", %0" :: "r"(bit)); }) + +#define csrc(reg, bit) ({ \ + if (__builtin_constant_p(bit) && (unsigned long)(bit) < 32) \ + asm volatile ("csrrc x0, " #reg ", %0" :: "i"(bit)); \ + else \ + asm volatile ("csrrc x0, " #reg ", %0" :: "r"(bit)); }) + +#ifdef __cplusplus +} +#endif + +#endif /* __SYSTEM_H */ diff --git a/litex/soc/cores/cpu/vexriscv/core.py b/litex/soc/cores/cpu/vexriscv/core.py index d651633dc..0231e9dea 100644 --- a/litex/soc/cores/cpu/vexriscv/core.py +++ b/litex/soc/cores/cpu/vexriscv/core.py @@ -210,6 +210,7 @@ class VexRiscv(CPU, AutoCSR): self.o_cmd_ready = Signal() self.o_rsp_data = Signal(32) self.o_resetOut = Signal() + self.o_halted = Signal() reset_debug_logic = Signal() @@ -278,7 +279,8 @@ class VexRiscv(CPU, AutoCSR): i_debug_bus_cmd_payload_data = self.i_cmd_payload_data, o_debug_bus_cmd_ready = self.o_cmd_ready, o_debug_bus_rsp_data = self.o_rsp_data, - o_debug_resetOut = self.o_resetOut + o_debug_resetOut = self.o_resetOut, + o_halted = self.o_halted, ) def add_cfu(self, cfu_filename): diff --git a/litex/soc/cores/cpu/vexriscv_smp/core.py b/litex/soc/cores/cpu/vexriscv_smp/core.py index 08320049a..2432e5f74 100755 --- a/litex/soc/cores/cpu/vexriscv_smp/core.py +++ b/litex/soc/cores/cpu/vexriscv_smp/core.py @@ -168,8 +168,8 @@ class VexRiscvSMP(CPU): @property def gcc_flags(self): flags = f" -march={VexRiscvSMP.get_arch()} -mabi={VexRiscvSMP.get_abi()}" - flags += f" -D__vexriscv__" - flags += f" -DUART_POLLING" + flags += f" -D__vexriscv_smp__" + flags += f" -D__riscv_plic__" return flags # Reserved Interrupts. @@ -463,6 +463,14 @@ class VexRiscvSMP(CPU): add_synthesis_define(cluster_filename) platform.add_source(cluster_filename, "verilog") + def add_jtag(self, pads): + self.comb += [ + self.jtag_tms.eq(pads.tms), + self.jtag_clk.eq(pads.tck), + self.jtag_tdi.eq(pads.tdi), + pads.tdo.eq(self.jtag_tdo), + ] + def add_soc_components(self, soc): if self.variant == "linux": # Set UART/Timer0 CSRs to the ones used by OpenSBI. diff --git a/litex/soc/cores/cpu/vexriscv_smp/crt0.S b/litex/soc/cores/cpu/vexriscv_smp/crt0.S index ec18b8ecd..595891a6e 100644 --- a/litex/soc/cores/cpu/vexriscv_smp/crt0.S +++ b/litex/soc/cores/cpu/vexriscv_smp/crt0.S @@ -102,6 +102,11 @@ bss_loop: j bss_loop bss_done: + call plic_init // initialize external interrupt controller + li t0, 0x800 // external interrupt sources only (using LiteX timer); + // NOTE: must still enable mstatus.MIE! + csrw mie,t0 + call main infinit_loop: j infinit_loop diff --git a/litex/soc/cores/cpu/vexriscv_smp/irq.h b/litex/soc/cores/cpu/vexriscv_smp/irq.h index 558adc4f1..4c8461447 100644 --- a/litex/soc/cores/cpu/vexriscv_smp/irq.h +++ b/litex/soc/cores/cpu/vexriscv_smp/irq.h @@ -9,30 +9,40 @@ extern "C" { #include #include +// VexRiscv-SMP uses a Platform-Level Interrupt Controller (PLIC) which +// is programmed and queried via a set of MMIO registerss + +#define PLIC_BASE 0xf0c00000L // Base address and per-pin priority array +#define PLIC_PENDING 0xf0c01000L // Bit field matching currently pending pins +#define PLIC_ENABLED 0xf0c02000L // Bit field corresponding to the current mask +#define PLIC_THRSHLD 0xf0e00000L // Per-pin priority must be >= this to trigger +#define PLIC_CLAIM 0xf0e00004L // Claim & completion register address + +#define PLIC_EXT_IRQ_BASE 0 + static inline unsigned int irq_getie(void) { - return 0; + return (csrr(mstatus) & CSR_MSTATUS_MIE) != 0; } static inline void irq_setie(unsigned int ie) { - + if(ie) csrs(mstatus,CSR_MSTATUS_MIE); else csrc(mstatus,CSR_MSTATUS_MIE); } static inline unsigned int irq_getmask(void) { - - return 0; + return *((unsigned int *)PLIC_ENABLED) >> PLIC_EXT_IRQ_BASE; } static inline void irq_setmask(unsigned int mask) { - + *((unsigned int *)PLIC_ENABLED) = mask << PLIC_EXT_IRQ_BASE; } static inline unsigned int irq_pending(void) { - return 0; + return *((unsigned int *)PLIC_PENDING) >> PLIC_EXT_IRQ_BASE; } #ifdef __cplusplus diff --git a/litex/soc/cores/cpu/zynq7000/core.py b/litex/soc/cores/cpu/zynq7000/core.py index afd94559e..ad449848d 100644 --- a/litex/soc/cores/cpu/zynq7000/core.py +++ b/litex/soc/cores/cpu/zynq7000/core.py @@ -40,6 +40,7 @@ class Zynq7000(CPU): def mem_map(self): return { "sram": 0x0010_0000, # DDR in fact + "csr": 0x4000_0000, # default GP0 address on Zynq "rom": 0xfc00_0000, } @@ -54,6 +55,13 @@ class Zynq7000(CPU): self.axi_gp_slaves = [] # General Purpose AXI Slaves. self.axi_hp_slaves = [] # High Performance AXI Slaves. + # PS7 peripherals. + self.can_use = [] + + # [ 7: 0]: SPI Numbers [68:61] + # [15: 8]: SPI Numbers [91:84] + self.interrupt = Signal(16) + # # # # PS7 Clocking. @@ -62,6 +70,12 @@ class Zynq7000(CPU): # PS7 (Minimal) ---------------------------------------------------------------------------- self.ps7_name = None self.ps7_tcl = [] + self.config = { + # Enable interrupts by default + "PCW_USE_FABRIC_INTERRUPT" : 1, + "PCW_IRQ_F2P_INTR" : 1, + "PCW_NUM_F2P_INTR_INPUTS" : 16, + } ps7_rst_n = Signal() ps7_ddram_pads = platform.request("ps7_ddram") self.cpu_params = dict( @@ -95,6 +109,9 @@ class Zynq7000(CPU): # USB0. i_USB0_VBUS_PWRFAULT = 0, + # Interrupts PL -> PS. + i_IRQ_F2P = self.interrupt, + # Fabric Clk / Rst. o_FCLK_CLK0 = ClockSignal("ps7"), o_FCLK_RESET0_N = ps7_rst_n @@ -154,6 +171,10 @@ class Zynq7000(CPU): if ps7_sdio0_wp_pads is not None: self.cpu_params.update(i_SDIO0_WP = ps7_sdio0_wp_pads.wp) + # GP0 as Bus master ------------------------------------------------------------------------ + self.pbus = self.add_axi_gp_master() + self.periph_buses.append(self.pbus) + def set_ps7_xci(self, xci): # Add .xci as Vivado IP and set ps7_name from .xci filename. self.ps7_xci = xci @@ -161,16 +182,9 @@ class Zynq7000(CPU): self.platform.add_ip(xci) def add_ps7_config(self, config): - # Check that PS7 has been set. - if self.ps7_name is None: - raise Exception("Please set PS7 with set_ps7 method first.") # Config must be provided as a config, value dict. assert isinstance(config, dict) - # Add configs to PS7. - self.ps7_tcl.append("set_property -dict [list \\") - for config, value in config.items(): - self.ps7_tcl.append("CONFIG.{} {} \\".format(config, '{{' + str(value) + '}}')) - self.ps7_tcl.append(f"] [get_ips {self.ps7_name}]") + self.config.update(config) def set_ps7(self, name=None, xci=None, preset=None, config=None): # Check that PS7 has not already been set. @@ -214,8 +228,23 @@ class Zynq7000(CPU): def add_axi_gp_master(self): assert len(self.axi_gp_masters) < 2 n = len(self.axi_gp_masters) - axi_gpn = axi.AXIInterface(data_width=32, address_width=32, id_width=12) + axi_gpn = axi.AXIInterface( + data_width = 32, + address_width = 32, + id_width = 12, + version = "axi3" + ) self.axi_gp_masters.append(axi_gpn) + + self.add_ps7_config({ + f"PCW_USE_M_AXI_GP{n}": 1, + #f"PCW_M_AXI_GP{n}_FREQMHZ": 100, # FIXME: parameter? + f"PCW_M_AXI_GP{n}_ID_WIDTH": 12, + f"PCW_M_AXI_GP{n}_ENABLE_STATIC_REMAP": 0, + f"PCW_M_AXI_GP{n}_SUPPORT_NARROW_BURST": 0, + f"PCW_M_AXI_GP{n}_THREAD_ID_WIDTH": 12, + }) + self.cpu_params.update({ # AXI GP clk. f"i_M_AXI_GP{n}_ACLK" : ClockSignal("ps7"), @@ -275,7 +304,13 @@ class Zynq7000(CPU): def add_axi_gp_slave(self, clock_domain="ps7"): assert len(self.axi_gp_slaves) < 2 n = len(self.axi_gp_slaves) - axi_gpn = axi.AXIInterface(data_width=32, address_width=32, id_width=12, clock_domain=clock_domain) + axi_gpn = axi.AXIInterface( + data_width = 32, + address_width = 32, + id_width = 12, + version = "axi3", + clock_domain = clock_domain + ) self.axi_gp_slaves.append(axi_gpn) self.cpu_params.update({ #AXI S GP clk. @@ -333,14 +368,20 @@ class Zynq7000(CPU): # AXI High Performance Slave ------------------------------------------------------------------- - def add_axi_hp_slave(self): + def add_axi_hp_slave(self, clock_domain="ps7"): assert len(self.axi_hp_slaves) < 4 n = len(self.axi_hp_slaves) - axi_hpn = axi.AXIInterface(data_width=64, address_width=32, id_width=6) + axi_hpn = axi.AXIInterface( + data_width = 64, + address_width = 32, + id_width = 6, + version = "axi3", + clock_domain = clock_domain + ) self.axi_hp_slaves.append(axi_hpn) self.cpu_params.update({ # AXI HP0 clk. - f"i_S_AXI_HP{n}_ACLK" : ClockSignal("ps7"), + f"i_S_AXI_HP{n}_ACLK" : ClockSignal(clock_domain), # AXI HP0 aw. f"i_S_AXI_HP{n}_AWVALID" : axi_hpn.aw.valid, @@ -392,10 +433,59 @@ class Zynq7000(CPU): }) return axi_hpn + """ + Enable CANx peripheral. Peripheral may be optionally set + Attributes + ========== + n: int + CAN id (0, 1) + pads: + Physicals pads (tx and rx) + ext_clk: int or None + When unset/None CAN is clocked by internal clock (IO PLL). + value must be 0 <= ext_clk < 54. + ext_clk_freq: float + when ext_clk is set, external clock frequency (Hz) + """ + def add_can(self, n, pads, ext_clk=None, ext_clk_freq=None): + assert n < 2 and not n in self.can_use + assert ext_clk is None or (ext_clk < 54 and ext_clk is not None) + assert pads is not None + + # Mark as used + self.can_use.append(n) + + # PS7 configuration. + self.add_ps7_config({ + f"PCW_CAN{n}_PERIPHERAL_ENABLE": 1, + f"PCW_CAN{n}_CAN{n}_IO": "EMIO", + f"PCW_CAN{n}_GRP_CLK_ENABLE": {True: 0, False: 1}[ext_clk == None], + }) + + if ext_clk: + self.add_ps7_config({ + f"PCW_CAN{n}_GRP_CLK_IO" : f"MIO {ext_clk}", + f"PCW_CAN{n}_PERIPHERAL_FREQMHZ" : int(clk_freq / 1e6), + }) + + # PS7 connections. + self.cpu_params.update({ + f"i_CAN{n}_PHY_RX": pads.rx, + f"o_CAN{n}_PHY_TX": pads.tx, + }) + def do_finalize(self): if self.ps7_name is None: raise Exception("PS7 must be set with set_ps7 or set_ps7_xci methods.") if len(self.ps7_tcl): + # Add configs to PS7. + if len(self.config): + self.ps7_tcl.append("set_property -dict [list \\") + for config, value in self.config.items(): + self.ps7_tcl.append("CONFIG.{} {} \\".format( + config, '{{' + str(value) + '}}')) + self.ps7_tcl.append(f"] [get_ips {self.ps7_name}]") + self.ps7_tcl += [ f"upgrade_ip [get_ips {self.ps7_name}]", f"generate_target all [get_ips {self.ps7_name}]", diff --git a/litex/soc/cores/cpu/zynqmp/core.py b/litex/soc/cores/cpu/zynqmp/core.py index f143d0356..c45814b5c 100644 --- a/litex/soc/cores/cpu/zynqmp/core.py +++ b/litex/soc/cores/cpu/zynqmp/core.py @@ -4,6 +4,8 @@ # Copyright (c) 2022 Ilia Sergachev # SPDX-License-Identifier: BSD-2-Clause +import os + from migen import * from litex.gen import * @@ -37,6 +39,7 @@ class ZynqMP(CPU): def mem_map(self): return { "sram": 0x0000_0000, # DDR low in fact + "csr": 0xA000_0000, # ZynqMP M_AXI_HPM0_FPD (HPM0) "rom": 0xc000_0000, # Quad SPI memory } @@ -47,40 +50,352 @@ class ZynqMP(CPU): self.periph_buses = [] # Peripheral buses (Connected to main SoC's bus). self.memory_buses = [] # Memory buses (Connected directly to LiteDRAM). self.axi_gp_masters = [None] * 3 # General Purpose AXI Masters. + self.gem_mac = [] # GEM MAC reserved ports. + self.i2c_use = [] # I2c reserved ports. + self.uart_use = [] # UART reserved ports. + self.can_use = [] # CAN reserved/used ports. + + # [ 7: 0]: PL_PS_Group0 [128:121] + # [15: 8]: PL_PS_Group1 [143:136] + self.interrupt = Signal(16) self.cd_ps = ClockDomain() self.ps_name = "ps" self.ps_tcl = [] - self.config = {'PSU__FPGA_PL0_ENABLE': 1} # enable pl_clk0 + self.config = { + 'PSU__FPGA_PL0_ENABLE' : 1, # enable pl_clk0 + 'PSU__USE__IRQ0' : 1, # enable PL_PS_Group0 + 'PSU__NUM_F2P0__INTR__INPUTS': 8, + 'PSU__USE__IRQ1' : 1, # enable PL_PS_Group1 + 'PSU__NUM_F2P1__INTR__INPUTS': 8, + 'PSU__USE__M_AXI_GP1' : 0, + } rst_n = Signal() self.cpu_params = dict( o_pl_clk0=ClockSignal("ps"), - o_pl_resetn0=rst_n + o_pl_resetn0=rst_n, + i_pl_ps_irq0 = self.interrupt[0: 8], + i_pl_ps_irq1 = self.interrupt[8:16] ) + + # Use GP0 as peripheral bus / CSR + self.pbus = self.add_axi_gp_master(0) + self.periph_buses.append(self.pbus) + self.comb += ResetSignal("ps").eq(~rst_n) self.ps_tcl.append(f"set ps [create_ip -vendor xilinx.com -name zynq_ultra_ps_e -module_name {self.ps_name}]") + def set_preset(self, preset): + preset = os.path.abspath(preset) + self.ps_tcl.append(f"source {preset}") + self.ps_tcl.append("set psu_cfg [apply_preset IPINST]") + self.ps_tcl.append("set_property -dict $psu_cfg [get_ips {}]".format(self.ps_name)) + def add_axi_gp_master(self, n=0, data_width=32): assert n < 3 and self.axi_gp_masters[n] is None assert data_width in [32, 64, 128] axi_gpn = axi.AXIInterface(data_width=data_width, address_width=32, id_width=16) - self.config[f'PSU__USE__M_AXI_GP{n}'] = 1 + xpd = {0 : "fpd", 1 : "fpd", 2 : "lpd"}[n] + self.config[f'PSU__USE__M_AXI_GP{n}'] = 1 self.config[f'PSU__MAXIGP{n}__DATA_WIDTH'] = data_width self.axi_gp_masters.append(axi_gpn) - xpd = {0 : "fpd", 1 : "fpd", 2 : "lpd"}[n] - self.cpu_params[f"i_maxihpm0_{xpd}_aclk"] = ClockSignal("ps") - layout = axi_gpn.layout_flat() - dir_map = {DIR_M_TO_S: 'o', DIR_S_TO_M: 'i'} - for group, signal, direction in layout: - sig_name = group + signal - if sig_name in ['bfirst', 'blast', 'rfirst', 'arfirst', 'arlast', 'awfirst', 'awlast', 'wfirst', 'wid']: - continue - direction = dir_map[direction] - self.cpu_params[f'{direction}_maxigp{n}_{group}{signal}'] = getattr(getattr(axi_gpn, group), signal) + self.cpu_params.update({ + # AXI GPx clk. + f"i_maxihpm0_{xpd}_aclk" : ClockSignal("ps"), + + # AXI GPx aw. + f"o_maxigp{n}_awid" : axi_gpn.aw.id, + f"o_maxigp{n}_awaddr" : axi_gpn.aw.addr, + f"o_maxigp{n}_awlen" : axi_gpn.aw.len, + f"o_maxigp{n}_awsize" : axi_gpn.aw.size, + f"o_maxigp{n}_awburst" : axi_gpn.aw.burst, + f"o_maxigp{n}_awlock" : axi_gpn.aw.lock, + f"o_maxigp{n}_awcache" : axi_gpn.aw.cache, + f"o_maxigp{n}_awprot" : axi_gpn.aw.prot, + f"o_maxigp{n}_awvalid" : axi_gpn.aw.valid, + f"o_maxigp{n}_awuser" : axi_gpn.aw.user, + f"i_maxigp{n}_awready" : axi_gpn.aw.ready, + f"o_maxigp{n}_awqos" : axi_gpn.aw.qos, + + # AXI GPx w. + f"o_maxigp{n}_wdata" : axi_gpn.w.data, + f"o_maxigp{n}_wstrb" : axi_gpn.w.strb, + f"o_maxigp{n}_wlast" : axi_gpn.w.last, + f"o_maxigp{n}_wvalid" : axi_gpn.w.valid, + f"i_maxigp{n}_wready" : axi_gpn.w.ready, + + # AXI GPx b. + f"i_maxigp{n}_bid" : axi_gpn.b.id, + f"i_maxigp{n}_bresp" : axi_gpn.b.resp, + f"i_maxigp{n}_bvalid" : axi_gpn.b.valid, + f"o_maxigp{n}_bready" : axi_gpn.b.ready, + + # AXI GPx ar. + f"o_maxigp{n}_arid" : axi_gpn.ar.id, + f"o_maxigp{n}_araddr" : axi_gpn.ar.addr, + f"o_maxigp{n}_arlen" : axi_gpn.ar.len, + f"o_maxigp{n}_arsize" : axi_gpn.ar.size, + f"o_maxigp{n}_arburst" : axi_gpn.ar.burst, + f"o_maxigp{n}_arlock" : axi_gpn.ar.lock, + f"o_maxigp{n}_arcache" : axi_gpn.ar.cache, + f"o_maxigp{n}_arprot" : axi_gpn.ar.prot, + f"o_maxigp{n}_arvalid" : axi_gpn.ar.valid, + f"o_maxigp{n}_aruser" : axi_gpn.ar.user, + f"i_maxigp{n}_arready" : axi_gpn.ar.ready, + f"o_maxigp{n}_arqos" : axi_gpn.ar.qos, + + # AXI GPx r. + f"i_maxigp{n}_rid" : axi_gpn.r.id, + f"i_maxigp{n}_rdata" : axi_gpn.r.data, + f"i_maxigp{n}_rresp" : axi_gpn.r.resp, + f"i_maxigp{n}_rlast" : axi_gpn.r.last, + f"i_maxigp{n}_rvalid" : axi_gpn.r.valid, + f"o_maxigp{n}_rready" : axi_gpn.r.ready, + }) return axi_gpn + def add_ethernet(self, n=0, pads=None, if_type="gmii"): + assert n < 3 and not n in self.gem_mac + assert pads is not None + + # psu configuration + self.config[f"PSU__ENET{n}__PERIPHERAL__ENABLE"] = 1 + self.config[f"PSU__ENET{n}__PERIPHERAL__IO"] = "EMIO" + self.config[f"PSU__ENET{n}__GRP_MDIO__ENABLE"] = 1 + self.config[f"PSU__ENET{n}__GRP_MDIO__IO"] = "EMIO" + + # psu GMII connection + gmii_rx_clk = Signal() + speed_mode = Signal(3) + gmii_crs = Signal() + gmii_col = Signal() + gmii_rxd = Signal(8) + gmii_rx_er = Signal() + gmii_rx_dv = Signal() + gmii_tx_clk = Signal() + gmii_txd = Signal(8) + gmii_tx_en = Signal() + gmii_tx_er = Signal() + + self.cpu_params.update({ + f"i_emio_enet{n}_gmii_rx_clk" : gmii_rx_clk, + f"o_emio_enet{n}_speed_mode" : speed_mode, + f"i_emio_enet{n}_gmii_crs" : gmii_crs, + f"i_emio_enet{n}_gmii_col" : gmii_col, + f"i_emio_enet{n}_gmii_rxd" : gmii_rxd, + f"i_emio_enet{n}_gmii_rx_er" : gmii_rx_er, + f"i_emio_enet{n}_gmii_rx_dv" : gmii_rx_dv, + f"i_emio_enet{n}_gmii_tx_clk" : gmii_tx_clk, + f"o_emio_enet{n}_gmii_txd" : gmii_txd, + f"o_emio_enet{n}_gmii_tx_en" : gmii_tx_en, + f"o_emio_enet{n}_gmii_tx_er" : gmii_tx_er, + }) + + # psu MDIO connection + mdio_mdc = Signal() + mdio_i = Signal() + mdio_o = Signal() + mdio_t = Signal() + self.cpu_params.update({ + f"o_emio_enet{n}_mdio_mdc" : mdio_mdc, + f"i_emio_enet{n}_mdio_i" : mdio_i, + f"o_emio_enet{n}_mdio_o" : mdio_o, + f"o_emio_enet{n}_mdio_t" : mdio_t, + }) + + if if_type == "gmii": + self.comb += pads.mdc.eq(mdio_mdc) + + self.specials += Instance("IOBUF", + i_I = mdio_o, + o_O = mdio_i, + i_T = mdio_t, + io_IO = pads.mdio + ) + else: + phys_mdio_i = Signal() + phys_mdio_o = Signal() + phys_mdio_t = Signal() + + self.specials += Instance("IOBUF", + i_I = phys_mdio_o, + o_O = phys_mdio_i, + i_T = phys_mdio_t, + io_IO = pads.mdio + ) + + self.comb += pads.rst_n.eq(~ResetSignal("sys")) + + mac_params = dict( + i_tx_reset = ResetSignal("sys"), + i_rx_reset = ResetSignal("sys"), + i_clkin = ClockSignal("rgmii"), + + # PS GEM: MDIO + i_mdio_gem_mdc = mdio_mdc, + o_mdio_gem_i = mdio_i, + i_mdio_gem_o = mdio_o, + i_mdio_gem_t = mdio_t, + # PS GEM: GMII + o_gmii_tx_clk = gmii_tx_clk, + i_gmii_tx_en = gmii_tx_en, + i_gmii_txd = gmii_txd, + i_gmii_tx_er = gmii_tx_er, + o_gmii_crs = gmii_crs, + o_gmii_col = gmii_col, + o_gmii_rx_clk = gmii_rx_clk, + o_gmii_rx_dv = gmii_rx_dv, + o_gmii_rxd = gmii_rxd, + o_gmii_rx_er = gmii_rx_er, + # PHY: RGMII + o_rgmii_txd = pads.tx_data, + o_rgmii_tx_ctl = pads.tx_ctl, + o_rgmii_txc = pads.txc, + i_rgmii_rxd = pads.rx_data, + i_rgmii_rx_ctl = pads.rx_ctl, + i_rgmii_rxc = pads.rxc, + # PHY: MDIO + o_mdio_phy_mdc = pads.mdc, + i_mdio_phy_i = phys_mdio_i, + o_mdio_phy_o = phys_mdio_o, + o_mdio_phy_t = phys_mdio_t, + + o_ref_clk_out = Open(), + o_mmcm_locked_out = Open(), + o_gmii_clk_125m_out = Open(), + o_gmii_clk_25m_out = Open(), + o_gmii_clk_2_5m_out = Open(), + o_link_status = Open(), + o_clock_speed = Open(2), + o_duplex_status = Open(), + o_speed_mode = Open(2), + ) + + self.specials += Instance(f"gem{n}", **mac_params) + self.gem_mac.append(n) + + def add_i2c(self, n, pads): + assert n < 2 and not n in self.i2c_use + assert pads is not None + + # PSU configuration. + self.config[f"PSU__I2C{n}__PERIPHERAL__ENABLE"] = 1 + self.config[f"PSU__I2C{n}__PERIPHERAL__IO"] = "EMIO" + + # Signals. + scl_i = Signal() + scl_o = Signal() + scl_t = Signal() + sda_i = Signal() + sda_o = Signal() + sda_t = Signal() + + # PSU connections. + self.specials += [ + Instance("IOBUF", + i_I = sda_o, + o_O = sda_i, + i_T = sda_t, + io_IO = pads.sda + ), + Instance("IOBUF", + i_I = scl_o, + o_O = scl_i, + i_T = scl_t, + io_IO = pads.scl + ), + ] + + self.cpu_params.update({ + f"i_emio_i2c{n}_scl_i" : scl_i, + f"o_emio_i2c{n}_scl_o" : scl_o, + f"o_emio_i2c{n}_scl_t" : scl_t, + f"i_emio_i2c{n}_sda_i" : sda_i, + f"o_emio_i2c{n}_sda_o" : sda_o, + f"o_emio_i2c{n}_sda_t" : sda_t, + }) + + def add_uart(self, n, pads): + assert n < 2 and not n in self.uart_use + assert pads is not None + + self.config[f"PSU__UART{n}__PERIPHERAL__ENABLE"] = 1 + self.config[f"PSU__UART{n}__PERIPHERAL__IO"] = "EMIO" + + self.cpu_params.update({ + f"i_emio_uart{n}_rxd" : pads.rx, + f"o_emio_uart{n}_txd" : pads.tx, + }) + + def add_gpios(self, pads): + assert pads is not None + + # Parameters. + pads_len = len(pads) + + # PSU configuration. + self.config["PSU__GPIO_EMIO__PERIPHERAL__ENABLE"] = 1 + self.config["PSU__GPIO_EMIO__PERIPHERAL__IO"] = len(pads) + + # Signals. + gpio_i = Signal(pads_len) + gpio_o = Signal(pads_len) + gpio_t = Signal(pads_len) + + # PSU connections. + for i in range(pads_len): + self.specials += Instance("IOBUF", + i_I = gpio_o[i], + o_O = gpio_i[i], + i_T = gpio_t[i], + io_IO = pads[i] + ) + + self.cpu_params.update({ + "i_emio_gpio_i" : gpio_i, + "o_emio_gpio_o" : gpio_o, + "o_emio_gpio_t" : gpio_t, + }) + + """ + Enable CANx peripheral. Peripheral may be optionally set + Attributes + ========== + n: int + CAN id (0, 1) + pads: + Physicals pads (tx and rx) + ext_clk: int or None + When unset/None CAN is clocked by internal clock (IO PLL). + value must be 0 <= ext_clk < 54. + ext_clk_freq: float + when ext_clk is set, external clock frequency (Hz) + """ + def add_can(self, n, pads, ext_clk=None, ext_clk_freq=None): + assert n < 2 and not n in self.can_use + assert ext_clk is None or (ext_clk < 54 and ext_clk is not None) + assert pads is not None + + # Mark as used + self.can_use.append(n) + + # PSU configuration. + self.config[f"PSU__CAN{n}__PERIPHERAL__ENABLE"] = 1 + self.config[f"PSU__CAN{n}__PERIPHERAL__IO"] = "EMIO" + self.config[f"PSU__CAN{n}__GRP_CLK__ENABLE"] = {True: 0, False: 1}[ext_clk == None] + + if ext_clk: + self.config[f"PSU__CAN{n}__GRP_CLK__IO"] = f"MIO {ext_clk}" + self.config[f"PSU__CRL_APB__CAN{n}_REF_CTRL__FREQMHZ"] = int(clk_freq / 1e6) + + # PS7 connections. + self.cpu_params.update({ + f"i_emio_can{n}_phy_rx": pads.rx, + f"o_emio_can{n}_phy_tx": pads.tx, + }) + def do_finalize(self): if len(self.ps_tcl): self.ps_tcl.append("set_property -dict [list \\") @@ -94,3 +409,23 @@ class ZynqMP(CPU): ] self.platform.toolchain.pre_synthesis_commands += self.ps_tcl self.specials += Instance(self.ps_name, **self.cpu_params) + + # ethernet + + if len(self.gem_mac): + mac_tcl = [] + for i in self.gem_mac: + mac_tcl.append(f"set gem{i} [create_ip -vendor xilinx.com -name gmii_to_rgmii -module_name gem{i}]") + mac_tcl.append("set_property -dict [ list \\") + # FIXME: when more this sequence differs for the first and others + mac_tcl.append("CONFIG.{} {} \\".format("C_EXTERNAL_CLOCK", '{{false}}')) + mac_tcl.append("CONFIG.{} {} \\".format("C_USE_IDELAY_CTRL", '{{true}}')) + mac_tcl.append("CONFIG.{} {} \\".format("C_PHYADDR", '{{' + str(8 + i) + '}}')) + mac_tcl.append("CONFIG.{} {} \\".format("RGMII_TXC_SKEW", '{{' + str(0) + '}}')) + mac_tcl.append("CONFIG.{} {} \\".format("SupportLevel", '{{Include_Shared_Logic_in_Core}}')) + mac_tcl += [ + f"] [get_ips gem{i}]", + f"generate_target all [get_ips gem{i}]", + f"synth_ip [get_ips gem{i}]" + ] + self.platform.toolchain.pre_synthesis_commands += mac_tcl diff --git a/litex/soc/cores/dma.py b/litex/soc/cores/dma.py index 735d102e6..5ccd21ccf 100644 --- a/litex/soc/cores/dma.py +++ b/litex/soc/cores/dma.py @@ -73,13 +73,13 @@ class WishboneDMAReader(LiteXModule): if with_csr: self.add_csr() - def add_csr(self, default_base=0, default_length=0, default_enable=0, default_loop=0): - self._base = CSRStorage(64, reset=default_base) - self._length = CSRStorage(32, reset=default_length) - self._enable = CSRStorage(reset=default_enable) - self._done = CSRStatus() - self._loop = CSRStorage(reset=default_loop) - self._offset = CSRStatus(32) + def add_ctrl(self, default_base=0, default_length=0, default_enable=0, default_loop=0): + self.base = Signal(64, reset=default_base) + self.length = Signal(32, reset=default_length) + self.enable = Signal(reset=default_enable) + self.done = Signal() + self.loop = Signal(reset=default_loop) + self.offset = Signal(32) # # # @@ -87,13 +87,13 @@ class WishboneDMAReader(LiteXModule): base = Signal(self.bus.adr_width) offset = Signal(self.bus.adr_width) length = Signal(self.bus.adr_width) - self.comb += base.eq(self._base.storage[shift:]) - self.comb += length.eq(self._length.storage[shift:]) + self.comb += base.eq(self.base[shift:]) + self.comb += length.eq(self.length[shift:]) - self.comb += self._offset.status.eq(offset) + self.comb += self.offset.eq(offset) self.fsm = fsm = ResetInserter()(FSM(reset_state="IDLE")) - self.comb += fsm.reset.eq(~self._enable.storage) + self.comb += fsm.reset.eq(~self.enable) fsm.act("IDLE", NextValue(offset, 0), NextState("RUN"), @@ -105,7 +105,7 @@ class WishboneDMAReader(LiteXModule): If(self.sink.ready, NextValue(offset, offset + 1), If(self.sink.last, - If(self._loop.storage, + If(self.loop, NextValue(offset, 0) ).Else( NextState("DONE") @@ -113,7 +113,30 @@ class WishboneDMAReader(LiteXModule): ) ) ) - fsm.act("DONE", self._done.status.eq(1)) + fsm.act("DONE", self.done.eq(1)) + + def add_csr(self, default_base=0, default_length=0, default_enable=0, default_loop=0): + if not hasattr(self, "base"): + self.add_ctrl() + self._base = CSRStorage(64, reset=default_base) + self._length = CSRStorage(32, reset=default_length) + self._enable = CSRStorage(reset=default_enable) + self._done = CSRStatus() + self._loop = CSRStorage(reset=default_loop) + self._offset = CSRStatus(32) + + # # # + + self.comb += [ + # Control. + self.base.eq(self._base.storage), + self.length.eq(self._length.storage), + self.enable.eq(self._enable.storage), + self.loop.eq(self._loop.storage), + # Status. + self._done.status.eq(self.done), + self._offset.status.eq(self.offset), + ] # WishboneDMAWriter -------------------------------------------------------------------------------- @@ -153,16 +176,16 @@ class WishboneDMAWriter(LiteXModule): if with_csr: self.add_csr() - def add_csr(self, default_base=0, default_length=0, default_enable=0, default_loop=0, ready_on_idle=1): + def add_ctrl(self, default_base=0, default_length=0, default_enable=0, default_loop=0, ready_on_idle=1): self._sink = self.sink self.sink = stream.Endpoint([("data", self.bus.data_width)]) - self._base = CSRStorage(64, reset=default_base) - self._length = CSRStorage(32, reset=default_length) - self._enable = CSRStorage(reset=default_enable) - self._done = CSRStatus() - self._loop = CSRStorage(reset=default_loop) - self._offset = CSRStatus(32) + self.base = Signal(64, reset=default_base) + self.length = Signal(32, reset=default_length) + self.enable = Signal(reset=default_enable) + self.done = Signal() + self.loop = Signal(reset=default_loop) + self.offset = Signal(32) # # # @@ -170,13 +193,13 @@ class WishboneDMAWriter(LiteXModule): base = Signal(self.bus.adr_width) offset = Signal(self.bus.adr_width) length = Signal(self.bus.adr_width) - self.comb += base.eq(self._base.storage[shift:]) - self.comb += length.eq(self._length.storage[shift:]) + self.comb += base.eq(self.base[shift:]) + self.comb += length.eq(self.length[shift:]) - self.comb += self._offset.status.eq(offset) + self.comb += self.offset.eq(offset) self.fsm = fsm = ResetInserter()(FSM(reset_state="IDLE")) - self.comb += fsm.reset.eq(~self._enable.storage) + self.comb += fsm.reset.eq(~self.enable) fsm.act("IDLE", self.sink.ready.eq(ready_on_idle), NextValue(offset, 0), @@ -191,7 +214,7 @@ class WishboneDMAWriter(LiteXModule): If(self.sink.valid & self.sink.ready, NextValue(offset, offset + 1), If(self._sink.last, - If(self._loop.storage, + If(self.loop, NextValue(offset, 0) ).Else( NextState("DONE") @@ -199,4 +222,27 @@ class WishboneDMAWriter(LiteXModule): ) ) ) - fsm.act("DONE", self._done.status.eq(1)) + fsm.act("DONE", self.done.eq(1)) + + def add_csr(self, default_base=0, default_length=0, default_enable=0, default_loop=0): + if not hasattr(self, "base"): + self.add_ctrl() + self._base = CSRStorage(64, reset=default_base) + self._length = CSRStorage(32, reset=default_length) + self._enable = CSRStorage(reset=default_enable) + self._done = CSRStatus() + self._loop = CSRStorage(reset=default_loop) + self._offset = CSRStatus(32) + + # # # + + self.comb += [ + # Control. + self.base.eq(self._base.storage), + self.length.eq(self._length.storage), + self.enable.eq(self._enable.storage), + self.loop.eq(self._loop.storage), + # Status. + self._done.status.eq(self.done), + self._offset.status.eq(self.offset), + ] diff --git a/litex/soc/cores/hyperbus.py b/litex/soc/cores/hyperbus.py index da8c03d2b..3725c33f8 100644 --- a/litex/soc/cores/hyperbus.py +++ b/litex/soc/cores/hyperbus.py @@ -1,16 +1,22 @@ # -# This file is part of LiteHyperBus +# This file is part of LiteX. # -# Copyright (c) 2019-2022 Florent Kermarrec +# Copyright (c) 2019-2024 Florent Kermarrec # Copyright (c) 2019 Antti Lukats # Copyright (c) 2021 Franck Jullien # SPDX-License-Identifier: BSD-2-Clause from migen import * +from migen.fhdl.specials import Tristate + +from litex.build.io import SDRTristate from litex.gen import * from litex.gen.genlib.misc import WaitTimer +from litex.soc.interconnect.csr import * +from litex.soc.interconnect import stream + from litex.build.io import DifferentialOutput from litex.soc.interconnect import wishbone @@ -21,143 +27,284 @@ class HyperRAM(LiteXModule): tCSM = 4e-6 """HyperRAM - Provides a very simple/minimal HyperRAM core that should work with all FPGA/HyperRam chips: - - FPGA vendor agnostic. - - no setup/chip configuration (use default latency). + Provides a very simple/minimal HyperRAM core with a Wishbone Interface that can work with all + FPGA/HyperRam chips: + - Vendor agnostic. + - Fixed/Variable latency. + - Latency/Registers (re-)configuration. - This core favors portability and ease of use over performance. - """ - def __init__(self, pads, latency=6, sys_clk_freq=None): + Parameters: + pads (Record) : Interface to the HyperRAM connection pads. + latency (int, optional) : Initial latency setting, defaults to 6. + latency_mode (str, optional) : Specifies the latency mode ('fixed' or 'variable'), defaults to 'variable'. + sys_clk_freq (float, optional) : System clock frequency in Hz. + with_csr (bool, optional) : Enables CSR interface for Latency/Registers configuration, defaults to True. + + Attributes: + pads (Record) : Platform pads of HyperRAM. + bus (wishbone.Interface) : Wishbone Interface. +""" + def __init__(self, pads, latency=6, latency_mode="variable", sys_clk_freq=10e6, clk_ratio="4:1", with_csr=True): self.pads = pads self.bus = bus = wishbone.Interface(data_width=32, address_width=32, addressing="word") # # # + # Parameters. + # ----------- + dw = len(pads.dq) if not hasattr(pads.dq, "oe") else len(pads.dq.o) + assert dw in [8, 16] + assert latency_mode in ["fixed", "variable"] + assert clk_ratio in [ + "4:1", # HyperRAM Clk = Sys Clk/4. + "2:1", # HyperRAM Clk = Sys Clk/2. + ] + self.cd_io = cd_io = { + "4:1": "sys", + "2:1": "sys2x" + }[clk_ratio] + self.sync_io = sync_io = getattr(self.sync, cd_io) + + # Config/Reg Interface. + # --------------------- + self.conf_rst = Signal() + self.conf_latency = Signal(8, reset=latency) + self.stat_latency_mode = Signal(reset={"fixed": 0, "variable": 1}[latency_mode]) + self.reg_write = Signal() + self.reg_read = Signal() + self.reg_addr = Signal(2) + self.reg_write_done = Signal() + self.reg_read_done = Signal() + self.reg_write_data = Signal(16) + self.reg_read_data = Signal(16) + if with_csr: + self.add_csr(default_latency=latency) + + # Internal Signals. + # ----------------- clk = Signal() clk_phase = Signal(2) cs = Signal() ca = Signal(48) - ca_active = Signal() + ca_oe = Signal() sr = Signal(48) - sr_new = Signal(48) - dq = self.add_tristate(pads.dq) if not hasattr(pads.dq, "oe") else pads.dq - rwds = self.add_tristate(pads.rwds) if not hasattr(pads.rwds, "oe") else pads.rwds - dw = len(pads.dq) if not hasattr(pads.dq, "oe") else len(pads.dq.o) + sr_next = Signal(48) + dq_o = Signal(dw) + dq_oe = Signal() + dq_i = Signal(dw) + rwds_o = Signal(dw//8) + rwds_oe = Signal() + rwds_i = Signal(dw//8) - assert dw in [8, 16] + # Tristates. + # ---------- + dq = self.add_tristate(pads.dq, register=False) if not hasattr(pads.dq, "oe") else pads.dq + rwds = self.add_tristate(pads.rwds, register=False) if not hasattr(pads.rwds, "oe") else pads.rwds + self.comb += [ # FIXME: Try to move to sync to allow switching to SDRTristate. + # DQ. + dq.o.eq( dq_o), + dq.oe.eq(dq_oe), + + # RWDS. + rwds.o.eq( rwds_o), + rwds.oe.eq(rwds_oe), + ] + self.sync_io += [ + # DQ. + dq_i.eq(dq.i), + + # RWDS. + rwds_i.eq(rwds.i) + ] # Drive Control Signals -------------------------------------------------------------------- # Rst. if hasattr(pads, "rst_n"): - self.comb += pads.rst_n.eq(1) + self.sync_io += pads.rst_n.eq(1 & ~self.conf_rst) # CSn. - self.comb += pads.cs_n[0].eq(~cs) - assert len(pads.cs_n) <= 2 - if len(pads.cs_n) == 2: - self.comb += pads.cs_n[1].eq(1) + pads.cs_n.reset = 2**len(pads.cs_n) - 1 + self.sync_io += pads.cs_n[0].eq(~cs) # Only supporting 1 CS. # Clk. + pads_clk = Signal() + self.sync_io += pads_clk.eq(clk) if hasattr(pads, "clk"): - self.comb += pads.clk.eq(clk) + # Single Ended Clk. + self.comb += pads.clk.eq(pads_clk) + elif hasattr(pads, "clk_p"): + # Differential Clk. + self.specials += DifferentialOutput(pads_clk, pads.clk_p, pads.clk_n) else: - self.specials += DifferentialOutput(clk, pads.clk_p, pads.clk_n) + raise ValueError # Burst Timer ------------------------------------------------------------------------------ - sys_clk_freq = 10e6 if sys_clk_freq is None else sys_clk_freq - burst_timer = WaitTimer(sys_clk_freq*self.tCSM) - self.burst_timer = burst_timer + self.burst_timer = burst_timer = WaitTimer(sys_clk_freq * self.tCSM) - # Clock Generation (sys_clk/4) ------------------------------------------------------------- - self.sync += clk_phase.eq(clk_phase + 1) - cases = {} - cases[1] = clk.eq(cs) # Set pads clk on 90° (if cs is set) - cases[3] = clk.eq(0) # Clear pads clk on 270° - self.sync += Case(clk_phase, cases) + # Clk Generation --------------------------------------------------------------------------- + self.sync_io += [ + clk_phase.eq(0b00), + If(cs, + clk_phase.eq(clk_phase + 1) + ) + ] + cases = { + 0b00 : clk.eq(0), # 0° + 0b01 : clk.eq(cs), # 90° / Set Clk. + 0b10 : clk.eq(cs), # 180° + 0b11 : clk.eq(0), # 270° / Clr Clk. + } + if clk_ratio in ["4:1"]: + self.comb += Case(clk_phase, cases) + if clk_ratio in ["2:1"]: + self.sync_io += Case(clk_phase, cases) # Data Shift-In Register ------------------------------------------------------------------- - dqi = Signal(dw) - self.sync += dqi.eq(dq.i) # Sample on 90° and 270° self.comb += [ - sr_new.eq(Cat(dqi, sr[:-dw])), - If(ca_active, - sr_new.eq(Cat(dqi[:8], sr[:-8])) # Only 8-bit during Command/Address. + # Command/Address: On 8-bit, so 8-bit shift and no input. + If(ca_oe, + sr_next[8:].eq(sr), + # Data: On dw-bit, so dw-bit shift. + ).Else( + sr_next[:dw].eq(dq_i), + sr_next[dw:].eq(sr), ) ] - self.sync += If(clk_phase[0] == 0, sr.eq(sr_new)) # Shift on 0° and 180° + if clk_ratio in ["4:1"]: + self.sync += If(clk_phase[0] == 0, sr.eq(sr_next)) + if clk_ratio in ["2:1"]: + self.sync += sr.eq(sr_next) # Data Shift-Out Register ------------------------------------------------------------------ + self.comb += bus.dat_r.eq(sr_next) self.comb += [ - bus.dat_r.eq(sr_new), - If(dq.oe, - dq.o.eq(sr[-dw:]), - If(ca_active, - dq.o.eq(sr[-8:]) # Only 8-bit during Command/Address. - ) + # Command/Address: 8-bit. + If(ca_oe, + dq_o.eq(sr[-8:]) + # Data: dw-bit. + ).Else( + dq_o.eq(sr[-dw:]) ) ] + # Register Access/Buffer ------------------------------------------------------------------- + reg_write_req = Signal() + reg_read_req = Signal() + self.reg_buf = reg_buf = stream.SyncFIFO( + layout = [("write", 1), ("read", 1), ("addr", 4), ("data", 16)], + depth = 4, + ) + reg_ep = reg_buf.source + self.comb += [ + reg_buf.sink.valid.eq(self.reg_write | self.reg_read), + reg_buf.sink.write.eq(self.reg_write), + reg_buf.sink.read.eq(self.reg_read), + reg_buf.sink.addr.eq(self.reg_addr), + reg_buf.sink.data.eq(self.reg_write_data), + reg_write_req.eq(reg_ep.valid & reg_ep.write), + reg_read_req.eq( reg_ep.valid & reg_ep.read), + ] + self.sync += If(reg_buf.sink.valid, + self.reg_write_done.eq(0), + self.reg_read_done.eq(0), + ) + # Command generation ----------------------------------------------------------------------- ashift = {8:1, 16:0}[dw] self.comb += [ - ca[47].eq(~bus.we), # R/W# - ca[45].eq(1), # Burst Type (Linear) - ca[16:45].eq(bus.adr[3-ashift:]), # Row & Upper Column Address - ca[ashift:3].eq(bus.adr), # Lower Column Address + # Register Command Generation. + If(reg_write_req | reg_read_req, + ca[47].eq(reg_ep.read), # R/W# + ca[46].eq(1), # Register Space. + ca[45].eq(1), # Burst Type (Linear) + Case(reg_ep.addr, { + 0 : ca[0:40].eq(0x00_00_00_00_00), # Identification Register 0 (Read Only). + 1 : ca[0:40].eq(0x00_00_00_00_01), # Identification Register 1 (Read Only). + 2 : ca[0:40].eq(0x00_01_00_00_00), # Configuration Register 0. + 3 : ca[0:40].eq(0x00_01_00_00_01), # Configuration Register 1. + }), + # Wishbone Command Generation. + ).Else( + ca[47].eq(~bus.we), # R/W# + ca[46].eq(0), # Memory Space. + ca[45].eq(1), # Burst Type (Linear) + ca[16:45].eq(bus.adr[3-ashift:]), # Row & Upper Column Address + ca[ashift:3].eq(bus.adr), # Lower Column Address + ) ] - # Latency count starts from the middle of the command (thus the -4). In fixed latency mode - # (default), latency is 2 x Latency count. We have 4 x sys_clk per RAM clock: - latency_cycles = (latency * 2 * 4) - 4 - # Bus Latch -------------------------------------------------------------------------------- bus_adr = Signal(32) bus_we = Signal() bus_sel = Signal(4) bus_latch = Signal() self.sync += If(bus_latch, - If(bus.we, - sr.eq(Cat(Signal(16), bus.dat_w)), - ), + If(bus.we, sr.eq(Cat(Signal(16), bus.dat_w))), bus_we.eq(bus.we), bus_sel.eq(bus.sel), bus_adr.eq(bus.adr) ) # FSM (Sequencer) -------------------------------------------------------------------------- - cycles = Signal(8) - first = Signal() + cycles = Signal(8) + first = Signal() + refresh = Signal() self.fsm = fsm = FSM(reset_state="IDLE") fsm.act("IDLE", NextValue(first, 1), - If(bus.cyc & bus.stb, - If(clk_phase == 0, - NextValue(sr, ca), - NextState("SEND-COMMAND-ADDRESS") - ) + If((bus.cyc & bus.stb) | reg_write_req | reg_read_req, + NextValue(sr, ca), + NextState("SEND-COMMAND-ADDRESS") ) ) fsm.act("SEND-COMMAND-ADDRESS", - # Set CSn. - cs.eq(1), # Send Command on DQ. - ca_active.eq(1), - dq.oe.eq(1), - # Wait for 6*2 cycles... + ca_oe.eq(1), + dq_oe.eq(1), + # Wait for 6*2 cycles. If(cycles == (6*2 - 1), - NextState("WAIT-LATENCY") + If(reg_write_req, + NextValue(sr, Cat(Signal(40), self.reg_write_data[8:])), + NextState("REG-WRITE-0") + ).Else( + # Sample RWDS to know if 1X/2X Latency should be used (Refresh). + NextValue(refresh, rwds_i | (latency_mode in ["fixed"])), + NextState("WAIT-LATENCY") + ) + ) + ) + fsm.act("REG-WRITE-0", + # Send Reg on DQ. + ca_oe.eq(1), + dq_oe.eq(1), + # Wait for 2 cycles. + If(cycles == (2 - 1), + NextValue(sr, Cat(Signal(40), self.reg_write_data[:8])), + NextState("REG-WRITE-1") + ) + ) + fsm.act("REG-WRITE-1", + # Send Reg on DQ. + ca_oe.eq(1), + dq_oe.eq(1), + # Wait for 2 cycles. + If(cycles == (2 - 1), + reg_ep.ready.eq(1), + NextValue(self.reg_write_done, 1), + NextState("IDLE") ) ) fsm.act("WAIT-LATENCY", - # Set CSn. - cs.eq(1), - # Wait for Latency cycles... - If(cycles == (latency_cycles - 1), + # Wait for 1X or 2X Latency cycles... (-4 since count start in the middle of the command). + If(((cycles == 2*(self.conf_latency * 4) - 4 - 1) & refresh) | # 2X Latency (No DRAM refresh required). + ((cycles == 1*(self.conf_latency * 4) - 4 - 1) & ~refresh) , # 1X Latency ( DRAM refresh required). # Latch Bus. bus_latch.eq(1), # Early Write Ack (to allow bursting). - bus.ack.eq(bus.we), + If(~reg_read_req, + bus.ack.eq(bus.we), + ), NextState("READ-WRITE-DATA0") ) ) @@ -166,15 +313,14 @@ class HyperRAM(LiteXModule): fsm.act(f"READ-WRITE-DATA{n}", # Enable Burst Timer. burst_timer.wait.eq(1), - # Set CSn. - cs.eq(1), + ca_oe.eq(reg_read_req), # Send Data on DQ/RWDS (for write). If(bus_we, - dq.oe.eq(1), - rwds.oe.eq(1), - *[rwds.o[dw//8-1-i].eq(~bus_sel[4-1-n*dw//8-i]) for i in range(dw//8)], + dq_oe.eq(1), + rwds_oe.eq(1), + *[rwds_o[dw//8-1-i].eq(~bus_sel[4-1-n*dw//8-i]) for i in range(dw//8)], ), - # Wait for 2 cycles (since HyperRAM's Clk = sys_clk/4). + # Wait for 2 cycles. If(cycles == (2 - 1), # Set next default state (with rollover for bursts). NextState(f"READ-WRITE-DATA{(n + 1)%states}"), @@ -182,7 +328,7 @@ class HyperRAM(LiteXModule): If(n == (states - 1), NextValue(first, 0), # Continue burst when a consecutive access is ready. - If(bus.stb & bus.cyc & (bus.we == bus_we) & (bus.adr == (bus_adr + 1)) & (~burst_timer.done), + If(~reg_read_req & bus.stb & bus.cyc & (bus.we == bus_we) & (bus.adr == (bus_adr + 1)) & (~burst_timer.done), # Latch Bus. bus_latch.eq(1), # Early Write Ack (to allow bursting). @@ -194,15 +340,118 @@ class HyperRAM(LiteXModule): ), # Read Ack (when dat_r ready). If((n == 0) & ~first, - bus.ack.eq(~bus_we), + If(reg_read_req, + reg_ep.ready.eq(1), + NextValue(self.reg_read_done, 1), + NextValue(self.reg_read_data, bus.dat_r), + NextState("IDLE"), + ).Else( + bus.ack.eq(~bus_we), + ) ) ) ) - fsm.finalize() - self.sync += cycles.eq(cycles + 1) - self.sync += If(fsm.next_state != fsm.state, cycles.eq(0)) - def add_tristate(self, pad): - t = TSTriple(len(pad)) - self.specials += t.get_tristate(pad) + # CS -------------------------------------------------------------------------------------- + self.comb += If(~fsm.ongoing("IDLE"), cs.eq(1)) # CS when not in IDLE state. + self.comb += If(fsm.before_leaving("IDLE"), cs.eq(1)) # Early Set. + self.comb += If(fsm.before_entering("IDLE"), cs.eq(0)) # Early Clr. + + # FSM Cycles ------------------------------------------------------------------------------- + fsm.finalize() + cycles_rst = { + "4:1" : 0, + "2:1" : 1, + }[clk_ratio] + cycles_inc = { + "4:1" : 1, + "2:1" : 2, + }[clk_ratio] + self.sync += cycles.eq(cycles + cycles_inc) + self.sync += If(fsm.next_state != fsm.state, cycles.eq(cycles_rst)) + + def add_tristate(self, pad, register=False): + class TristatePads: + def __init__(self, width): + self.o = Signal(len(pad)) + self.oe = Signal() + self.i = Signal(len(pad)) + t = TristatePads(len(pad)) + if register: + for n in range(len(pad)): + self.specials += SDRTristate(pad[n], + o = t.o[n], + oe = t.oe, + i = t.i[n], + clk = ClockSignal(cd_io), + ) + else: + self.specials += Tristate(pad, + o = t.o, + oe = t.oe, + i = t.i, + ) return t + + def add_csr(self, default_latency=6): + # Config/Status Interface. + # ------------------------ + self.config = CSRStorage(fields=[ + CSRField("rst", offset=0, size=1, pulse=True, description="HyperRAM Rst."), + CSRField("latency", offset=8, size=8, description="HyperRAM Latency (X1).", reset=default_latency), + ]) + self.comb += [ + self.conf_rst.eq( self.config.fields.rst), + self.conf_latency.eq(self.config.fields.latency), + ] + self.status = CSRStatus(fields=[ + CSRField("latency_mode", offset=0, size=1, values=[ + ("``0b0``", "Fixed Latency."), + ("``0b1``", "Variable Latency."), + ]), + CSRField("clk_ratio", offset=1, size=4, values=[ + ("``4``", "HyperRAM Clk = Sys Clk/4."), + ("``2``", "HyperRAM Clk = Sys Clk/2."), + ]), + ]) + self.comb += [ + self.status.fields.latency_mode.eq(self.stat_latency_mode), + self.status.fields.clk_ratio.eq({ + "sys" : 4, + "sys2x": 2, + }[self.cd_io]), + ] + + # Reg Interface. + # -------------- + self.reg_control = CSRStorage(fields=[ + CSRField("write", offset=0, size=1, pulse=True, description="Issue Register Write."), + CSRField("read", offset=1, size=1, pulse=True, description="Issue Register Read."), + CSRField("addr", offset=8, size=4, values=[ + ("``0b00``", "Identification Register 0 (Read Only)."), + ("``0b01``", "Identification Register 1 (Read Only)."), + ("``0b10``", "Configuration Register 0."), + ("``0b11``", "Configuration Register 1."), + ]), + ]) + self.reg_status = CSRStatus(fields=[ + CSRField("write_done", offset=0, size=1, description="Register Write Done."), + CSRField("read_done", offset=1, size=1, description="Register Read Done."), + ]) + self.reg_wdata = CSRStorage(16, description="Register Write Data.") + self.reg_rdata = CSRStatus( 16, description="Register Read Data.") + + self.comb += [ + # Control. + self.reg_write.eq(self.reg_control.fields.write), + self.reg_read.eq( self.reg_control.fields.read), + self.reg_addr.eq( self.reg_control.fields.addr), + + # Status. + self.reg_status.fields.write_done.eq(self.reg_write_done), + self.reg_status.fields.read_done.eq( self.reg_read_done), + + # Data. + self.reg_write_data.eq(self.reg_wdata.storage), + self.reg_rdata.status.eq(self.reg_read_data), + ] diff --git a/litex/soc/cores/i2c.py b/litex/soc/cores/i2c.py new file mode 100644 index 000000000..702b35c22 --- /dev/null +++ b/litex/soc/cores/i2c.py @@ -0,0 +1,291 @@ +# +# This file is part of MiSoC and has been adapted/modified for Litex. +# +# Copyright 2007-2023 / M-Labs Ltd +# Copyright 2012-2015 / Enjoy-Digital +# Copyright from Misoc LICENCE file added above +# +# Copyright 2023 Andrew Dennison +# +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * +from litex.gen import * +from litex.soc.interconnect import wishbone +from litex.soc.interconnect.csr_eventmanager import * + +# I2C----------------------------------------------------------------------------------------------- + +__all__ = [ + "I2CMaster", + "I2C_XFER_ADDR", "I2C_CONFIG_ADDR", + "I2C_ACK", "I2C_READ", "I2C_WRITE", "I2C_STOP", "I2C_START", "I2C_IDLE", +] + + +class I2CClockGen(LiteXModule): + def __init__(self, width): + self.load = Signal(width) + self.clk2x = Signal() + + cnt = Signal.like(self.load) + self.comb += [ + self.clk2x.eq(cnt == 0), + ] + self.sync += [ + If(self.clk2x, + cnt.eq(self.load), + ).Else( + cnt.eq(cnt - 1), + ), + ] + + +class I2CMasterMachine(LiteXModule): + def __init__(self, clock_width): + self.scl_o = Signal(reset=1) + self.sda_o = Signal(reset=1) + self.sda_i = Signal() + + self.cg = CEInserter()(I2CClockGen(clock_width)) + self.idle = Signal() + self.start = Signal() + self.stop = Signal() + self.write = Signal() + self.read = Signal() + self.ack = Signal() + self.data = Signal(8) + + ### + + busy = Signal() + bits = Signal(4) + + fsm = CEInserter()(FSM("IDLE")) + self.fsm = fsm + + fsm.act("IDLE", + # Valid combinations (lowest to highest priority): + # stop: lowest priority + # read (& optional stop with automatic NACK) + # write (& optional stop) + # start (indicates start or restart) + # start & write (& optional stop) + # start & write & read (& optional stop) + # lowest priority + # *** TODO: support compound commands with I2CMaster *** + If(self.stop & ~self.scl_o, + # stop is only valid after an ACK + NextState("STOP0"), + ), + If(self.read, + # post decrement so read first bit and shift in 7 + NextValue(bits, 8-1), + NextState("READ0"), + ), + If(self.write, + NextValue(bits, 8), + NextState("WRITE0"), + ), + # start could be requesting a restart + If(self.start, + NextState("RESTART0"), + ), + # highest priority: start only if scl is high + If(self.start & self.scl_o, + NextState("START0"), + ), + ) + + fsm.act("START0", + # Always entered with scl_o = 1 + NextValue(self.sda_o, 0), + NextState("IDLE")) + + fsm.act("RESTART0", + # Only entered from IDLE with scl_o = 0 + NextValue(self.sda_o, 1), + NextState("RESTART1")) + fsm.act("RESTART1", + NextValue(self.scl_o, 1), + NextState("START0")) + + fsm.act("STOP0", + # Only entered from IDLE with scl_o = 0 + NextValue(self.sda_o, 0), + NextState("STOP1")) + fsm.act("STOP1", + NextValue(self.scl_o, 1), + NextState("STOP2")) + fsm.act("STOP2", + NextValue(self.sda_o, 1), + NextState("IDLE")) + + fsm.act("WRITE0", + NextValue(self.scl_o, 0), + If(bits == 0, + NextValue(self.sda_o, 1), + NextState("READACK0"), + ).Else( + NextValue(self.sda_o, self.data[7]), + NextState("WRITE1"), + ) + ) + fsm.act("WRITE1", + NextValue(self.scl_o, 1), + NextValue(self.data[1:], self.data[:-1]), + NextValue(bits, bits - 1), + NextState("WRITE0"), + ) + fsm.act("READACK0", + NextValue(self.scl_o, 1), + NextState("READACK1"), + ) + fsm.act("READACK1", + # ACK => IDLE always with scl_o = 0 + NextValue(self.scl_o, 0), + NextValue(self.ack, ~self.sda_i), + NextState("IDLE") + ) + + fsm.act("READ0", + # ACK => IDLE => READ0 always with scl_o = 0 + NextValue(self.scl_o, 1), + NextState("READ1"), + ) + fsm.act("READ1", + NextValue(self.data[0], self.sda_i), + NextValue(self.scl_o, 0), + If(bits == 0, + NextValue(self.sda_o, ~self.ack), + NextState("WRITEACK0"), + ).Else( + #NextValue(self.sda_o, 1), must already be high + NextState("READ2"), + ) + ) + fsm.act("READ2", + NextValue(self.scl_o, 1), + NextValue(self.data[1:], self.data[:-1]), + NextValue(bits, bits - 1), + NextState("READ1"), + ) + fsm.act("WRITEACK0", + NextValue(self.scl_o, 1), + NextState("WRITEACK1"), + ) + fsm.act("WRITEACK1", + # ACK => IDLE always with scl_o = 0 + NextValue(self.scl_o, 0), + NextValue(self.sda_o, 1), + NextState("IDLE") + ) + + run = Signal() + self.comb += [ + run.eq(self.start | self.stop | self.write | self.read), + self.idle.eq(~run & fsm.ongoing("IDLE")), + self.cg.ce.eq(~self.idle), + fsm.ce.eq(run | self.cg.clk2x), + ] + +# Registers: +# config = Record([ +# ("div", 20), +# ]) +# xfer = Record([ +# ("data", 8), +# ("ack", 1), +# ("read", 1), +# ("write", 1), +# ("start", 1), +# ("stop", 1), +# ("idle", 1), +# ]) +class I2CMaster(LiteXModule): + def __init__(self, pads, bus=None): + if bus is None: + bus = wishbone.Interface(data_width=32) + self.bus = bus + + ### + + # Wishbone + self.i2c = i2c = I2CMasterMachine( + clock_width=20) + + self.sync += [ + # read + If(bus.adr[0], + bus.dat_r.eq(i2c.cg.load), + ).Else( + bus.dat_r.eq(Cat(i2c.data, i2c.ack, C(0, 4), i2c.idle)), + ), + + # write + i2c.read.eq(0), + i2c.write.eq(0), + i2c.start.eq(0), + i2c.stop.eq(0), + + bus.ack.eq(0), + If(bus.cyc & bus.stb & ~bus.ack, + bus.ack.eq(1), + If(bus.we, + If(bus.adr[0], + i2c.cg.load.eq(bus.dat_w), + ).Else( + i2c.data.eq(bus.dat_w[0:8]), + i2c.ack.eq(bus.dat_w[8]), + i2c.read.eq(bus.dat_w[9]), + i2c.write.eq(bus.dat_w[10]), + i2c.start.eq(bus.dat_w[11]), + i2c.stop.eq(bus.dat_w[12]), + ) + ) + ) + ] + + # I/O + self.scl_t = TSTriple() + self.scl_tristate = self.scl_t.get_tristate(pads.scl) + self.comb += [ + self.scl_t.oe.eq(~i2c.scl_o), + self.scl_t.o.eq(0), + ] + + self.sda_t = TSTriple() + self.sda_tristate = self.sda_t.get_tristate(pads.sda) + + self.scl_i_n = Signal() # previous scl_t.i + self.sda_oe_n = Signal() # previous sda_t.oe + self.sync += [ + self.scl_i_n.eq(self.scl_t.i), + self.sda_oe_n.eq(self.sda_t.oe), + ] + + self.comb += [ + self.sda_t.oe.eq(self.sda_oe_n), + # only change SDA when SCL is stable + If(self.scl_i_n == i2c.scl_o, + self.sda_t.oe.eq(~i2c.sda_o), + ), + self.sda_t.o.eq(0), + i2c.sda_i.eq(self.sda_t.i), + ] + + # Event Manager. + self.ev = EventManager() + self.ev.idle = EventSourceProcess(edge="rising") + self.ev.finalize() + self.comb += self.ev.idle.trigger.eq(i2c.idle) + +I2C_XFER_ADDR, I2C_CONFIG_ADDR = range(2) +( + I2C_ACK, + I2C_READ, + I2C_WRITE, + I2C_START, + I2C_STOP, + I2C_IDLE, +) = (1 << i for i in range(8, 14)) diff --git a/litex/soc/cores/ram/lattice_nx.py b/litex/soc/cores/ram/lattice_nx.py index 20eada5da..4eab1172d 100644 --- a/litex/soc/cores/ram/lattice_nx.py +++ b/litex/soc/cores/ram/lattice_nx.py @@ -79,10 +79,10 @@ class NXLRAM(LiteXModule): wren = Signal() self.comb += [ datain.eq(self.bus.dat_w[32*w:32*(w+1)]), - self.bus.dat_r[32*w:32*(w+1)].eq(dataout), If(self.bus.adr[14:14+self.depth_cascading.bit_length()] == d, cs.eq(1), - wren.eq(self.bus.we & self.bus.stb & self.bus.cyc) + wren.eq(self.bus.we & self.bus.stb & self.bus.cyc), + self.bus.dat_r[32*w:32*(w+1)].eq(dataout), ), ] lram_block = Instance("SP512K", diff --git a/litex/soc/cores/spi/spi_mmap.py b/litex/soc/cores/spi/spi_mmap.py index acb9300ed..f9ac6b160 100644 --- a/litex/soc/cores/spi/spi_mmap.py +++ b/litex/soc/cores/spi/spi_mmap.py @@ -35,6 +35,7 @@ SPI_SLOT_MODE_3 = 0b11 SPI_SLOT_LENGTH_32B = 0b00 SPI_SLOT_LENGTH_16B = 0b01 SPI_SLOT_LENGTH_8B = 0b10 +SPI_SLOT_LENGTH_24B = 0b11 SPI_SLOT_BITORDER_MSB_FIRST = 0b0 SPI_SLOT_BITORDER_LSB_FIRST = 0b1 @@ -206,7 +207,10 @@ class SPIMaster(LiteXModule): self.sync += [ If(miso_shift, miso_data.eq(Cat(miso, miso_data)) - ) + ), + If(self.start, + miso_data.eq(0) + ), ] self.comb += self.miso.eq(miso_data) @@ -239,10 +243,18 @@ class SPICtrl(LiteXModule): default_slot_bitorder = SPI_SLOT_BITORDER_MSB_FIRST, default_slot_loopback = 0b1, default_slot_divider = 2, + default_enable = 0b1, + default_slot_wait = 0, ): self.nslots = nslots self.slot_controls = [] - self.slot_status = [] + + version = "SPI0" + self._version = CSRStatus(size=32, description="""SPI Module Version.""", + reset=int.from_bytes(str.encode(version), 'little')) + + self.slot_count = CSRStatus(size=32, description="""SPI Module Slot Count.""", + reset=nslots) # Create TX/RX Control/Status registers. self.tx_control = CSRStorage(fields=[ @@ -302,6 +314,13 @@ class SPICtrl(LiteXModule): self.ev.rx.trigger.eq(self.rx_status.fields.level > self.rx_control.fields.threshold), ] + self.engine = CSRStorage(fields=[ + CSRField("enable", size=1, offset=0, values=[ + ("``0b0``", "SPI Engine Disabled."), + ("``0b1``", "SPI Engine Enabled."), + ], reset=default_enable), + ]) + # Create Slots Control/Status registers. for slot in range(nslots): control = CSRStorage(name=f"slot_control{slot}", fields=[ @@ -319,7 +338,7 @@ class SPICtrl(LiteXModule): ("``0b00``", "32-bit Max."), ("``0b01``", "16-bit Max."), ("``0b10``", " 8-bit Max."), - ("``0b11``", "Reserved."), + ("``0b11``", "24-bit Max."), ], reset=default_slot_length), CSRField("bitorder", size=1, offset=5, values=[ ("``0b0``", "MSB-First."), @@ -335,13 +354,15 @@ class SPICtrl(LiteXModule): ("``0x0002``", "SPI-Clk = Sys-Clk/2."), ("``0x0004``", "SPI-Clk = Sys-Clk/4."), ("``0xxxxx``", "SPI-Clk = Sys-Clk/xxxxx."), - ], reset=default_slot_divider) + ], reset=default_slot_divider), + CSRField("wait", size=16, offset=32, values=[ + ("``0x0000``", "No wait time."), + ("``0x0001``", "wait = 1 / Sys-Clk."), + ("``0xxxxx``", "wait = xxxx / Sys-Clk."), + ], reset=default_slot_wait), ]) - status = CSRStatus(name=f"slot_status{slot}") # CHECKME: Useful? setattr(self, f"slot_control{slot}", control) - setattr(self, f"slot_status{slot}", status) self.slot_controls.append(control) - self.slot_status.append(status) def get_ctrl(self, name, slot=None, cs=None): assert not ((slot is None) and (cs is None)) @@ -487,7 +508,7 @@ class SPIRXMMAP(LiteXModule): # SPI Engine --------------------------------------------------------------------------------------- class SPIEngine(LiteXModule): - def __init__(self, pads, ctrl, data_width, sys_clk_freq, default_enable=0b1): + def __init__(self, pads, ctrl, data_width, sys_clk_freq): self.sink = sink = stream.Endpoint(spi_layout( data_width = data_width, be_width = data_width//8, @@ -499,13 +520,6 @@ class SPIEngine(LiteXModule): cs_width = len(pads.cs_n) )) - self.control = CSRStorage(fields=[ - CSRField("enable", size=1, offset=0, values=[ - ("``0b0``", "SPI Engine Disabled."), - ("``0b1``", "SPI Engine Enabled."), - ], reset=default_enable), - ]) - # # # # SPI Master. @@ -532,6 +546,7 @@ class SPIEngine(LiteXModule): }) self.comb += Case(ctrl.get_ctrl("length", cs=sink.cs), { SPI_SLOT_LENGTH_32B : spi_length_max.eq(32), # 32-bit access max. + SPI_SLOT_LENGTH_24B : spi_length_max.eq(24), # 24-bit access max. SPI_SLOT_LENGTH_16B : spi_length_max.eq(16), # 16-bit access max. SPI_SLOT_LENGTH_8B : spi_length_max.eq( 8), # 8-bit access max. }) @@ -543,8 +558,15 @@ class SPIEngine(LiteXModule): ) ] + # Wait between transfers. + ctrl_wait = ctrl.get_ctrl("wait", cs=sink.cs) + wait_ticks = Signal.like(ctrl_wait) + wait_count = Signal.like(ctrl_wait) + self.comb += wait_ticks.eq(ctrl_wait) + cs_wait = Signal() + # SPI CS. (Use Manual CS to allow back-to-back Xfers). - self.comb += If(self.control.fields.enable & sink.valid, + self.comb += If(ctrl.engine.fields.enable & sink.valid & ~cs_wait, spi.cs.eq(sink.cs) ) @@ -555,7 +577,7 @@ class SPIEngine(LiteXModule): # Control-Path. self.fsm = fsm = FSM(reset_state="START") fsm.act("START", - If(self.control.fields.enable & sink.valid, + If(ctrl.engine.fields.enable & sink.valid, spi.start.eq(1), NextState("XFER") ) @@ -571,6 +593,20 @@ class SPIEngine(LiteXModule): source.be.eq(sink.be), If(source.ready, sink.ready.eq(1), + If(wait_ticks, + cs_wait.eq(1), + NextValue(wait_count, wait_ticks-1), + NextState("WAIT") + ).Else( + NextState("START") + ) + ) + ) + fsm.act("WAIT", + If(wait_count, + cs_wait.eq(1), + NextValue(wait_count, wait_count-1) + ).Else( NextState("START") ) ) @@ -580,9 +616,10 @@ class SPIEngine(LiteXModule): # MSB First. If(spi_bitorder == SPI_SLOT_BITORDER_MSB_FIRST, # TX copy/bitshift. - Case(spi_length, { + Case(spi.length, { 8 : spi.mosi[24:32].eq(sink.data[0: 8]), 16 : spi.mosi[16:32].eq(sink.data[0:16]), + 24 : spi.mosi[ 8:32].eq(sink.data[0:24]), 32 : spi.mosi[ 0:32].eq(sink.data[0:32]), }), # RX copy. @@ -593,9 +630,10 @@ class SPIEngine(LiteXModule): # TX copy. spi.mosi.eq(sink.data[::-1]), # RX copy/bitshift. - Case(spi_length, { + Case(spi.length, { 8 : source.data[0: 8].eq(spi.miso[::-1][24:32]), 16 : source.data[0:16].eq(spi.miso[::-1][16:32]), + 24 : source.data[0:24].eq(spi.miso[::-1][ 8:32]), 32 : source.data[0:32].eq(spi.miso[::-1][ 0:32]), }) ) diff --git a/litex/soc/cores/video.py b/litex/soc/cores/video.py index 257a9b212..b9469280b 100644 --- a/litex/soc/cores/video.py +++ b/litex/soc/cores/video.py @@ -646,14 +646,17 @@ class VideoTerminal(LiteXModule): class VideoFrameBuffer(LiteXModule): """Video FrameBuffer""" - def __init__(self, dram_port, hres=800, vres=600, base=0x00000000, fifo_depth=65536, clock_domain="sys", clock_faster_than_sys=False, format="rgb888"): + def __init__(self, dram_port, hres=800, vres=600, base=0x00000000, fifo_depth=64*KILOBYTE, clock_domain="sys", clock_faster_than_sys=False, format="rgb888"): self.vtg_sink = vtg_sink = stream.Endpoint(video_timing_layout) self.source = source = stream.Endpoint(video_data_layout) self.underflow = Signal() self.depth = depth = { "rgb888" : 32, - "rgb565" : 16 + "rgb565" : 16, + "rgb332" : 8, + "mono8" : 8, + "mono1" : 1, }[format] # # # @@ -728,16 +731,34 @@ class VideoFrameBuffer(LiteXModule): if (depth == 32): self.comb += [ - source.r.eq(video_pipe_source.data[ 0: 8]), - source.g.eq(video_pipe_source.data[ 8:16]), - source.b.eq(video_pipe_source.data[16:24]), + source.r.eq(video_pipe_source.data[ 0: 8]), + source.g.eq(video_pipe_source.data[ 8:16]), + source.b.eq(video_pipe_source.data[16:24]), ] - else: # depth == 16 + elif (depth == 16): self.comb += [ source.r.eq(Cat(Signal(3, reset = 0), video_pipe_source.data[11:16])), source.g.eq(Cat(Signal(2, reset = 0), video_pipe_source.data[ 5:11])), source.b.eq(Cat(Signal(3, reset = 0), video_pipe_source.data[ 0: 5])), ] + elif (depth == 8 and format == "rgb332"): + self.comb += [ + source.r.eq(Cat(Signal(5, reset = 0), video_pipe_source.data[5:8])), + source.g.eq(Cat(Signal(5, reset = 0), video_pipe_source.data[2:5])), + source.b.eq(Cat(Signal(6, reset = 0), video_pipe_source.data[0:2])), + ] + elif (depth == 8 and format == "mono8"): + self.comb += [ + source.r.eq(video_pipe_source.data[0:8]), + source.g.eq(video_pipe_source.data[0:8]), + source.b.eq(video_pipe_source.data[0:8]), + ] + else: # depth == 1 + self.comb += [ + source.r.eq(Cat(Signal(7, reset = 0), video_pipe_source.data[0:1])), + source.g.eq(Cat(Signal(7, reset = 0), video_pipe_source.data[0:1])), + source.b.eq(Cat(Signal(7, reset = 0), video_pipe_source.data[0:1])), + ] # Underflow. self.comb += self.underflow.eq(~source.valid) diff --git a/litex/soc/cores/watchdog.py b/litex/soc/cores/watchdog.py new file mode 100644 index 000000000..9ebcc2fb3 --- /dev/null +++ b/litex/soc/cores/watchdog.py @@ -0,0 +1,70 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2024 Fin Maaß +# SPDX-License-Identifier: BSD-2-Clause + +from migen import * + +from litex.gen import * +from litex.gen.genlib.misc import WaitTimer + +from litex.soc.interconnect.csr import * +from litex.soc.interconnect.csr_eventmanager import * + +# Watchdog -------------------------------------------------------------------------------------------- + +class Watchdog(LiteXModule): + """Watchdog + + Provides a generic Watchdog core. + """ + + def __init__(self, width=32, crg_rst=None, reset_delay=0, halted=None): + self.enable = Signal() + self.reset_mode = Signal() + self.feed = Signal() + self.halted = Signal() + + self.execute = Signal() + + self._control = CSRStorage(description="Watchdog Control.", fields=[ + CSRField("feed", size=1, offset=0, pulse=True, description="Watchdog feed (Write ``1`` to feed)."), + CSRField("enable", size=1, offset=8, description="Watchdog enable."), + CSRField("reset", size=1, offset=16, description="Reset SoC when watchdog times out."), + CSRField("pause_halted", size=1, offset=24, description="Pause watchdog when CPU is halted.") + ]) + + self.comb += [ + self.enable.eq(self._control.fields.enable & ~self.halted), + self.feed.eq(self._control.fields.feed), + self.reset_mode.eq(self._control.fields.reset), + ] + + if isinstance(halted, Signal): + self.comb += self.halted.eq(halted & self._control.fields.pause_halted) + + self._cycles = cycles = CSRStorage(description="Watchdog cycles until timeout.", size=width) + self._remaining = remaining = CSRStatus(description="Watchdog cycles remaining until timeout.", size=width) + + self.ev = EventManager() + self.ev.wdt = EventSourceProcess(edge="rising") + self.ev.finalize() + + self.sync += [ + If(self.feed, + remaining.status.eq(cycles.storage) + ).Elif(self.enable, + If(remaining.status != 0, + remaining.status.eq(remaining.status - 1) + ), + self.execute.eq(remaining.status == 0), + ) + ] + + self.comb += If(self.enable, self.ev.wdt.trigger.eq(self.execute)) + + if isinstance(crg_rst, Signal): + self.reset_timer = WaitTimer(reset_delay) + self.comb += self.reset_timer.wait.eq(self.enable & self.execute & self.reset_mode) + self.comb += If(self.reset_timer.done, crg_rst.eq(1)) diff --git a/litex/soc/integration/builder.py b/litex/soc/integration/builder.py index dfe0fc7b7..027862e8f 100644 --- a/litex/soc/integration/builder.py +++ b/litex/soc/integration/builder.py @@ -92,7 +92,9 @@ class Builder: # Documentation. generate_doc = False): - self.soc = soc + # SoC/Builder Attach. + self.soc = soc # Attach SoC to Builder. + self.soc.builder = self # Attach Builder to SoC. # Directories. self.output_dir = os.path.abspath(output_dir or os.path.join("build", soc.platform.name)) @@ -106,9 +108,9 @@ class Builder: self.compile_gateware = compile_gateware self.build_backend = build_backend - # Exports. - self.csr_csv = csr_csv - self.csr_json = csr_json + # Exports (Generated by default to output_dir with default name unless explicitly specified). + self.csr_csv = csr_csv if csr_csv else os.path.join(self.output_dir, "csr.csv") + self.csr_json = csr_json if csr_json else os.path.join(self.output_dir, "csr.json") self.csr_svd = csr_svd self.memory_x = memory_x @@ -138,27 +140,28 @@ class Builder: def add_software_library(self, name): self.software_libraries.append(name) - def add_json(self, filename, origin=0, name=""): - self.jsons.append((filename, origin, name)) + def add_json(self, filename, origin=0, name="", exclude_constants=["_INTERRUPT"]): + self.jsons.append((filename, origin, name, exclude_constants)) def _get_json_mem_regions(self): mem_regions = {} - for filename, name, origin in self.jsons: - _, _, _mem_regions = export.load_csr_json(filename, name, origin) + for filename, origin, name, _ in self.jsons: + _, _, _mem_regions = export.load_csr_json(filename, origin, name) mem_regions.update(_mem_regions) return mem_regions def _get_json_constants(self): constants = {} - for filename, name, origin in self.jsons: - _, _constants, _ = export.load_csr_json(filename, name, origin) + for filename, origin, name, exclude in self.jsons: + _, _constants, _ = export.load_csr_json(filename, origin, name) + _constants = {k: v for k, v in _constants.items() if not any(ex in k for ex in exclude)} constants.update(_constants) return constants def _get_json_csr_regions(self): csr_regions = {} - for filename, name, origin in self.jsons: - _csr_regions, _, _ = export.load_csr_json(filename, name, origin) + for filename, origin, name, _ in self.jsons: + _csr_regions, _, _ = export.load_csr_json(filename, origin, name) csr_regions.update(_csr_regions) return csr_regions @@ -250,7 +253,10 @@ class Builder: csr_contents = export.get_csr_header( regions = self.soc.csr_regions, constants = self.soc.constants, - csr_base = self.soc.mem_regions["csr"].origin) + csr_base = self.soc.mem_regions["csr"].origin, + with_access_functions = True, + with_fields_access_functions = False, + ) write_to_file(os.path.join(self.generated_dir, "csr.h"), csr_contents) # Generate Git SHA1 of tools to git.h @@ -331,7 +337,7 @@ class Builder: ) # Initialize SoC with with BIOS data. - self.soc.initialize_rom(bios_data) + self.soc.init_rom(name="rom", contents=bios_data) def build(self, **kwargs): # Pass Output Directory to Platform. @@ -388,9 +394,16 @@ class Builder: self._prepare_rom_software() self._generate_rom_software(compile_bios=use_bios) + # Initialize Memories. + # Allow User Design to optionally initialize Memories through SoC.init_ram/init_rom. + if hasattr(self.soc, "init_mems"): + self.soc.init_mems(**kwargs) + # Initialize ROM. if use_bios and self.soc.integrated_rom_size: - self._initialize_rom_software() + # Only initialize if not already initialized. + if not getattr(self.soc, "rom").mem.init: + self._initialize_rom_software() # Translate compile_gateware to run. if "run" not in kwargs: diff --git a/litex/soc/integration/export.py b/litex/soc/integration/export.py index f04b03fef..5835e7c22 100644 --- a/litex/soc/integration/export.py +++ b/litex/soc/integration/export.py @@ -31,7 +31,7 @@ from migen import * from litex.soc.interconnect.csr import CSRStatus from litex.soc.integration.soc import SoCRegion -from litex.build.tools import generated_banner +from litex.build.tools import generated_separator, generated_banner from litex.soc.doc.rst import reflow from litex.soc.doc.module import gather_submodules, ModuleNotDocumented, DocumentedModule, DocumentedInterrupts @@ -136,6 +136,9 @@ def get_linker_regions(regions): # C Export ----------------------------------------------------------------------------------------- + +# Header. + def get_git_header(): from litex.build.tools import get_litex_git_revision r = generated_banner("//") @@ -193,25 +196,75 @@ def get_soc_header(constants, with_access_functions=True): r += "\n#endif\n" return r +def _generate_csr_header_includes_c(with_access_functions): + includes = "" + if with_access_functions: + includes += "#include \n" + includes += "#ifndef __GENERATED_CSR_H\n" + includes += "#define __GENERATED_CSR_H\n" + if with_access_functions: + includes += "#include \n" + includes += "#include \n" + includes += "#ifndef CSR_ACCESSORS_DEFINED\n" + includes += "#include \n" + includes += "#endif /* ! CSR_ACCESSORS_DEFINED */\n" + return includes + +def _generate_csr_base_define_c(csr_base, with_csr_base_define): + includes = "" + if with_csr_base_define: + includes += "\n" + includes += "#ifndef CSR_BASE\n" + includes += f"#define CSR_BASE {hex(csr_base)}L\n" + includes += "#endif /* ! CSR_BASE */\n" + return includes + +# CSR Definitions. + def _get_csr_addr(csr_base, addr, with_csr_base_define=True): if with_csr_base_define: return f"(CSR_BASE + {hex(addr)}L)" else: return f"{hex(csr_base + addr)}L" -def _get_rw_functions_c(reg_name, reg_base, nwords, busword, alignment, read_only, csr_base, with_csr_base_define, with_access_functions): - r = "" +def _generate_csr_definitions_c(reg_name, reg_base, nwords, csr_base, with_csr_base_define): + addr_str = f"CSR_{reg_name.upper()}_ADDR" + size_str = f"CSR_{reg_name.upper()}_SIZE" + definitions = f"#define {addr_str} {_get_csr_addr(csr_base, reg_base, with_csr_base_define)}\n" + definitions += f"#define {size_str} {nwords}\n" + return definitions - addr_str = f"CSR_{reg_name.upper()}_ADDR" - size_str = f"CSR_{reg_name.upper()}_SIZE" - r += f"#define {addr_str} {_get_csr_addr(csr_base, reg_base, with_csr_base_define)}\n" +def _generate_csr_region_definitions_c(name, region, origin, alignment, csr_base, with_csr_base_define): + base_define = with_csr_base_define and not isinstance(region, MockCSRRegion) + base = csr_base if not isinstance(region, MockCSRRegion) else 0 + region_defs = f"\n/* {name.upper()} Registers */\n" + region_defs += f"#define CSR_{name.upper()}_BASE {_get_csr_addr(base, origin, base_define)}\n" - r += f"#define {size_str} {nwords}\n" + if not isinstance(region.obj, Memory): + for csr in region.obj: + nr = (csr.size + region.busword - 1) // region.busword + region_defs += _generate_csr_definitions_c( + reg_name = name + "_" + csr.name, + reg_base = origin, + nwords = nr, + csr_base = base, + with_csr_base_define = base_define, + ) + origin += alignment // 8 * nr - size = nwords*busword//8 + region_defs += f"\n/* {name.upper()} Fields */\n" + if not isinstance(region.obj, Memory): + for csr in region.obj: + if hasattr(csr, "fields"): + region_defs += _generate_csr_field_definitions_c(csr, name) + + return region_defs + +# CSR Read/Write Access Functions. + +def _determine_ctype_and_stride_c(size, alignment): if size > 8: - # Downstream should select appropriate `csr_[rd|wr]_buf_uintX()` pair! - return r + return None, None elif size > 4: ctype = "uint64_t" elif size > 2: @@ -220,98 +273,177 @@ def _get_rw_functions_c(reg_name, reg_base, nwords, busword, alignment, read_onl ctype = "uint16_t" else: ctype = "uint8_t" + stride = alignment // 8 + return ctype, stride - stride = alignment//8; - if with_access_functions: - r += f"static inline {ctype} {reg_name}_read(void) {{\n" - if nwords > 1: - r += f"\t{ctype} r = csr_read_simple({_get_csr_addr(csr_base, reg_base, with_csr_base_define)});\n" - for sub in range(1, nwords): - r += f"\tr <<= {busword};\n" - r += f"\tr |= csr_read_simple({_get_csr_addr(csr_base, reg_base+sub*stride, with_csr_base_define)});\n" - r += "\treturn r;\n}\n" - else: - r += f"\treturn csr_read_simple({_get_csr_addr(csr_base, reg_base, with_csr_base_define)});\n}}\n" +def _generate_csr_read_function_c(reg_name, reg_base, nwords, busword, ctype, stride, csr_base, with_csr_base_define): + read_function = f"static inline {ctype} {reg_name}_read(void) {{\n" + if nwords > 1: + read_function += f"\t{ctype} r = csr_read_simple({_get_csr_addr(csr_base, reg_base, with_csr_base_define)});\n" + for sub in range(1, nwords): + read_function += f"\tr <<= {busword};\n" + read_function += f"\tr |= csr_read_simple({_get_csr_addr(csr_base, reg_base + sub * stride, with_csr_base_define)});\n" + read_function += "\treturn r;\n}\n" + else: + read_function += f"\treturn csr_read_simple({_get_csr_addr(csr_base, reg_base, with_csr_base_define)});\n}}\n" + return read_function - if not read_only: - r += f"static inline void {reg_name}_write({ctype} v) {{\n" - for sub in range(nwords): - shift = (nwords-sub-1)*busword - if shift: - v_shift = "v >> {}".format(shift) - else: - v_shift = "v" - r += f"\tcsr_write_simple({v_shift}, {_get_csr_addr(csr_base, reg_base+sub*stride, with_csr_base_define)});\n" - r += "}\n" - return r +def _generate_csr_write_function_c(reg_name, reg_base, nwords, busword, ctype, stride, csr_base, with_csr_base_define): + write_function = f"static inline void {reg_name}_write({ctype} v) {{\n" + for sub in range(nwords): + shift = (nwords - sub - 1) * busword + v_shift = f"v >> {shift}" if shift else "v" + write_function += f"\tcsr_write_simple({v_shift}, {_get_csr_addr(csr_base, reg_base + sub * stride, with_csr_base_define)});\n" + write_function += "}\n" + return write_function +def _get_csr_read_write_access_functions_c(reg_name, reg_base, nwords, busword, alignment, read_only, csr_base, with_csr_base_define): + result = "" + size = nwords * busword // 8 + + ctype, stride = _determine_ctype_and_stride_c(size, alignment) + if ctype is None: + return result + + result += _generate_csr_read_function_c(reg_name, reg_base, nwords, busword, ctype, stride, csr_base, with_csr_base_define) + if not read_only: + result += _generate_csr_write_function_c(reg_name, reg_base, nwords, busword, ctype, stride, csr_base, with_csr_base_define) + + return result + +def _generate_csr_region_access_functions_c(name, region, origin, alignment, csr_base, with_csr_base_define): + base_define = with_csr_base_define and not isinstance(region, MockCSRRegion) + region_defs = f"\n/* {name.upper()} Access Functions */\n" + + if not isinstance(region.obj, Memory): + for csr in region.obj: + nr = (csr.size + region.busword - 1) // region.busword + region_defs += _get_csr_read_write_access_functions_c( + reg_name = name + "_" + csr.name, + reg_base = origin, + nwords = nr, + busword = region.busword, + alignment = alignment, + read_only = getattr(csr, "read_only", False), + csr_base = csr_base, + with_csr_base_define = base_define, + ) + origin += alignment // 8 * nr + return region_defs + +# CSR Fields. + +def _generate_csr_field_definitions_c(csr, name): + field_defs = "" + for field in csr.fields.fields: + offset = str(field.offset) + size = str(field.size) + field_defs += f"#define CSR_{name.upper()}_{csr.name.upper()}_{field.name.upper()}_OFFSET {offset}\n" + field_defs += f"#define CSR_{name.upper()}_{csr.name.upper()}_{field.name.upper()}_SIZE {size}\n" + return field_defs + +def _generate_csr_field_accessors_c(name, csr, field): + accessors = "" + if csr.size <= 32: + reg_name = name + "_" + csr.name.lower() + field_name = reg_name + "_" + field.name.lower() + offset = str(field.offset) + size = str(field.size) + accessors += f"static inline uint32_t {field_name}_extract(uint32_t oldword) {{\n" + accessors += f"\tuint32_t mask = 0x{(1 << int(size)) - 1:x};\n" + accessors += f"\treturn ((oldword >> {offset}) & mask);\n}}\n" + accessors += f"static inline uint32_t {field_name}_read(void) {{\n" + accessors += f"\tuint32_t word = {reg_name}_read();\n" + accessors += f"\treturn {field_name}_extract(word);\n}}\n" + if not getattr(csr, "read_only", False): + accessors += f"static inline uint32_t {field_name}_replace(uint32_t oldword, uint32_t plain_value) {{\n" + accessors += f"\tuint32_t mask = 0x{(1 << int(size)) - 1:x};\n" + accessors += f"\treturn (oldword & (~(mask << {offset}))) | ((mask & plain_value) << {offset});\n}}\n" + accessors += f"static inline void {field_name}_write(uint32_t plain_value) {{\n" + accessors += f"\tuint32_t oldword = {reg_name}_read();\n" + accessors += f"\tuint32_t newword = {field_name}_replace(oldword, plain_value);\n" + accessors += f"\t{reg_name}_write(newword);\n}}\n" + return accessors + +def _generate_csr_field_functions_c(csr, name): + field_funcs = "" + for field in csr.fields.fields: + field_funcs += _generate_csr_field_accessors_c(name, csr, field) + return field_funcs + +def _generate_csr_fields_access_functions_c(name, region, origin, alignment, csr_base, with_csr_base_define): + base_define = with_csr_base_define and not isinstance(region, MockCSRRegion) + region_defs = f"\n/* {name.upper()} Fields Access Functions */\n" + + if not isinstance(region.obj, Memory): + for csr in region.obj: + nr = (csr.size + region.busword - 1) // region.busword + origin += alignment // 8 * nr + if hasattr(csr, "fields"): + region_defs += _generate_csr_field_functions_c(csr, name) + return region_defs + +# CSR Header. + +def get_csr_header(regions, constants, csr_base=None, with_csr_base_define=True, with_access_functions=True, with_fields_access_functions=False): + """ + Generate the CSR header file content. + """ -def get_csr_header(regions, constants, csr_base=None, with_csr_base_define=True, with_access_functions=True): alignment = constants.get("CONFIG_CSR_ALIGNMENT", 32) r = generated_banner("//") - if with_access_functions: # FIXME - r += "#include \n" - r += "#ifndef __GENERATED_CSR_H\n#define __GENERATED_CSR_H\n" - if with_access_functions: - r += "#include \n" - r += "#include \n" - r += "#ifndef CSR_ACCESSORS_DEFINED\n" - r += "#include \n" - r += "#endif /* ! CSR_ACCESSORS_DEFINED */\n" + + # CSR Includes. + r += "\n" + r += generated_separator("//", "CSR Includes.") + r += "\n" + r += _generate_csr_header_includes_c(with_access_functions) _csr_base = regions[next(iter(regions))].origin - csr_base = csr_base if csr_base is not None else _csr_base - if with_csr_base_define: - r += "\n#ifndef CSR_BASE\n" - r += f"#define CSR_BASE {hex(csr_base)}L\n" - r += "#endif\n" + csr_base = csr_base if csr_base is not None else _csr_base + r += _generate_csr_base_define_c(csr_base, with_csr_base_define) + + # CSR Registers/Fields Definition. + r += "\n" + r += generated_separator("//", "CSR Registers/Fields Definition.") for name, region in regions.items(): origin = region.origin - _csr_base - r += "\n/* "+name+" */\n" - r += f"#define CSR_{name.upper()}_BASE {_get_csr_addr(csr_base, origin, with_csr_base_define)}\n" - if not isinstance(region.obj, Memory): - for csr in region.obj: - nr = (csr.size + region.busword - 1)//region.busword - r += _get_rw_functions_c( - reg_name = name + "_" + csr.name, - reg_base = origin, - nwords = nr, - busword = region.busword, - alignment = alignment, - read_only = getattr(csr, "read_only", False), - csr_base = csr_base, - with_csr_base_define = with_csr_base_define, - with_access_functions = with_access_functions, - ) - origin += alignment//8*nr - if hasattr(csr, "fields"): - for field in csr.fields.fields: - offset = str(field.offset) - size = str(field.size) - r += f"#define CSR_{name.upper()}_{csr.name.upper()}_{field.name.upper()}_OFFSET {offset}\n" - r += f"#define CSR_{name.upper()}_{csr.name.upper()}_{field.name.upper()}_SIZE {size}\n" - if with_access_functions and csr.size <= 32: # FIXME: Implement extract/read functions for csr.size > 32-bit. - reg_name = name + "_" + csr.name.lower() - field_name = reg_name + "_" + field.name.lower() - r += "static inline uint32_t " + field_name + "_extract(uint32_t oldword) {\n" - r += f"\tuint32_t mask = 0x{(1<= self.sys_clk_freq, ) diff --git a/litex/soc/integration/soc_core.py b/litex/soc/integration/soc_core.py index 28611d197..0373c5058 100644 --- a/litex/soc/integration/soc_core.py +++ b/litex/soc/integration/soc_core.py @@ -42,7 +42,7 @@ __all__ = [ # SoCCore ------------------------------------------------------------------------------------------ class SoCCore(LiteXSoC): - # Default register/interrupt/memory mappings (can be redefined by user) + # Default register/interrupt/memory mappings (can be redefined by user). csr_map = {} interrupt_map = {} mem_map = { @@ -52,7 +52,7 @@ class SoCCore(LiteXSoC): } def __init__(self, platform, clk_freq, - # Bus parameters + # Bus parameters. bus_standard = "wishbone", bus_data_width = 32, bus_address_width = 32, @@ -60,62 +60,67 @@ class SoCCore(LiteXSoC): bus_bursting = False, bus_interconnect = "shared", - # CPU parameters + # CPU parameters. cpu_type = "vexriscv", cpu_reset_address = None, cpu_variant = None, cpu_cfu = None, - # CFU parameters + # CFU parameters. cfu_filename = None, - # ROM parameters + # ROM parameters. integrated_rom_size = 0, integrated_rom_mode = "rx", integrated_rom_init = [], - # SRAM parameters + # SRAM parameters. integrated_sram_size = 0x2000, integrated_sram_init = [], - # MAIN_RAM parameters + # MAIN_RAM parameters. integrated_main_ram_size = 0, integrated_main_ram_init = [], - # CSR parameters + # CSR parameters. csr_data_width = 32, csr_address_width = 14, csr_paging = 0x800, csr_ordering = "big", - # Interrupt parameters + # Interrupt parameters. irq_n_irqs = 32, - # Identifier parameters + # Identifier parameters. ident = "", ident_version = False, - # UART parameters + # UART parameters. with_uart = True, uart_name = "serial", uart_baudrate = 115200, uart_fifo_depth = 16, - # Timer parameters + # Timer parameters. with_timer = True, timer_uptime = False, - # Controller parameters + # Controller parameters. with_ctrl = True, - # JTAGBone + # JTAGBone. with_jtagbone = False, jtagbone_chain = 1, - # UARTBone + # UARTBone. with_uartbone = False, - # Others + # Watchdog. + with_watchdog = False, + watchdog_width = 32, + watchdog_reset_delay = None, + + # Others. **kwargs): # New LiteXSoC class ----------------------------------------------------------------------- @@ -138,7 +143,7 @@ class SoCCore(LiteXSoC): irq_reserved_irqs = {}, ) - # Attributes + # Attributes. self.mem_regions = self.bus.regions self.clk_freq = self.sys_clk_freq self.mem_map = self.mem_map @@ -198,28 +203,29 @@ class SoCCore(LiteXSoC): # JTAGBone and jtag_uart can't be used at the same time. assert not (with_jtagbone and uart_name == "jtag_uart") + # UARTBone and serial can't be used at the same time. assert not (with_uartbone and uart_name == "serial") # Modules instances ------------------------------------------------------------------------ - # Add SoCController + # Add SoCController. if with_ctrl: self.add_controller("ctrl") - # Add CPU + # Add CPU. self.add_cpu( name = str(cpu_type), variant = "standard" if cpu_variant is None else cpu_variant, reset_address = None if integrated_rom_size else cpu_reset_address, cfu = cpu_cfu) - # Add User's interrupts + # Add User's interrupts. if self.irq.enabled: for name, loc in self.interrupt_map.items(): self.irq.add(name, loc) - # Add integrated ROM + # Add integrated ROM. if integrated_rom_size: self.add_rom("rom", origin = self.cpu.reset_address, @@ -228,14 +234,14 @@ class SoCCore(LiteXSoC): mode = integrated_rom_mode ) - # Add integrated SRAM + # Add integrated SRAM. if integrated_sram_size: self.add_ram("sram", origin = self.mem_map["sram"], size = integrated_sram_size, ) - # Add integrated MAIN_RAM (only useful when no external SRAM/SDRAM is available) + # Add integrated MAIN_RAM (only useful when no external SRAM/SDRAM is available). if integrated_main_ram_size: self.add_ram("main_ram", origin = self.mem_map["main_ram"], @@ -243,36 +249,37 @@ class SoCCore(LiteXSoC): contents = integrated_main_ram_init, ) - # Add Identifier + # Add Identifier. if ident != "": self.add_identifier("identifier", identifier=ident, with_build_time=ident_version) - # Add UARTBone + # Add UARTBone. if with_uartbone: self.add_uartbone(baudrate=uart_baudrate) - # Add UART + # Add UART. if with_uart: self.add_uart(name="uart", uart_name=uart_name, baudrate=uart_baudrate, fifo_depth=uart_fifo_depth) - # Add JTAGBone + # Add JTAGBone. if with_jtagbone: self.add_jtagbone(chain=jtagbone_chain) - # Add Timer + # Add Timer. if with_timer: self.add_timer(name="timer0") if timer_uptime: self.timer0.add_uptime() + # Add Watchdog. + if with_watchdog: + self.add_watchdog(name="watchdog0" ,width=watchdog_width, reset_delay=watchdog_reset_delay) + # Methods -------------------------------------------------------------------------------------- def add_csr(self, csr_name, csr_id=None, use_loc_if_exists=False): self.csr.add(csr_name, csr_id, use_loc_if_exists=use_loc_if_exists) - def initialize_rom(self, data): - self.init_rom(name="rom", contents=data) - def add_memory_region(self, name, origin, length, type="cached"): self.bus.add_region(name, SoCRegion(origin=origin, size=length, cached="cached" in type, @@ -286,7 +293,7 @@ class SoCCore(LiteXSoC): def soc_core_args(parser): soc_group = parser.add_argument_group(title="SoC options") - # Bus parameters + # Bus parameters. soc_group.add_argument("--bus-standard", default="wishbone", help="Select bus standard: {}.".format(", ".join(SoCBusHandler.supported_standard))) soc_group.add_argument("--bus-data-width", default=32, type=auto_int, help="Bus data-width.") soc_group.add_argument("--bus-address-width", default=32, type=auto_int, help="Bus address-width.") @@ -294,53 +301,58 @@ def soc_core_args(parser): soc_group.add_argument("--bus-bursting", action="store_true", help="Enable burst cycles on the bus if supported.") soc_group.add_argument("--bus-interconnect", default="shared", help="Select bus interconnect: shared (default) or crossbar.") - # CPU parameters + # CPU parameters. soc_group.add_argument("--cpu-type", default="vexriscv", help="Select CPU: {}.".format(", ".join(iter(cpu.CPUS.keys())))) soc_group.add_argument("--cpu-variant", default=None, help="CPU variant.") soc_group.add_argument("--cpu-reset-address", default=None, type=auto_int, help="CPU reset address (Boot from Integrated ROM by default).") soc_group.add_argument("--cpu-cfu", default=None, help="Optional CPU CFU file/instance to add to the CPU.") - # Controller parameters + # Controller parameters. soc_group.add_argument("--no-ctrl", action="store_true", help="Disable Controller.") - # ROM parameters + # ROM parameters. soc_group.add_argument("--integrated-rom-size", default=0x20000, type=auto_int, help="Size/Enable the integrated (BIOS) ROM (Automatically resized to BIOS size when smaller).") soc_group.add_argument("--integrated-rom-init", default=None, type=str, help="Integrated ROM binary initialization file (override the BIOS when specified).") - # SRAM parameters + # SRAM parameters. soc_group.add_argument("--integrated-sram-size", default=0x2000, type=auto_int, help="Size/Enable the integrated SRAM.") - # MAIN_RAM parameters + # MAIN_RAM parameters. soc_group.add_argument("--integrated-main-ram-size", default=None, type=auto_int, help="size/enable the integrated main RAM.") - # CSR parameters + # CSR parameters. soc_group.add_argument("--csr-data-width", default=32 , type=auto_int, help="CSR bus data-width (8 or 32).") soc_group.add_argument("--csr-address-width", default=14, type=auto_int, help="CSR bus address-width.") soc_group.add_argument("--csr-paging", default=0x800, type=auto_int, help="CSR bus paging.") soc_group.add_argument("--csr-ordering", default="big", help="CSR registers ordering (big or little).") - # Identifier parameters + # Identifier parameters. soc_group.add_argument("--ident", default=None, type=str, help="SoC identifier.") soc_group.add_argument("--no-ident-version", action="store_true", help="Disable date/time in SoC identifier.") - # UART parameters + # UART parameters. soc_group.add_argument("--no-uart", action="store_true", help="Disable UART.") soc_group.add_argument("--uart-name", default="serial", type=str, help="UART type/name.") soc_group.add_argument("--uart-baudrate", default=115200, type=auto_int, help="UART baudrate.") soc_group.add_argument("--uart-fifo-depth", default=16, type=auto_int, help="UART FIFO depth.") - # UARTBone parameters + # UARTBone parameters. soc_group.add_argument("--with-uartbone", action="store_true", help="Enable UARTbone.") - # JTAGBone parameters + # JTAGBone parameters. soc_group.add_argument("--with-jtagbone", action="store_true", help="Enable Jtagbone support.") soc_group.add_argument("--jtagbone-chain", default=1, type=int, help="Jtagbone chain index.") - # Timer parameters + # Timer parameters. soc_group.add_argument("--no-timer", action="store_true", help="Disable Timer.") soc_group.add_argument("--timer-uptime", action="store_true", help="Add an uptime capability to Timer.") - # L2 Cache + # Watchdog parameters. + soc_group.add_argument("--with-watchdog", action="store_true", help="Enable Watchdog.") + soc_group.add_argument("--watchdog-width", default=32, type=auto_int, help="Watchdog width.") + soc_group.add_argument("--watchdog-reset-delay", default=None, type=auto_int, help="Watchdog width.") + + # L2 Cache. soc_group.add_argument("--l2-size", default=8192, type=auto_int, help="L2 cache size.") def soc_core_argdict(args): @@ -366,7 +378,7 @@ def soc_core_argdict(args): r[a] = arg return r -# SoCMini --------------------------------------------------------------------------------------- +# SoCMini ------------------------------------------------------------------------------------------ class SoCMini(SoCCore): def __init__(self, *args, **kwargs): @@ -381,7 +393,7 @@ class SoCMini(SoCCore): SoCCore.__init__(self, *args, **kwargs) -# SoCMini arguments ----------------------------------------------------------------------------- +# SoCMini arguments -------------------------------------------------------------------------------- soc_mini_args = soc_core_args soc_mini_argdict = soc_core_argdict diff --git a/litex/soc/interconnect/ahb.py b/litex/soc/interconnect/ahb.py index 178eeb346..9935a4d67 100644 --- a/litex/soc/interconnect/ahb.py +++ b/litex/soc/interconnect/ahb.py @@ -41,11 +41,12 @@ def ahb_description(data_width, address_width): ] class AHBInterface(Record): - def __init__(self, data_width=32, address_width=32): + def __init__(self, data_width=32, address_width=32, addressing="byte"): + assert addressing == "byte" Record.__init__(self, ahb_description(data_width, address_width)) self.data_width = data_width self.address_width = address_width - self.addressing = "byte" + self.addressing = addressing # AHB to Wishbone --------------------------------------------------------------------------------- diff --git a/litex/soc/interconnect/axi/axi_lite.py b/litex/soc/interconnect/axi/axi_lite.py index fb3908daa..b5aa754a3 100644 --- a/litex/soc/interconnect/axi/axi_lite.py +++ b/litex/soc/interconnect/axi/axi_lite.py @@ -57,6 +57,7 @@ class AXILiteInterface: # ----------- self.data_width = data_width self.address_width = address_width + self.bursting = bursting self.addressing = addressing self.clock_domain = clock_domain @@ -148,7 +149,7 @@ class AXILiteRemapper(LiteXModule): # AXI-Lite to Simple Bus --------------------------------------------------------------------------- -def axi_lite_to_simple(axi_lite, port_adr, port_dat_r, port_dat_w=None, port_we=None): +def axi_lite_to_simple(axi_lite, port_adr, port_dat_r, port_dat_w=None, port_re=None, port_we=None): """Connection of AXILite to simple bus with 1-cycle latency, such as CSR bus or Memory port""" bus_data_width = axi_lite.data_width adr_shift = log2_int(bus_data_width//8) @@ -168,6 +169,11 @@ def axi_lite_to_simple(axi_lite, port_adr, port_dat_r, port_dat_w=None, port_we= else: comb.append(port_we.eq(axi_lite.w.valid & axi_lite.w.ready & (axi_lite.w.strb != 0))) + if port_re is not None: + comb.append(port_re.eq(axi_lite.ar.valid & axi_lite.ar.ready)) + + port_adr_reg = Signal(len(port_adr)) + fsm = FSM() fsm.act("START-TRANSACTION", # If the last access was a read, do a write, and vice versa. @@ -186,12 +192,24 @@ def axi_lite_to_simple(axi_lite, port_adr, port_dat_r, port_dat_w=None, port_we= If(axi_lite.w.valid, axi_lite.w.ready.eq(1), NextState("SEND-WRITE-RESPONSE") + ).Else( + # write data is not yet available - register the address + # and wait until the master provides the data + NextValue(port_adr_reg, port_adr), + NextState("WAIT-FOR-WRITE-DATA") ) ).Elif(do_read, port_adr.eq(axi_lite.ar.addr[adr_shift:]), NextState("LATCH-READ-RESPONSE"), ) ) + fsm.act("WAIT-FOR-WRITE-DATA", + port_adr.eq(port_adr_reg), + If(axi_lite.w.valid, + axi_lite.w.ready.eq(1), + NextState("SEND-WRITE-RESPONSE") + ) + ), fsm.act("LATCH-READ-RESPONSE", NextValue(port_dat_r_latched, port_dat_r), NextState("SEND-READ-RESPONSE") @@ -219,6 +237,7 @@ def axi_lite_to_simple(axi_lite, port_adr, port_dat_r, port_dat_w=None, port_we= # AXI-Lite SRAM ------------------------------------------------------------------------------------ class AXILiteSRAM(LiteXModule): + autocsr_exclude = {"mem"} def __init__(self, mem_or_size, read_only=None, init=None, bus=None, name=None): if bus is None: bus = AXILiteInterface() @@ -240,8 +259,11 @@ class AXILiteSRAM(LiteXModule): # # # # Create memory port - port = self.mem.get_port(write_capable=not read_only, we_granularity=8, - mode=READ_FIRST if read_only else WRITE_FIRST) + port = self.mem.get_port( + write_capable = not read_only, + we_granularity = 8, + mode = READ_FIRST if read_only else WRITE_FIRST, + ) self.specials += self.mem, port # Generate write enable signal @@ -257,7 +279,7 @@ class AXILiteSRAM(LiteXModule): port_dat_r = port.dat_r, port_dat_w = port.dat_w if not read_only else None, port_we = port.we if not read_only else None) - self.fsm = fsm + self.submodules.fsm = fsm self.comb += comb # AXI-Lite Data-Width Converter -------------------------------------------------------------------- diff --git a/litex/soc/interconnect/axi/axi_lite_to_csr.py b/litex/soc/interconnect/axi/axi_lite_to_csr.py index b21dfa5b8..d6ceb68c4 100644 --- a/litex/soc/interconnect/axi/axi_lite_to_csr.py +++ b/litex/soc/interconnect/axi/axi_lite_to_csr.py @@ -32,6 +32,7 @@ class AXILite2CSR(LiteXModule): fsm, comb = axi_lite_to_simple( axi_lite = self.axi_lite, port_adr = self.csr.adr, + port_re = self.csr.re, port_dat_r = self.csr.dat_r, port_dat_w = self.csr.dat_w, port_we = self.csr.we) diff --git a/litex/soc/interconnect/csr_bus.py b/litex/soc/interconnect/csr_bus.py index 6a60f35cb..29e798c5d 100644 --- a/litex/soc/interconnect/csr_bus.py +++ b/litex/soc/interconnect/csr_bus.py @@ -18,9 +18,11 @@ sys_clk domain of the SoC, completing writes in a single cycle and reads in two ┌───────────┐ Write in 1 cycle: │ │ - adr/we/dat_w set by bridge. │ ├───► adr - │ │ Read in 2 cycles: - Main SoC Bus ◄────► CSR ├───► we - adr set by bridge - │ Bridge │ - dat_r set returned by user logic. + | | Read in 2 cycles: + │ ├───► re - adr and re set by bridge. + Main SoC Bus ◄────► │ - User logic can ignore re if there is no reading side effect. + | CSR ├───► we - dat_r set returned by user logic. + │ Bridge │ │ ├───► dat_w │ │ │ ◄──── dat_r @@ -46,6 +48,7 @@ from litex.soc.interconnect.csr import CSRStorage _layout = [ ("adr", "address_width", DIR_M_TO_S), + ("re", 1, DIR_M_TO_S), ("we", 1, DIR_M_TO_S), ("dat_w", "data_width", DIR_M_TO_S), ("dat_r", "data_width", DIR_S_TO_M) @@ -81,9 +84,11 @@ class Interface(Record): def read(self, adr): yield self.adr.eq(adr) + yield self.re.eq(1) yield - yield - return (yield self.dat_r) + value = (yield self.dat_r) + yield self.re.eq(0) + return value # CSR Interconnect --------------------------------------------------------------------------------- @@ -97,6 +102,7 @@ class InterconnectShared(Module): intermediate = Interface.like(masters[0]) self.comb += [ intermediate.adr.eq( Reduce("OR", [masters[i].adr for i in range(len(masters))])), + intermediate.re.eq( Reduce("OR", [masters[i].re for i in range(len(masters))])), intermediate.we.eq( Reduce("OR", [masters[i].we for i in range(len(masters))])), intermediate.dat_w.eq(Reduce("OR", [masters[i].dat_w for i in range(len(masters))])) ] @@ -207,8 +213,8 @@ class CSRBank(csr.GenericBank): self.comb += [ c.r.eq(self.bus.dat_w[:c.size]), If(sel & (self.bus.adr[:log2_int(aligned_paging)] == i), - c.re.eq( self.bus.we), - c.we.eq(~self.bus.we) + c.re.eq(self.bus.we), + c.we.eq(self.bus.re) ) ] diff --git a/litex/soc/interconnect/packet.py b/litex/soc/interconnect/packet.py index dfb504e94..762fcd8d9 100644 --- a/litex/soc/interconnect/packet.py +++ b/litex/soc/interconnect/packet.py @@ -23,10 +23,10 @@ class Status(LiteXModule): self.ongoing = Signal() ongoing = Signal() - self.comb += If(endpoint.valid, self.last.eq(endpoint.last & endpoint.ready)) - self.sync += ongoing.eq((endpoint.valid | ongoing) & ~self.last) + self.comb += self.last.eq(endpoint.valid & endpoint.last & endpoint.ready) self.comb += self.ongoing.eq((endpoint.valid | ongoing) & ~self.last) self.sync += [ + ongoing.eq(self.ongoing), If(self.last, self.first.eq(1) ).Elif(endpoint.valid & endpoint.ready, @@ -101,7 +101,7 @@ class Dispatcher(LiteXModule): # Header ------------------------------------------------------------------------------------------- class HeaderField: - def __init__(self, byte, offset, width): + def __init__(self, byte=0, offset=0, width=1): self.byte = byte self.offset = offset self.width = width @@ -386,7 +386,7 @@ class PacketFIFO(LiteXModule): sink.connect(param_fifo.sink, keep=set([e[0] for e in param_layout])), sink.connect(payload_fifo.sink, keep=set([e[0] for e in payload_layout] + ["last"])), param_fifo.sink.valid.eq(sink.valid & sink.last), - payload_fifo.sink.valid.eq(sink.valid & payload_fifo.sink.ready), + payload_fifo.sink.valid.eq(sink.valid & param_fifo.sink.ready), sink.ready.eq(param_fifo.sink.ready & payload_fifo.sink.ready), ] diff --git a/litex/soc/interconnect/stream.py b/litex/soc/interconnect/stream.py index c7d521221..2703c6586 100644 --- a/litex/soc/interconnect/stream.py +++ b/litex/soc/interconnect/stream.py @@ -254,8 +254,16 @@ class ClockDomainCrossing(LiteXModule, DUID): # Same Clk Domains. if cd_from == cd_to: - # No adaptation. - self.comb += self.sink.connect(self.source) + if buffered: + # Add Buffer. + self.buffer = ClockDomainsRenamer(cd_from)(Buffer(layout)) + self.comb += [ + self.sink.connect(self.buffer.sink), + self.buffer.source.connect(self.source), + ] + else: + # No adaptation. + self.comb += self.sink.connect(self.source) # Different Clk Domains. else: if with_common_rst: @@ -688,19 +696,22 @@ class Monitor(LiteXModule): # Generic Monitor Counter ------------------------------------------------------------------ class MonitorCounter(Module): def __init__(self, reset, latch, enable, count): - _count = Signal.like(count) - _count_latched = Signal.like(count) + _count = Signal(len(count), reset_less=True) + _count_latched = Signal(len(count), reset_less=True) _sync = getattr(self.sync, clock_domain) _sync += [ + # Count. If(reset, _count.eq(0), - _count_latched.eq(0), ).Elif(enable, If(_count != (2**len(count)-1), _count.eq(_count + 1) ) ), - If(latch, + # Latch. + If(reset, + _count_latched.eq(0), + ).Elif(latch, _count_latched.eq(_count) ) ] diff --git a/litex/soc/interconnect/wishbone.py b/litex/soc/interconnect/wishbone.py index 57eead4df..3cf2e62de 100644 --- a/litex/soc/interconnect/wishbone.py +++ b/litex/soc/interconnect/wishbone.py @@ -90,6 +90,8 @@ class Interface(Record): yield self.bte.eq(bte) yield self.we.eq(1) yield from self._do_transaction() + if (yield self.err): + raise ValueError("bus error") def read(self, adr, cti=None, bte=None): yield self.adr.eq(adr) @@ -99,6 +101,8 @@ class Interface(Record): if bte is not None: yield self.bte.eq(bte) yield from self._do_transaction() + if (yield self.err): + raise ValueError("bus error") return (yield self.dat_r) def get_ios(self, bus_name="wb"): @@ -459,7 +463,8 @@ class Converter(LiteXModule): # Wishbone SRAM ------------------------------------------------------------------------------------ -class SRAM(Module): # FIXME: Switch to LiteXModule. +class SRAM(LiteXModule): + autocsr_exclude = {"mem"} def __init__(self, mem_or_size, read_only=None, write_only=None, init=None, bus=None, name=None): if bus is None: bus = Interface(data_width=32, address_width=32, addressing="word") @@ -554,8 +559,11 @@ class SRAM(Module): # FIXME: Switch to LiteXModule. # Memory. # ------- - port = self.mem.get_port(write_capable=not read_only, we_granularity=8, - mode=READ_FIRST if read_only else WRITE_FIRST) + port = self.mem.get_port( + write_capable = not read_only, + we_granularity = 8, + mode = READ_FIRST if read_only else WRITE_FIRST, + ) self.specials += self.mem, port # Generate write enable signal if not read_only: @@ -607,12 +615,14 @@ class Wishbone2CSR(LiteXModule): NextValue(self.csr.dat_w, self.wishbone.dat_w), If(self.wishbone.cyc & self.wishbone.stb, NextValue(self.csr.adr, self.wishbone.adr[wishbone_adr_shift:]), - NextValue(self.csr.we, self.wishbone.we & (self.wishbone.sel != 0)), + NextValue(self.csr.re, ~self.wishbone.we & (self.wishbone.sel != 0)), + NextValue(self.csr.we, self.wishbone.we & (self.wishbone.sel != 0)), NextState("WRITE-READ") ) ) fsm.act("WRITE-READ", NextValue(self.csr.adr, 0), + NextValue(self.csr.re, 0), NextValue(self.csr.we, 0), NextState("ACK") ) @@ -628,7 +638,8 @@ class Wishbone2CSR(LiteXModule): self.csr.dat_w.eq(self.wishbone.dat_w), If(self.wishbone.cyc & self.wishbone.stb, self.csr.adr.eq(self.wishbone.adr[wishbone_adr_shift:]), - self.csr.we.eq(self.wishbone.we & (self.wishbone.sel != 0)), + self.csr.re.eq(~self.wishbone.we & (self.wishbone.sel != 0)), + self.csr.we.eq( self.wishbone.we & (self.wishbone.sel != 0)), NextState("ACK") ) ) diff --git a/litex/soc/software/bios/helpers.c b/litex/soc/software/bios/helpers.c index 0f8e9bfab..6b384ec0e 100644 --- a/litex/soc/software/bios/helpers.c +++ b/litex/soc/software/bios/helpers.c @@ -18,18 +18,24 @@ extern unsigned int _ftext, _edata_rom; #define NUMBER_OF_BYTES_ON_A_LINE 16 void dump_bytes(unsigned int *ptr, int count, unsigned long addr) { - char *data = (char *)ptr; + uint32_t *dptr = (uint32_t *)ptr; + char data[NUMBER_OF_BYTES_ON_A_LINE]; int line_bytes = 0, i = 0; + fputs("Memory dump:", stdout); while (count > 0) { line_bytes = (count > NUMBER_OF_BYTES_ON_A_LINE)? NUMBER_OF_BYTES_ON_A_LINE : count; + for (i = 0; i < line_bytes; i+=4){ + *((uint32_t*)&data[i]) = *(dptr++); + } + printf("\n0x%08lx ", addr); for (i = 0; i < line_bytes; i++) - printf("%02x ", *(unsigned char *)(data+i)); + printf("%02x ", (unsigned char)data[i]); for (; i < NUMBER_OF_BYTES_ON_A_LINE; i++) printf(" "); @@ -37,16 +43,15 @@ void dump_bytes(unsigned int *ptr, int count, unsigned long addr) printf(" "); for (i = 0; i 0x7e)) + if ((data[i] < 0x20) || (data[i] > 0x7e)) printf("."); else - printf("%c", *(data+i)); + printf("%c", data[i]); } for (; i < NUMBER_OF_BYTES_ON_A_LINE; i++) printf(" "); - data += (char)line_bytes; count -= line_bytes; addr += line_bytes; } diff --git a/litex/soc/software/bios/main.c b/litex/soc/software/bios/main.c index 67ca3d590..3a86cca36 100644 --- a/litex/soc/software/bios/main.c +++ b/litex/soc/software/bios/main.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -173,7 +174,11 @@ __attribute__((__used__)) int main(int i, char **c) printf("\n"); #endif - sdr_ok = 1; + sdr_ok = 1; + +#ifdef CSR_HYPERRAM_BASE + hyperram_init(); +#endif #if defined(CSR_ETHMAC_BASE) || defined(MAIN_RAM_BASE) || defined(CSR_SPIFLASH_CORE_BASE) printf("--========== \e[1mInitialization\e[0m ============--\n"); diff --git a/litex/soc/software/libbase/Makefile b/litex/soc/software/libbase/Makefile index 6b8736b17..fab41e466 100755 --- a/litex/soc/software/libbase/Makefile +++ b/litex/soc/software/libbase/Makefile @@ -11,7 +11,8 @@ OBJECTS = \ uart.o \ spiflash.o \ i2c.o \ - isr.o + isr.o \ + hyperram.o all: libbase.a diff --git a/litex/soc/software/libbase/hyperram.c b/litex/soc/software/libbase/hyperram.c new file mode 100644 index 000000000..9dccd581c --- /dev/null +++ b/litex/soc/software/libbase/hyperram.c @@ -0,0 +1,96 @@ +// This file is Copyright (c) 2024 Florent Kermarrec +// License: BSD + +#include + +#include + +#include + +#ifdef CSR_HYPERRAM_BASE + +static void hyperram_write_reg(uint16_t reg_addr, uint16_t data) { + /* Write data to the register */ + hyperram_reg_wdata_write(data); + hyperram_reg_control_write( + 1 << CSR_HYPERRAM_REG_CONTROL_WRITE_OFFSET | + 0 << CSR_HYPERRAM_REG_CONTROL_READ_OFFSET | + reg_addr << CSR_HYPERRAM_REG_CONTROL_ADDR_OFFSET + ); + /* Wait for write to complete */ + while ((hyperram_reg_status_read() & (1 << CSR_HYPERRAM_REG_STATUS_WRITE_DONE_OFFSET)) == 0); + } + +static uint16_t hyperram_read_reg(uint16_t reg_addr) { + /* Read data from the register */ + hyperram_reg_control_write( + 0 << CSR_HYPERRAM_REG_CONTROL_WRITE_OFFSET | + 1 << CSR_HYPERRAM_REG_CONTROL_READ_OFFSET | + reg_addr << CSR_HYPERRAM_REG_CONTROL_ADDR_OFFSET + ); + /* Wait for read to complete */ + while ((hyperram_reg_status_read() & (1 << CSR_HYPERRAM_REG_STATUS_READ_DONE_OFFSET)) == 0); + return hyperram_reg_rdata_read(); +} + +/* Configuration and Utility Functions */ + +static uint16_t hyperram_get_core_latency_setting(uint32_t clk_freq) { + /* Raw clock latency settings for the HyperRAM core */ + if (clk_freq <= 85000000) return 3; /* 3 Clock Latency */ + if (clk_freq <= 104000000) return 4; /* 4 Clock Latency */ + if (clk_freq <= 133000000) return 5; /* 5 Clock Latency */ + if (clk_freq <= 166000000) return 6; /* 6 Clock Latency */ + if (clk_freq <= 250000000) return 7; /* 7 Clock Latency */ + return 7; /* Default to highest latency for safety */ +} + +static uint16_t hyperram_get_chip_latency_setting(uint32_t clk_freq) { + /* LUT/Translated settings for the HyperRAM chip */ + if (clk_freq <= 85000000) return 0b1110; /* 3 Clock Latency */ + if (clk_freq <= 104000000) return 0b1111; /* 4 Clock Latency */ + if (clk_freq <= 133000000) return 0b0000; /* 5 Clock Latency */ + if (clk_freq <= 166000000) return 0b0001; /* 6 Clock Latency */ + if (clk_freq <= 250000000) return 0b0010; /* 7 Clock Latency */ + return 0b0010; /* Default to highest latency for safety */ +} + +static void hyperram_configure_latency(void) { + uint16_t config_reg_0 = 0x8f2f; + uint8_t core_clk_ratio; + uint16_t core_latency_setting; + uint16_t chip_latency_setting; + + /* Compute Latency settings */ + core_clk_ratio = (hyperram_status_read() >> CSR_HYPERRAM_STATUS_CLK_RATIO_OFFSET & 0xf); + printf("HyperRAM Clk Ratio %d:1.\n", core_clk_ratio); + core_latency_setting = hyperram_get_core_latency_setting(CONFIG_CLOCK_FREQUENCY/core_clk_ratio); + chip_latency_setting = hyperram_get_chip_latency_setting(CONFIG_CLOCK_FREQUENCY/core_clk_ratio); + + /* Write Latency to HyperRAM Core */ + printf("HyperRAM Core Latency: %d CK (X1).\n", core_latency_setting); + hyperram_config_write(core_latency_setting << CSR_HYPERRAM_CONFIG_LATENCY_OFFSET); + + /* Enable Variable Latency on HyperRAM Chip */ + if (hyperram_status_read() & 0x1) + config_reg_0 &= ~(0b1 << 3); /* Enable Variable Latency */ + + /* Update Latency on HyperRAM Chip */ + config_reg_0 &= ~(0b1111 << 4); + config_reg_0 |= chip_latency_setting << 4; + + /* Write Configuration Register 0 to HyperRAM Chip */ + hyperram_write_reg(2, config_reg_0); + + /* Read current configuration */ + config_reg_0 = hyperram_read_reg(2); + printf("HyperRAM Configuration Register 0: %08x\n", config_reg_0); +} + +void hyperram_init(void) { + printf("HyperRAM init...\n"); + hyperram_configure_latency(); + printf("\n"); +} + +#endif \ No newline at end of file diff --git a/litex/soc/software/libbase/hyperram.h b/litex/soc/software/libbase/hyperram.h new file mode 100644 index 000000000..1c8a7d1e7 --- /dev/null +++ b/litex/soc/software/libbase/hyperram.h @@ -0,0 +1,17 @@ +// This file is Copyright (c) 2024 Florent Kermarrec +// License: BSD + +#ifndef __HYPERRAM_H +#define __HYPERRAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +void hyperram_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __HYPERRAM_H */ diff --git a/litex/soc/software/libbase/isr.c b/litex/soc/software/libbase/isr.c index 268605d72..983c67fc7 100644 --- a/litex/soc/software/libbase/isr.c +++ b/litex/soc/software/libbase/isr.c @@ -3,7 +3,6 @@ // This file is Copyright (c) 2020 Raptor Engineering, LLC // License: BSD - #include #include #include @@ -19,61 +18,84 @@ void isr(void); #ifdef CONFIG_CPU_HAS_INTERRUPT -#if defined(__blackparrot__) /*TODO: Update this function for BP*/ // -void isr(void) +/*******************************************************/ +/* Common Interrupt Table for All CPUs. */ +/*******************************************************/ +struct irq_table { - static int onetime = 0; - if ( onetime == 0){ - printf("ISR blackparrot\n"); - printf("TRAP!!\n"); - onetime++; - } + isr_t isr; +} irq_table[CONFIG_CPU_INTERRUPTS]; + +int irq_attach(unsigned int irq, isr_t isr) +{ + if (irq >= CONFIG_CPU_INTERRUPTS) { + printf("Inv irq %d\n", irq); + return -1; + } + + unsigned int ie = irq_getie(); + irq_setie(0); + irq_table[irq].isr = isr; + irq_setie(ie); + return irq; } -#elif defined(__rocket__) || defined(__openc906__) -#if defined(__openc906__) -#define PLIC_EXT_IRQ_BASE 16 -#else -#define PLIC_EXT_IRQ_BASE 1 -#endif -void plic_init(void); + +int irq_detach(unsigned int irq) +{ + return irq_attach(irq, NULL); +} + +/***********************************************************/ +/* ISR and PLIC Initialization for RISC-V PLIC-based CPUs. */ +/***********************************************************/ +#if defined(__riscv_plic__) + +/* PLIC initialization. */ void plic_init(void) { - int i; + int i; + /* Set priorities for the first 8 external interrupts to 1. */ + for (i = 0; i < 8; i++) + *((unsigned int *)PLIC_BASE + PLIC_EXT_IRQ_BASE + i) = 1; - // priorities for first 8 external interrupts - for (i = 0; i < 8; i++) - *((unsigned int *)PLIC_BASE + PLIC_EXT_IRQ_BASE + i) = 1; - // enable first 8 external interrupts - *((unsigned int *)PLIC_ENABLED) = 0xff << PLIC_EXT_IRQ_BASE; - // set priority threshold to 0 (any priority > 0 triggers interrupt) - *((unsigned int *)PLIC_THRSHLD) = 0; + /* Enable the first 8 external interrupts. */ + *((unsigned int *)PLIC_ENABLED) = 0xff << PLIC_EXT_IRQ_BASE; + + /* Set priority threshold to 0 (any priority > 0 triggers an interrupt). */ + *((unsigned int *)PLIC_THRSHLD) = 0; } +/* Interrupt Service Routine. */ void isr(void) { - unsigned int claim; + unsigned int claim; - while ((claim = *((unsigned int *)PLIC_CLAIM))) { - switch (claim - PLIC_EXT_IRQ_BASE) { - case UART_INTERRUPT: - uart_isr(); - break; - default: - printf("## PLIC: Unhandled claim: %d\n", claim); - printf("# plic_enabled: %08x\n", irq_getmask()); - printf("# plic_pending: %08x\n", irq_pending()); - printf("# mepc: %016lx\n", csrr(mepc)); - printf("# mcause: %016lx\n", csrr(mcause)); - printf("# mtval: %016lx\n", csrr(mtval)); - printf("# mie: %016lx\n", csrr(mie)); - printf("# mip: %016lx\n", csrr(mip)); - printf("###########################\n\n"); - break; - } - *((unsigned int *)PLIC_CLAIM) = claim; - } + /* Claim and handle pending interrupts. */ + while ((claim = *((unsigned int *)PLIC_CLAIM))) { + unsigned int irq = claim - PLIC_EXT_IRQ_BASE; + if (irq < CONFIG_CPU_INTERRUPTS && irq_table[irq].isr) { + irq_table[irq].isr(); + } else { + /* Unhandled interrupt source, print diagnostic information. */ + printf("## PLIC: Unhandled claim: %d\n", claim); + printf("# plic_enabled: %08x\n", irq_getmask()); + printf("# plic_pending: %08x\n", irq_pending()); + printf("# mepc: %016lx\n", csrr(mepc)); + printf("# mcause: %016lx\n", csrr(mcause)); + printf("# mtval: %016lx\n", csrr(mtval)); + printf("# mie: %016lx\n", csrr(mie)); + printf("# mip: %016lx\n", csrr(mip)); + printf("###########################\n\n"); + } + /* Acknowledge the interrupt. */ + *((unsigned int *)PLIC_CLAIM) = claim; + } } -#elif defined(__cv32e40p__) + +/************************************************/ +/* ISR Handling for CV32E40P and CV32E41P CPUs. */ +/************************************************/ +#elif defined(__cv32e40p__) || defined(__cv32e41p__) #define FIRQ_OFFSET 16 #define IRQ_MASK 0x7FFFFFFF @@ -81,164 +103,131 @@ void isr(void) #define ECALL 11 #define RISCV_TEST +/* Interrupt Service Routine. */ void isr(void) { unsigned int cause = csrr(mcause) & IRQ_MASK; if (csrr(mcause) & 0x80000000) { -#ifndef UART_POLLING - if (cause == (UART_INTERRUPT+FIRQ_OFFSET)){ - uart_isr(); + /* Handle fast interrupts (FIRQ). */ + unsigned int irq = cause - FIRQ_OFFSET; + if (irq < CONFIG_CPU_INTERRUPTS && irq_table[irq].isr) { + irq_table[irq].isr(); } -#endif } else { + /* Handle regular exceptions and system calls. */ #ifdef RISCV_TEST int gp; - asm volatile ("mv %0, gp" : "=r"(gp)); + asm volatile("mv %0, gp" : "=r"(gp)); printf("E %d\n", cause); if (cause == INVINST) { printf("Inv Instr\n"); - for(;;); + for (;;); } if (cause == ECALL) { printf("Ecall (gp: %d)\n", gp); - csrw(mepc, csrr(mepc)+4); + csrw(mepc, csrr(mepc) + 4); } #endif } } +/*************************************/ +/* ISR Handling for BlackParrot CPU. */ +/*************************************/ + +#elif defined(__blackparrot__) /*TODO: Update this function for BP.*/ +/* Interrupt Service Routine. */ +void isr(void) +{ + static int onetime = 0; + if (onetime == 0) { + printf("ISR blackparrot\n"); + printf("TRAP!!\n"); + onetime++; + } +} + +/***********************************/ +/* ISR Handling for Microwatt CPU. */ +/***********************************/ #elif defined(__microwatt__) void isr(uint64_t vec) { - if (vec == 0x900) - return isr_dec(); + if (vec == 0x900) + return isr_dec(); - if (vec == 0x500) { - // Read interrupt source - uint32_t xirr = xics_icp_readw(PPC_XICS_XIRR); - uint32_t irq_source = xirr & 0x00ffffff; + if (vec == 0x500) { + /* Read interrupt source. */ + uint32_t xirr = xics_icp_readw(PPC_XICS_XIRR); + uint32_t irq_source = xirr & 0x00ffffff; - __attribute__((unused)) unsigned int irqs; + __attribute__((unused)) unsigned int irqs; - // Handle IPI interrupts separately - if (irq_source == 2) { - // IPI interrupt - xics_icp_writeb(PPC_XICS_MFRR, 0xff); - } - else { - // External interrupt - irqs = irq_pending() & irq_getmask(); + /* Handle IPI interrupts separately. */ + if (irq_source == 2) { + /* IPI interrupt. */ + xics_icp_writeb(PPC_XICS_MFRR, 0xff); + } else { + /* External interrupt. */ + irqs = irq_pending() & irq_getmask(); -#ifndef UART_POLLING - if(irqs & (1 << UART_INTERRUPT)) - uart_isr(); -#endif - } + if (irqs) { + const unsigned int irq = __builtin_ctz(irqs); + if (irq < CONFIG_CPU_INTERRUPTS && irq_table[irq].isr) { + irq_table[irq].isr(); + } else { + irq_setmask(irq_getmask() & ~(1 << irq)); + printf("\n*** disabled spurious irq %d ***\n", irq); + } + irqs &= irqs - 1; /* Clear this IRQ (the first bit set). */ + } + } - // Clear interrupt - xics_icp_writew(PPC_XICS_XIRR, xirr); + /* Clear interrupt. */ + xics_icp_writew(PPC_XICS_XIRR, xirr); - return; - } + return; + } } void isr_dec(void) { - // For now, just set DEC back to a large enough value to slow the flood of DEC-initiated timer interrupts - mtdec(0x000000000ffffff); -} - -#elif defined(__cva6__) -void plic_init(void); -void plic_init(void) -{ - int i; - - // priorities for interrupt pins 0...7 - for (i = 0; i < 8; i++) - *((unsigned int *)PLIC_SOURCE_0 + i) = 1; - // enable interrupt pins 0...7 (M-mode) - *((unsigned int *)PLIC_M_ENABLE) = 0xff; - // set priority threshold to 0 (any priority > 0 triggers interrupt) - *((unsigned int *)PLIC_M_THRESHOLD) = 0; -} - -void isr(void) -{ - unsigned int claim; - - while ((claim = *((unsigned int *)PLIC_M_CLAIM))) { - switch (claim - 1) { - case UART_INTERRUPT: - uart_isr(); - break; - default: - printf("## PLIC: Unhandled claim: %d\n", claim); - printf("# plic_enabled: %08x\n", irq_getmask()); - printf("# plic_pending: %08x\n", irq_pending()); - printf("# mepc: %016lx\n", csrr(mepc)); - printf("# mcause: %016lx\n", csrr(mcause)); - printf("# mtval: %016lx\n", csrr(mtval)); - printf("# mie: %016lx\n", csrr(mie)); - printf("# mip: %016lx\n", csrr(mip)); - printf("###########################\n\n"); - break; - } - *((unsigned int *)PLIC_M_CLAIM) = claim; - } + /* Set DEC back to a large enough value to slow the flood of DEC-initiated timer interrupts. */ + mtdec(0x000000000ffffff); } +/*******************************************************/ +/* Generic ISR Handling for CPUs with Interrupt Table. */ +/*******************************************************/ #else -struct irq_table -{ - isr_t isr; -} irq_table[CONFIG_CPU_INTERRUPTS]; - -int irq_attach(unsigned int irq, isr_t isr) -{ - if (irq >= CONFIG_CPU_INTERRUPTS) { - printf("Inv irq %d\n", irq); - return -1; - } - - unsigned int ie = irq_getie(); - irq_setie(0); - irq_table[irq].isr = isr; - irq_setie(ie); - return irq; -} - -int irq_detach(unsigned int irq) -{ - return irq_attach(irq, NULL); -} +/* Interrupt Service Routine. */ void isr(void) { - unsigned int irqs = irq_pending() & irq_getmask(); + unsigned int irqs = irq_pending() & irq_getmask(); - while (irqs) - { - const unsigned int irq = __builtin_ctz(irqs); - if ((irq < CONFIG_CPU_INTERRUPTS) && irq_table[irq].isr) - irq_table[irq].isr(); - else { - irq_setmask(irq_getmask() & ~(1<> CSR_SPIFLASH_CORE_MASTER_STATUS_TX_READY_OFFSET) & 1; +} + +static bool spiflash_rx_ready(void) +{ + return (spiflash_core_master_status_read() >> CSR_SPIFLASH_CORE_MASTER_STATUS_RX_READY_OFFSET) & 1; +} + static void spiflash_master_write(uint32_t val, size_t len, size_t width, uint32_t mask) { /* Be sure to empty RX queue before doing Xfer. */ - while (spiflash_core_master_status_rx_ready_read()) + while (spiflash_rx_ready()) spiflash_core_master_rxtx_read(); /* Configure Master */ - spiflash_core_master_phyconfig_len_write(8 * len); - spiflash_core_master_phyconfig_mask_write(mask); - spiflash_core_master_phyconfig_width_write(width); + spiflash_len_mask_width_write(8*len, width, mask); /* Set CS. */ spiflash_core_master_cs_write(1); /* Do Xfer. */ spiflash_core_master_rxtx_write(val); - while (!spiflash_core_master_status_rx_ready_read()); + while (!spiflash_rx_ready()); /* Clear CS. */ spiflash_core_master_cs_write(0); @@ -102,21 +121,19 @@ static volatile uint8_t r_buf[SPI_FLASH_BLOCK_SIZE + 4]; static uint32_t transfer_byte(uint8_t b) { /* wait for tx ready */ - while (!spiflash_core_master_status_tx_ready_read()); + while (!spiflash_tx_ready()); spiflash_core_master_rxtx_write((uint32_t)b); /* wait for rx ready */ - while (!spiflash_core_master_status_rx_ready_read()); + while (!spiflash_rx_ready()); return spiflash_core_master_rxtx_read(); } static void transfer_cmd(volatile uint8_t *bs, volatile uint8_t *resp, int len) { - spiflash_core_master_phyconfig_len_write(8); - spiflash_core_master_phyconfig_width_write(1); - spiflash_core_master_phyconfig_mask_write(1); + spiflash_len_mask_width_write(8, 1, 1); spiflash_core_master_cs_write(1); flush_cpu_dcache(); diff --git a/litex/tools/litex_json2dts_linux.py b/litex/tools/litex_json2dts_linux.py index 5ccf5d47d..8d5e7f8c1 100755 --- a/litex/tools/litex_json2dts_linux.py +++ b/litex/tools/litex_json2dts_linux.py @@ -7,55 +7,50 @@ # Copyright (c) 2020 Antmicro # SPDX-License-Identifier: BSD-2-Clause +import os import sys import json import argparse -import os + +from litex.gen.common import KILOBYTE, MEGABYTE def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_device=None, polling=False): - kB = 1024 - mB = kB*1024 - aliases = {} - # CPU Architectures ---------------------------------------------------------------------------- - # CHECKME: Move to core and generate a constant for each CPU? - cpu_architectures = { - "mor1kx" : "or1k", - "marocchino" : "or1k", - "vexriscv" : "riscv", - "rocket" : "riscv", - "naxriscv" : "riscv", - } - # CPU Parameters ------------------------------------------------------------------------------- - ncpus = int(d["constants"].get("config_cpu_count", 1)) - cpu_name = d["constants"].get("config_cpu_name") - cpu_arch = cpu_architectures[cpu_name] - cpu_isa = d["constants"].get("config_cpu_isa", None) - cpu_mmu = d["constants"].get("config_cpu_mmu", None) + cpu_count = int(d["constants"].get("config_cpu_count", 1)) + cpu_name = d["constants"].get("config_cpu_name") + cpu_family = d["constants"].get("config_cpu_family") + cpu_isa = d["constants"].get("config_cpu_isa", None) + cpu_mmu = d["constants"].get("config_cpu_mmu", None) # Header --------------------------------------------------------------------------------------- + platform = d["constants"]["config_platform_name"] dts = """ /dts-v1/; -/ { +/ {{ + compatible = "litex,{platform}", "litex,soc"; + model = "{identifier}"; #address-cells = <1>; #size-cells = <1>; -""" +""".format( + platform=platform, + identifier=d["constants"].get("identifier", platform), + ) # Boot Arguments ------------------------------------------------------------------------------- + # Init Ram Disk. default_initrd_start = { - "or1k": 8*mB, - "riscv": 16*mB, + "or1k": 8 * MEGABYTE, + "riscv": 16 * MEGABYTE, } - default_initrd_size = 8*mB - + default_initrd_size = 8 * MEGABYTE if initrd_start is None: - initrd_start = default_initrd_start[cpu_arch] + initrd_start = default_initrd_start[cpu_family] if initrd_size is None: initrd_size = default_initrd_size @@ -68,23 +63,28 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic initrd_enabled = True initrd_size = os.path.getsize(initrd) - # if json constants contains localip ethernet has been enabled - if "localip1" in d["constants"]: - localip = '.'.join([str(d["constants"][f"localip{i}"]) for i in range(1,5)]) - remoteip = '.'.join([str(d["constants"][f"remoteip{i}"]) for i in range(1,5)]) - ip = f" ip={localip}:{remoteip}:{remoteip}:255.255.255.0::eth0:off:::" - else: - ip = "" - + # Root Filesystem. if root_device is None: root_device = "ram0" + # Ethernet IP Address. + def get_eth_ip_config(): + def get_ip_address(prefix): + return '.'.join(str(d["constants"][f"{prefix}{i+1}"]) for i in range(4)) + ip_config = "" + if all(f"localip{i + 1}" in d["constants"] for i in range(4)): + local_ip = get_ip_address("localip") + remote_ip = get_ip_address("remoteip") + ip_config = f" ip={local_ip}:{remote_ip}:{remote_ip}:255.255.255.0::eth0:off:::" + return ip_config + + # Bootargs Generation. dts += """ chosen {{ bootargs = "{console} {rootfs}{ip}";""".format( console = "console=liteuart earlycon=liteuart,0x{:x}".format(d["csr_bases"]["uart"]), rootfs = "rootwait root=/dev/{}".format(root_device), - ip = ip) + ip = get_eth_ip_config()) if initrd_enabled is True: dts += """ @@ -97,21 +97,46 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic }; """ - # Clocks ------------------------------------------------------------------------------------------ + # Clocks --------------------------------------------------------------------------------------- - dts += """ - sys_clk: pll {{ + for c in [c for c in d["constants"].keys() if c.endswith("config_clock_frequency")]: + name = c.removesuffix("config_clock_frequency") + "sys_clk" + dts += """ + {name}: clock-{freq} {{ compatible = "fixed-clock"; #clock-cells = <0>; - clock-frequency = <{sys_clk_freq}>; + clock-frequency = <{freq}>; }}; -""".format(sys_clk_freq=d["constants"]["config_clock_frequency"]) +""".format( + name=name, + freq=d["constants"][c], + ) # CPU ------------------------------------------------------------------------------------------ # RISC-V # ------ - if cpu_arch == "riscv": + if cpu_family == "riscv": + + def get_riscv_cpu_isa_base(cpu_isa): + return cpu_isa[:5] + + def get_riscv_cpu_isa_extensions(cpu_isa, cpu_name): + isa_extensions = set(["i"]) + + # Collect common extensions. + common_extensions = {'i', 'm', 'a', 'f', 'd', 'c'} + for extension in cpu_isa[5:]: + if extension in common_extensions: + isa_extensions.update({extension}) + + # Add rocket-specific extensions. + if cpu_name == "rocket": + isa_extensions.update({"zicsr", "zifencei", "zihpm"}) + + # Format extensions. + return ", ".join(f"\"{extension}\"" for extension in sorted(isa_extensions)) + # Cache description. cache_desc = "" if "config_cpu_dcache_size" in d["constants"]: @@ -137,6 +162,7 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic tlb_desc = "" if "config_cpu_dtlb_size" in d["constants"]: tlb_desc += """ + tlb-split; d-tlb-size = <{d_tlb_size}>; d-tlb-sets = <{d_tlb_ways}>; """.format( @@ -151,25 +177,23 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic i_tlb_ways = d["constants"]["config_cpu_itlb_ways"]) # Rocket specific attributes - if ("rocket" in cpu_name): - cpu_isa = cpu_isa.replace("2p0_", "") + if (cpu_name == "rocket"): extra_attr = """ hardware-exec-breakpoint-count = <1>; next-level-cache = <&memory>; riscv,pmpgranularity = <4>; riscv,pmpregions = <8>; - tlb-split; """ else: extra_attr = "" # CPU(s) Topology. cpu_map = "" - if ncpus > 1: + if cpu_count > 1: cpu_map += """ cpu-map { cluster0 {""" - for cpu in range(ncpus): + for cpu in range(cpu_count): cpu_map += """ core{cpu} {{ cpu = <&CPU{cpu}>; @@ -184,12 +208,14 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic #size-cells = <0>; timebase-frequency = <{sys_clk_freq}>; """.format(sys_clk_freq=d["constants"]["config_clock_frequency"]) - for cpu in range(ncpus): + for cpu in range(cpu_count): dts += """ CPU{cpu}: cpu@{cpu} {{ device_type = "cpu"; compatible = "riscv"; riscv,isa = "{cpu_isa}"; + riscv,isa-base = "{cpu_isa_base}"; + riscv,isa-extensions = {cpu_isa_extensions}; mmu-type = "riscv,{cpu_mmu}"; reg = <{cpu}>; clock-frequency = <{sys_clk_freq}>; @@ -205,12 +231,14 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic }}; }}; """.format(cpu=cpu, irq=cpu, - sys_clk_freq = d["constants"]["config_clock_frequency"], - cpu_isa = cpu_isa, - cpu_mmu = cpu_mmu, - cache_desc = cache_desc, - tlb_desc = tlb_desc, - extra_attr = extra_attr) + sys_clk_freq = d["constants"]["config_clock_frequency"], + cpu_isa = cpu_isa, + cpu_isa_base = get_riscv_cpu_isa_base(cpu_isa), # Required for kernel >= 6.6.0 + cpu_isa_extensions = get_riscv_cpu_isa_extensions(cpu_isa, cpu_name), # Required for kernel >= 6.6.0 + cpu_mmu = cpu_mmu, + cache_desc = cache_desc, + tlb_desc = tlb_desc, + extra_attr = extra_attr) dts += """ {cpu_map} }}; @@ -218,7 +246,7 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic # Or1k # ---- - elif cpu_arch == "or1k": + elif cpu_family == "or1k": dts += """ cpus {{ #address-cells = <1>; @@ -305,7 +333,7 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic # Interrupt Controller ------------------------------------------------------------------------- - if (cpu_arch == "riscv") and ("rocket" in cpu_name): + if (cpu_family == "riscv") and (cpu_name in ["rocket", "vexiiriscv"]): # FIXME : L4 definitiion? # CHECKME: interrupts-extended. dts += """ @@ -317,10 +345,10 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic reg-names = "control"; }}; """.format( - clint_base=d["memories"]["clint"]["base"], - cpu_mapping =("\n" + " "*20).join(["&L{} 3 &L{} 7".format(cpu, cpu) for cpu in range(ncpus)])) - if cpu_arch == "riscv": - if "rocket" in cpu_name: + clint_base = d["memories"]["clint"]["base"], + cpu_mapping = ("\n" + " "*20).join(["&L{} 3 &L{} 7".format(cpu, cpu) for cpu in range(cpu_count)])) + if cpu_family == "riscv": + if cpu_name == "rocket": extra_attr = """ reg-names = "control"; riscv,max-priority = <7>; @@ -341,11 +369,11 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic {extra_attr} }}; """.format( - plic_base =d["memories"]["plic"]["base"], - cpu_mapping =("\n" + " "*20).join(["&L{} 11 &L{} 9".format(cpu, cpu) for cpu in range(ncpus)]), - extra_attr =extra_attr) + plic_base = d["memories"]["plic"]["base"], + cpu_mapping = ("\n" + " "*20).join(["&L{} 11 &L{} 9".format(cpu, cpu) for cpu in range(cpu_count)]), + extra_attr = extra_attr) - elif cpu_arch == "or1k": + elif cpu_family == "or1k": dts += """ intc0: interrupt-controller { interrupt-controller; @@ -354,7 +382,7 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic status = "okay"; }; """ - if (cpu_arch == "riscv") and ("rocket" in cpu_name): + if (cpu_family == "riscv") and (cpu_name == "rocket"): dts += """ dbg_ctl: debug-controller@0 {{ compatible = "sifive,debug-013", "riscv,debug-013"; @@ -376,11 +404,12 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic reg-names = "mem"; }}; """.format( - cpu_mapping =("\n" + " "*20).join(["&L{} 0x3F".format(cpu) for cpu in range(ncpus)])) + cpu_mapping =("\n" + " "*20).join(["&L{} 0x3F".format(cpu) for cpu in range(cpu_count)])) # UART ----------------------------------------------------------------------------------------- if "uart" in d["csr_bases"]: aliases["serial0"] = "liteuart0" + it_incr = {True: 1, False: 0}[cpu_name == "rocket"] dts += """ liteuart0: serial@{uart_csr_base:x} {{ compatible = "litex,liteuart"; @@ -390,13 +419,17 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic }}; """.format( uart_csr_base = d["csr_bases"]["uart"], - uart_interrupt = "" if polling else "interrupts = <{}>;".format(d["constants"]["uart_interrupt"])) + uart_interrupt = "" if polling else "interrupts = <{}>;".format(int(d["constants"]["uart_interrupt"]) + it_incr)) # Ethernet ------------------------------------------------------------------------------------- - - if "ethphy" in d["csr_bases"] and "ethmac" in d["csr_bases"]: - dts += """ - mac0: mac@{ethmac_csr_base:x} {{ + for i in [''] + list(range(0, 10)): + idx = (0 if i == '' else i) + ethphy_name = "ethphy" + str(i) + ethmac_name = "ethmac" + str(i) + it_incr = {True: 1, False: 0}[cpu_name == "rocket"] + if ethphy_name in d["csr_bases"] and ethmac_name in d["csr_bases"]: + dts += """ + mac{idx}: mac@{ethmac_csr_base:x} {{ compatible = "litex,liteeth"; reg = <0x{ethmac_csr_base:x} 0x7c>, <0x{ethphy_csr_base:x} 0x0a>, @@ -409,14 +442,15 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic status = "okay"; }}; """.format( - ethphy_csr_base = d["csr_bases"]["ethphy"], - ethmac_csr_base = d["csr_bases"]["ethmac"], - ethmac_mem_base = d["memories"]["ethmac"]["base"], - ethmac_mem_size = d["memories"]["ethmac"]["size"], - ethmac_rx_slots = d["constants"]["ethmac_rx_slots"], - ethmac_tx_slots = d["constants"]["ethmac_tx_slots"], - ethmac_slot_size = d["constants"]["ethmac_slot_size"], - ethmac_interrupt = "" if polling else "interrupts = <{}>;".format(d["constants"]["ethmac_interrupt"])) + idx = idx, + ethphy_csr_base = d["csr_bases"][ethphy_name], + ethmac_csr_base = d["csr_bases"][ethmac_name], + ethmac_mem_base = d["memories"][ethmac_name]["base"], + ethmac_mem_size = d["memories"][ethmac_name]["size"], + ethmac_rx_slots = d["constants"][ethmac_name + "_rx_slots"], + ethmac_tx_slots = d["constants"][ethmac_name + "_tx_slots"], + ethmac_slot_size = d["constants"][ethmac_name + "_slot_size"], + ethmac_interrupt = "" if polling else "interrupts = <{}>;".format(int(d["constants"][ethmac_name + "_interrupt"]) + it_incr)) # USB OHCI ------------------------------------------------------------------------------------- @@ -599,6 +633,25 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic }}; """.format(xadc_csr_base=d["csr_bases"]["xadc"]) + # CAN ------------------------------------------------------------------------------------------ + + for mem in d["memories"]: + if "can" in mem: + dts += """ + {name}: can@{can_mem_base:x} {{ + compatible = "ctu,ctucanfd"; + reg = <0x{can_mem_base:x} 0x{can_mem_size:x}>; + interrupt-parent = <&intc0>; + interrupts = <{can_interrupt}>; + clocks = <&sys_clk>; + status = "okay"; + }}; +""".format(name=mem, + can_mem_base=d["memories"][mem]["base"], + can_mem_size=d["memories"][mem]["size"], + can_interrupt = int(d["constants"][f"{mem}_interrupt"]), + ) + # Framebuffer ---------------------------------------------------------------------------------- if "video_framebuffer" in d["csr_bases"]: @@ -694,14 +747,14 @@ def generate_dts(d, initrd_start=None, initrd_size=None, initrd=None, root_devic litex,clkout-divide-max = <{clkout_divide_range[1]}>; litex,vco-margin = <{vco_margin}>; """.format( - mmcm_lock_timeout = d["constants"]["mmcm_lock_timeout"], - mmcm_drdy_timeout = d["constants"]["mmcm_drdy_timeout"], - sys_clk = d["constants"]["config_clock_frequency"], + mmcm_lock_timeout = d["constants"]["mmcm_lock_timeout"], + mmcm_drdy_timeout = d["constants"]["mmcm_drdy_timeout"], + sys_clk = d["constants"]["config_clock_frequency"], divclk_divide_range = (d["constants"]["divclk_divide_range_min"], d["constants"]["divclk_divide_range_max"]), clkfbout_mult_frange = (d["constants"]["clkfbout_mult_frange_min"], d["constants"]["clkfbout_mult_frange_max"]), vco_freq_range = (d["constants"]["vco_freq_range_min"], d["constants"]["vco_freq_range_max"]), clkout_divide_range = (d["constants"]["clkout_divide_range_min"], d["constants"]["clkout_divide_range_max"]), - vco_margin = d["constants"]["vco_margin"]) + vco_margin = d["constants"]["vco_margin"]) for clkout_nr in range(nclkout): dts += add_clkout(clkout_nr, d["constants"]["clkout_def_freq"], diff --git a/litex/tools/litex_json2dts_zephyr.py b/litex/tools/litex_json2dts_zephyr.py index 485fe9aef..707e710c5 100755 --- a/litex/tools/litex_json2dts_zephyr.py +++ b/litex/tools/litex_json2dts_zephyr.py @@ -104,6 +104,10 @@ def dts_reg_names(regs): def disabled_handler(name, parm, csr): return indent('status = "disabled";\n') +def cpu_handler(name, parm, csr): + return indent("clock-frequency = <{}>;\n".format( + csr['constants']['config_clock_frequency'] + )) def ram_handler(name, parm, csr): mem_reg = { @@ -177,6 +181,51 @@ def i2s_handler(name, parm, csr): return dtsi +def spimaster_handler(name, parm, csr): + registers = get_registers_of(name, csr) + if len(registers) == 0: + raise KeyError + + dtsi = dts_reg(registers) + dtsi += dts_reg_names(registers) + + dtsi += indent("clock-frequency = <{}>;\n".format( + csr['constants'][name + '_frequency'])) + + dtsi += indent("data-width = <{}>;\n".format( + csr['constants'][name + '_data_width'])) + + dtsi += indent("max-cs = <{}>;\n".format( + csr['constants'][name + '_max_cs'])) + + return dtsi + + +def spiflash_handler(name, parm, csr): + registers = get_registers_of(name, csr) + if len(registers) == 0: + raise KeyError + + # Add memory mapped region for spiflash, the linker script in zephyr expects this region to be + # the entry with the name flash_mmap in the reg property of the spi controller. + try: + registers.append({ + 'addr': csr['memories'][name]['base'], + 'size': csr['memories'][name]['size'], + 'name': 'flash_mmap', + }) + except KeyError as e: + print('memory mapped', e, 'not found') + + dtsi = dts_reg(registers) + dtsi += dts_reg_names(registers) + + dtsi += indent("clock-frequency = <{}>;\n".format( + csr['constants'][name + '_phy_frequency'])) + + return dtsi + + def peripheral_handler(name, parm, csr): registers = get_registers_of(name, csr) if len(registers) == 0: @@ -193,71 +242,79 @@ def peripheral_handler(name, parm, csr): overlay_handlers = { + 'cpu': { + 'handler': cpu_handler, + 'alias': 'cpu0', + }, + 'ctrl': { + 'handler': peripheral_handler, + 'alias': 'ctrl0', + }, 'uart': { 'handler': peripheral_handler, 'alias': 'uart0', - 'config_entry': 'UART_LITEUART' }, 'timer0': { 'handler': peripheral_handler, - 'config_entry': 'LITEX_TIMER' }, 'ethmac': { 'handler': ethmac_handler, 'alias': 'eth0', - 'config_entry': 'ETH_LITEETH' + }, + 'spimaster': { + 'handler': spimaster_handler, + 'alias': 'spi0', }, 'spiflash': { - 'handler': peripheral_handler, - 'alias': 'spi0', - 'config_entry': 'SPI_LITESPI' + 'handler': spiflash_handler, + 'alias': 'spi1', }, 'sdcard_block2mem': { 'handler': peripheral_handler, 'alias': 'sdcard_block2mem', 'size': 0x18, - 'config_entry': 'SD_LITESD' + 'disable_handler': False, }, 'sdcard_core': { 'handler': peripheral_handler, 'alias': 'sdcard_core', 'size': 0x2C, - 'config_entry': 'SD_LITESD' + 'disable_handler': False, }, 'sdcard_irq': { 'handler': peripheral_handler, 'alias': 'sdcard_irq', 'size': 0x0C, - 'config_entry': 'SD_LITESD' + 'disable_handler': False, }, 'sdcard_mem2block': { 'handler': peripheral_handler, 'alias': 'sdcard_mem2block', 'size': 0x18, - 'config_entry': 'SD_LITESD' + 'disable_handler': False, }, 'sdcard_phy': { 'handler': peripheral_handler, 'alias': 'sdcard_phy', 'size': 0x10, - 'config_entry': 'SD_LITESD' + 'disable_handler': False, }, 'i2c0' : { 'handler': i2c_handler, - 'config_entry': 'I2C_LITEX' }, 'i2s_rx' : { 'handler': i2s_handler, - 'config_entry': 'I2S_LITEX' }, 'i2s_tx' : { 'handler': i2s_handler, - 'config_entry': 'I2S_LITEX' + }, + 'watchdog0': { + 'handler': peripheral_handler, + 'alias': 'wdt0', }, 'mmcm' : { 'alias': 'clock0', 'handler': peripheral_handler, - 'config_entry': 'CLOCK_CONTROL_LITEX' }, 'main_ram': { 'handler': ram_handler, @@ -281,10 +338,14 @@ def generate_dts_config(csr): try: dtsi += parm['handler'](name, parm, csr) except KeyError as e: - print(' dtsi key', e, 'not found, disable', name) enable = 'n' - dtsi += disabled_handler(name, parm, csr) - + if parm.get('disable_handler', True): + print(' dtsi key', e, 'not found, disable', name) + dtsi += disabled_handler(name, parm, csr) + else: + print(' dtsi key', e, 'not found, skip', name) + continue + dtsi += dts_close() dts += dtsi if 'config_entry' in parm: diff --git a/litex/tools/litex_json2renode.py b/litex/tools/litex_json2renode.py index 0f83b5e76..fd91e78a0 100755 --- a/litex/tools/litex_json2renode.py +++ b/litex/tools/litex_json2renode.py @@ -219,23 +219,29 @@ def get_cpu_count(csr): vexriscv_common_kind = { 'name': 'VexRiscv', 'variants': { - 'linux': { - 'properties': ['cpuType: "rv32ima"', 'privilegeArchitecture: PrivilegeArchitecture.Priv1_10'], + 'minimal': { + 'properties': ['cpuType: "rv32i_zicsr_zifencei"'], }, - 'i': { - 'properties': ['cpuType: "rv32i"'], + 'lite': { + 'properties': ['cpuType: "rv32im_zicsr_zifencei"'], }, - 'im': { - 'properties': ['cpuType: "rv32im"'], - }, - 'ima': { - 'properties': ['cpuType: "rv32ima"'], + 'standard': { + 'properties': ['cpuType: "rv32im_zicsr_zifencei"'], }, 'imac': { - 'properties': ['cpuType: "rv32imac"'], + 'properties': ['cpuType: "rv32imac_zicsr_zifencei"'], + }, + 'full': { + 'properties': ['cpuType: "rv32im_zicsr_zifencei"'], + }, + 'linux': { + 'properties': ['cpuType: "rv32ima_zicsr_zifencei"'], + }, + 'secure': { + 'properties': ['cpuType: "rv32ima_zicsr_zifencei"'], }, 'others': { - 'properties': ['cpuType: "rv32im"'], + 'properties': ['cpuType: "rv32im_zicsr_zifencei"'], } }, 'supports_time_provider': True, @@ -900,6 +906,12 @@ sysbus LoadBinary @{} {} for cpu_id in range(0, number_of_cores): result += f"cpu{cpu_id} PC {hex(rom_base)}\n" + if args.bios_elf: + # load LiteX BIOS to ROM base + result += """ +sysbus LoadELF @{} +""".format(args.bios_elf) + if args.tftp_ip: result += """ @@ -1057,6 +1069,8 @@ def parse_args(): bios_group = parser.add_mutually_exclusive_group() bios_group.add_argument('--bios-binary', action='store', help='Path to the BIOS binary') + bios_group.add_argument('--bios-elf', action='store', + help='Path to the BIOS ELF file') bios_group.add_argument('--opensbi-binary', action='store', help='Path to the OpenSBI binary') diff --git a/litex/tools/litex_sim.py b/litex/tools/litex_sim.py index 1e412b87c..3407fc85d 100755 --- a/litex/tools/litex_sim.py +++ b/litex/tools/litex_sim.py @@ -15,35 +15,35 @@ import argparse from migen import * from litex.build.generic_platform import * -from litex.build.sim import SimPlatform -from litex.build.sim.config import SimConfig +from litex.build.sim import SimPlatform +from litex.build.sim.config import SimConfig -from litex.soc.integration.common import * +from litex.soc.integration.common import * from litex.soc.integration.soc_core import * -from litex.soc.integration.builder import * -from litex.soc.integration.soc import * -from litex.soc.cores.bitbang import * -from litex.soc.cores.gpio import GPIOTristate -from litex.soc.cores.cpu import CPUS +from litex.soc.integration.builder import * +from litex.soc.integration.soc import * -from litedram import modules as litedram_modules -from litedram.modules import parse_spd_hexdump +from litex.soc.cores.bitbang import * +from litex.soc.cores.gpio import GPIOTristate +from litex.soc.cores.cpu import CPUS +from litex.soc.cores.video import VideoGenericPHY + +from litedram import modules as litedram_modules +from litedram.modules import parse_spd_hexdump from litedram.phy.model import sdram_module_nphases, get_sdram_phy_settings from litedram.phy.model import SDRAMPHYModel -from liteeth.phy.gmii import LiteEthPHYGMII -from liteeth.phy.xgmii import LiteEthPHYXGMII -from liteeth.phy.model import LiteEthPHYModel -from liteeth.mac import LiteEthMAC -from liteeth.core.arp import LiteEthARP -from liteeth.core.ip import LiteEthIP -from liteeth.core.udp import LiteEthUDP -from liteeth.core.icmp import LiteEthICMP -from liteeth.core import LiteEthUDPIPCore +from liteeth.common import * +from liteeth.phy.gmii import LiteEthPHYGMII +from liteeth.phy.xgmii import LiteEthPHYXGMII +from liteeth.phy.model import LiteEthPHYModel +from liteeth.mac import LiteEthMAC +from liteeth.core.arp import LiteEthARP +from liteeth.core.ip import LiteEthIP +from liteeth.core.udp import LiteEthUDP +from liteeth.core.icmp import LiteEthICMP +from liteeth.core import LiteEthUDPIPCore from liteeth.frontend.etherbone import LiteEthEtherbone -from liteeth.common import * - -from litex.soc.cores.video import VideoGenericPHY from litescope import LiteScopeAnalyzer @@ -135,6 +135,7 @@ _io = [ Subsignal("tms", Pins(1)), Subsignal("tdi", Pins(1)), Subsignal("tdo", Pins(1)), + Subsignal("ntrst", Pins(1)), ), # Video (VGA). @@ -178,6 +179,7 @@ class SimSoC(SoCCore): with_gpio = False, with_video_framebuffer = False, with_video_terminal = False, + with_video_colorbars = False, sim_debug = False, trace_reset_on = False, with_jtag = False, @@ -260,9 +262,29 @@ class SimSoC(SoCCore): interface = "wishbone", endianness = self.cpu.endianness ) - ethmac_region_size = (ethmac.rx_slots.constant + ethmac.tx_slots.constant)*ethmac.slot_size.constant - ethmac_region = SoCRegion(origin=self.mem_map.get("ethmac", None), size=ethmac_region_size, cached=False) - self.bus.add_slave(name="ethmac", slave=ethmac.bus, region=ethmac_region) + ethmac_rx_region_size = ethmac.rx_slots.constant*ethmac.slot_size.constant + ethmac_tx_region_size = ethmac.tx_slots.constant*ethmac.slot_size.constant + ethmac_region_size = ethmac_rx_region_size + ethmac_tx_region_size + self.bus.add_region("ethmac", SoCRegion( + origin = self.mem_map.get("ethmac", None), + size = ethmac_region_size, + linker = True, + cached = False, + )) + ethmac_rx_region = SoCRegion( + origin = self.bus.regions["ethmac"].origin + 0, + size = ethmac_rx_region_size, + linker = True, + cached = False, + ) + self.bus.add_slave(name="ethmac_rx", slave=ethmac.bus_rx, region=ethmac_rx_region) + ethmac_tx_region = SoCRegion( + origin = self.bus.regions["ethmac"].origin + ethmac_rx_region_size, + size = ethmac_tx_region_size, + linker = True, + cached = False, + ) + self.bus.add_slave(name="ethmac_tx", slave=ethmac.bus_tx, region=ethmac_tx_region) # Add IRQs (if enabled). if self.irq.enabled: @@ -276,10 +298,7 @@ class SimSoC(SoCCore): # JTAG ------------------------------------------------------------------------------------- if with_jtag: jtag_pads = platform.request("jtag") - self.comb += self.cpu.jtag_clk.eq(jtag_pads.tck) - self.comb += self.cpu.jtag_tms.eq(jtag_pads.tms) - self.comb += self.cpu.jtag_tdi.eq(jtag_pads.tdi) - self.comb += jtag_pads.tdo.eq(self.cpu.jtag_tdo) + self.cpu.add_jtag(jtag_pads) # SDCard ----------------------------------------------------------------------------------- if with_sdcard: @@ -313,6 +332,11 @@ class SimSoC(SoCCore): self.submodules.videophy = VideoGenericPHY(platform.request("vga")) self.add_video_terminal(phy=self.videophy, timings="640x480@60Hz") + # Video test pattern ----------------------------------------------------------------------- + if with_video_colorbars: + self.submodules.videophy = VideoGenericPHY(platform.request("vga")) + self.add_video_colorbars(phy=self.videophy, timings="640x480@60Hz") + # Simulation debugging ---------------------------------------------------------------------- if sim_debug: platform.add_debug(self, reset=1 if trace_reset_on else 0) @@ -428,6 +452,8 @@ def sim_args(parser): # Video. parser.add_argument("--with-video-framebuffer", action="store_true", help="Enable Video Framebuffer.") parser.add_argument("--with-video-terminal", action="store_true", help="Enable Video Terminal.") + parser.add_argument("--with-video-colorbars", action="store_true", help="Enable Video test pattern.") + parser.add_argument("--video-vsync", action="store_true", help="Only render on frame vsync.") # Debug/Waveform. parser.add_argument("--sim-debug", action="store_true", help="Add simulation debugging modules.") @@ -510,8 +536,8 @@ def main(): sim_config.add_module("jtagremote", "jtag", args={'port': 44853}) # Video. - if args.with_video_framebuffer or args.with_video_terminal: - sim_config.add_module("video", "vga") + if args.with_video_framebuffer or args.with_video_terminal or args.with_video_colorbars: + sim_config.add_module("video", "vga", args={"render_on_vsync": args.video_vsync}) # SoC ------------------------------------------------------------------------------------------ soc = SimSoC( @@ -528,6 +554,7 @@ def main(): with_gpio = args.with_gpio, with_video_framebuffer = args.with_video_framebuffer, with_video_terminal = args.with_video_terminal, + with_video_colorbars = args.with_video_colorbars, sim_debug = args.sim_debug, trace_reset_on = int(float(args.trace_start)) > 0 or int(float(args.trace_end)) > 0, spi_flash_init = None if args.spi_flash_init is None else get_mem_data(args.spi_flash_init, endianness="big"), @@ -551,7 +578,7 @@ def main(): builder.build( sim_config = sim_config, interactive = not args.non_interactive, - video = args.with_video_framebuffer or args.with_video_terminal, + video = args.with_video_framebuffer or args.with_video_terminal or args.with_video_colorbars, pre_run_callback = pre_run_callback, **parser.toolchain_argdict, ) diff --git a/litex/tools/litex_term.py b/litex/tools/litex_term.py index 66e3fa502..4dfac7d5c 100755 --- a/litex/tools/litex_term.py +++ b/litex/tools/litex_term.py @@ -81,7 +81,7 @@ else: def handle_escape(self, b): return None -# Crossover UART ------------------------------------------------------------------------------------- +# Crossover UART ---------------------------------------------------------------------------------- from litex import RemoteClient @@ -553,8 +553,7 @@ class LiteXTerm: def start_reader(self): self.reader_alive = True - self.reader_thread = threading.Thread(target=self.reader) - self.reader_thread.setDaemon(True) + self.reader_thread = threading.Thread(target=self.reader, daemon=True) self.reader_thread.start() def stop_reader(self): @@ -582,8 +581,7 @@ class LiteXTerm: def start_writer(self): self.writer_alive = True - self.writer_thread = threading.Thread(target=self.writer) - self.writer_thread.setDaemon(True) + self.writer_thread = threading.Thread(target=self.writer, daemon=True) self.writer_thread.start() def stop_writer(self): @@ -607,21 +605,21 @@ class LiteXTerm: def _get_args(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("port", help="Serial port (eg /dev/tty*, crossover, jtag).") - parser.add_argument("--speed", default=115200, help="Serial baudrate.") - parser.add_argument("--serial-boot", default=False, action='store_true', help="Automatically initiate serial boot.") - parser.add_argument("--kernel", default=None, help="Kernel image.") - parser.add_argument("--kernel-adr", default="0x40000000", help="Kernel address.") - parser.add_argument("--images", default=None, help="JSON description of the images to load to memory.") - parser.add_argument("--safe", action="store_true", help="Safe serial boot mode, disable upload speed optimizations.") + parser.add_argument("port", help="Serial port (eg /dev/tty*, crossover, jtag).") + parser.add_argument("--speed", default=115200, help="Serial baudrate.") + parser.add_argument("--serial-boot", default=False, action='store_true', help="Automatically initiate serial boot.") + parser.add_argument("--kernel", default=None, help="Kernel image.") + parser.add_argument("--kernel-adr", default="0x40000000", help="Kernel address.") + parser.add_argument("--images", default=None, help="JSON description of the images to load to memory.") + parser.add_argument("--safe", action="store_true", help="Safe serial boot mode, disable upload speed optimizations.") parser.add_argument("--csr-csv", default=None, help="SoC CSV file.") parser.add_argument("--base-address", default=None, help="CSR base address.") parser.add_argument("--crossover-name", default="uart_xover", help="Crossover UART name to use (present in design/csr.csv).") - parser.add_argument("--jtag-name", default="jtag_uart", help="JTAG UART type (jtag_uart).") - parser.add_argument("--jtag-config", default="openocd_xc7_ft2232.cfg", help="OpenOCD JTAG configuration file for jtag_uart.") - parser.add_argument("--jtag-chain", default=1, help="JTAG chain.") + parser.add_argument("--jtag-name", default="jtag_uart", help="JTAG UART type (jtag_uart).") + parser.add_argument("--jtag-config", default="openocd_xc7_ft2232.cfg", help="OpenOCD JTAG configuration file for jtag_uart.") + parser.add_argument("--jtag-chain", default=1, help="JTAG chain.") return parser.parse_args() def main(): diff --git a/litex_setup.py b/litex_setup.py index bb71abaae..0c0679176 100755 --- a/litex_setup.py +++ b/litex_setup.py @@ -126,6 +126,7 @@ git_repos = { "pythondata-cpu-picorv32": GitRepo(url="https://github.com/litex-hub/"), "pythondata-cpu-rocket": GitRepo(url="https://github.com/litex-hub/"), "pythondata-cpu-serv": GitRepo(url="https://github.com/litex-hub/"), + "pythondata-cpu-vexiiriscv": GitRepo(url="https://github.com/litex-hub/", branch="main"), "pythondata-cpu-vexriscv": GitRepo(url="https://github.com/litex-hub/"), "pythondata-cpu-vexriscv-smp": GitRepo(url="https://github.com/litex-hub/", clone="recursive"), } diff --git a/setup.py b/setup.py index a52575a62..d9ae3f81f 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ with open("README.md", "r", encoding="utf-8") as fp: setup( name = "litex", - version = "2023.12", + version = "2024.04", description = "Python SoC/Core builder for building FPGA based systems.", long_description = long_description, long_description_content_type = "text/markdown", diff --git a/test/test_cpu.py b/test/test_cpu.py index 22dac251a..7a151d9bf 100644 --- a/test/test_cpu.py +++ b/test/test_cpu.py @@ -37,7 +37,7 @@ class TestCPU(unittest.TestCase): def test_cpu(self): tested_cpus = [ - "cv32e40p", # (riscv / softcore) + #"cv32e40p", # (riscv / softcore) "femtorv", # (riscv / softcore) "firev", # (riscv / softcore) "marocchino", # (or1k / softcore) @@ -45,7 +45,7 @@ class TestCPU(unittest.TestCase): "serv", # (riscv / softcore) "vexriscv", # (riscv / softcore) "vexriscv_smp", # (riscv / softcore) - "microwatt", # (ppc64 / softcore) + #"microwatt", # (ppc64 / softcore) "neorv32", # (riscv / softcore) ] untested_cpus = [ diff --git a/test/test_csr.py b/test/test_csr.py index c86e2350b..0e044f91f 100644 --- a/test/test_csr.py +++ b/test/test_csr.py @@ -19,9 +19,9 @@ def csr32_write(dut, adr, dat): def csr32_read(dut, adr): dat = 0 - for i in range(4): + for i in range(5): dat |= ((yield from dut.csr.read(adr + 3 - i)) << 8*i) - return dat + return dat >> 8 class CSRModule(Module, csr.AutoCSR): diff --git a/test/test_hyperbus.py b/test/test_hyperbus.py index 8d3a59c9a..6f0d655ed 100644 --- a/test/test_hyperbus.py +++ b/test/test_hyperbus.py @@ -33,7 +33,65 @@ class TestHyperBus(unittest.TestCase): pads = Record([("clk_p", 1), ("clk_n", 1), ("cs_n", 1), ("dq", 8), ("rwds", 1)]) hyperram = HyperRAM(pads) - def test_hyperram_write(self): + def test_hyperram_write_latency_5_2x(self): + def fpga_gen(dut): + yield from dut.bus.write(0x1234, 0xdeadbeef, sel=0b1001) + yield + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--_______" + cs_n = "--________________________________________________________------" + dq_oe = "__------------____________________________________--------______" + dq_o = "002000048d0000000000000000000000000000000000000000deadbeef000000" + rwds_oe = "__________________________________________________--------______" + rwds_o = "____________________________________________________----________" + for i in range(len(clk)): + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o)) + yield + + dut = HyperRAM(HyperRamPads(), latency=5, latency_mode="fixed") + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_write_latency_5_2x_sys2x(self): + def fpga_gen(dut): + yield from dut.bus.write(0x1234, 0xdeadbeef, sel=0b1001) + yield + + def hyperram_gen(dut): + clk = "____--__--__--__--__--__--__--__--__--__--__--__--__--__--_______" + cs_n = "--________________________________________________________-------" + dq_oe = "___------------____________________________________--------______" + dq_o = "0002000048d0000000000000000000000000000000000000000deadbeef000000" + rwds_oe = "___________________________________________________--------______" + rwds_o = "_____________________________________________________----________" + for i in range(len(clk)): + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + #if (yield dut.pads.dq.oe): + # self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o)) + yield + + dut = HyperRAM(HyperRamPads(), latency=5, latency_mode="fixed", clk_ratio="2:1") + generators = { + "sys" : fpga_gen(dut), + "sys2x" : hyperram_gen(dut), + } + clocks = { + "sys" : 4, + "sys2x" : 2, + } + run_simulation(dut, generators, clocks, vcd_name="sim.vcd") + + def test_hyperram_write_latency_6_2x(self): def fpga_gen(dut): yield from dut.bus.write(0x1234, 0xdeadbeef, sel=0b1001) yield @@ -45,21 +103,97 @@ class TestHyperBus(unittest.TestCase): dq_o = "002000048d000000000000000000000000000000000000000000000000deadbeef000000" rwds_oe = "__________________________________________________________--------______" rwds_o = "____________________________________________________________----________" - for i in range(3): - yield for i in range(len(clk)): self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) - self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o)) yield - dut = HyperRAM(HyperRamPads()) + dut = HyperRAM(HyperRamPads(), latency=6, latency_mode="fixed") run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") - def test_hyperram_read(self): + def test_hyperram_write_latency_7_2x(self): + def fpga_gen(dut): + yield from dut.bus.write(0x1234, 0xdeadbeef, sel=0b1001) + yield + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_______" + cs_n = "--________________________________________________________________________------" + dq_oe = "__------------____________________________________________________--------______" + dq_o = "002000048d00000000000000000000000000000000000000000000000000000000deadbeef000000" + rwds_oe = "__________________________________________________________________--------______" + rwds_o = "____________________________________________________________________----________" + for i in range(len(clk)): + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o)) + yield + + dut = HyperRAM(HyperRamPads(), latency=7, latency_mode="fixed") + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_write_latency_7_1x(self): + def fpga_gen(dut): + yield from dut.bus.write(0x1234, 0xdeadbeef, sel=0b1001) + yield + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--_______" + cs_n = "--____________________________________________------" + dq_oe = "__------------________________________--------______" + dq_o = "002000048d0000000000000000000000000000deadbeef000000" + rwds_oe = "______________________________________--------______" + rwds_o = "________________________________________----________" + for i in range(len(clk)): + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o)) + yield + + dut = HyperRAM(HyperRamPads(), latency=7, latency_mode="variable") + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_read_latency_5_2x(self): + def fpga_gen(dut): + dat = yield from dut.bus.read(0x1234) + self.assertEqual(dat, 0xdeadbeef) + dat = yield from dut.bus.read(0x1235) + self.assertEqual(dat, 0xcafefade) + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_" + cs_n = "--________________________________________________________________________" + dq_oe = "__------------____________________________________________________________" + dq_o = "00a000048d0000000000000000000000000000000000000000000000000000000000000000" + dq_i = "00000000000000000000000000000000000000000000000000deadbeefcafefade00000000" + rwds_oe = "__________________________________________________________________________" + for i in range(len(clk)): + yield dut.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16)) + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + yield + + dut = HyperRAM(HyperRamPads(), latency=5, latency_mode="fixed") + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_read_latency_6_2x(self): def fpga_gen(dut): dat = yield from dut.bus.read(0x1234) self.assertEqual(dat, 0xdeadbeef) @@ -73,16 +207,100 @@ class TestHyperBus(unittest.TestCase): dq_o = "00a000048d000000000000000000000000000000000000000000000000000000000000000000000000" dq_i = "0000000000000000000000000000000000000000000000000000000000deadbeefcafefade00000000" rwds_oe = "__________________________________________________________________________________" - for i in range(3): - yield for i in range(len(clk)): yield dut.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16)) self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) - self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) yield - dut = HyperRAM(HyperRamPads()) + dut = HyperRAM(HyperRamPads(), latency=6, latency_mode="fixed") run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_read_latency_7_2x(self): + def fpga_gen(dut): + dat = yield from dut.bus.read(0x1234) + self.assertEqual(dat, 0xdeadbeef) + dat = yield from dut.bus.read(0x1235) + self.assertEqual(dat, 0xcafefade) + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_" + cs_n = "--________________________________________________________________________________________" + dq_oe = "__------------____________________________________________________________________________" + dq_o = "00a000048d00000000000000000000000000000000000000000000000000000000000000000000000000000000" + dq_i = "000000000000000000000000000000000000000000000000000000000000000000deadbeefcafefade00000000" + rwds_oe = "__________________________________________________________________________________________" + for i in range(len(clk)): + yield dut.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16)) + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + yield + + dut = HyperRAM(HyperRamPads(), latency=7, latency_mode="fixed") + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_read_latency_7_1x(self): + def fpga_gen(dut): + dat = yield from dut.bus.read(0x1234) + self.assertEqual(dat, 0xdeadbeef) + dat = yield from dut.bus.read(0x1235) + self.assertEqual(dat, 0xcafefade) + + def hyperram_gen(dut): + clk = "___--__--__--__--__--__--__--__--__--__--__--__--__--__--__--_" + cs_n = "--____________________________________________________________" + dq_oe = "__------------________________________________________________" + dq_o = "00a000048d0000000000000000000000000000000000000000000000000000" + dq_i = "00000000000000000000000000000000000000deadbeefcafefade00000000" + rwds_oe = "______________________________________________________________" + for i in range(len(clk)): + yield dut.pads.dq.i.eq(int(dq_i[2*(i//2):2*(i//2)+2], 16)) + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + yield + + dut = HyperRAM(HyperRamPads(), latency=7, latency_mode="variable") + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") + + def test_hyperram_reg_write(self): + def fpga_gen(dut): + yield dut.reg_addr.eq(2) + yield dut.reg_write_data.eq(0x1234) + yield + yield dut.reg_write.eq(1) + yield + yield dut.reg_write.eq(0) + for i in range(128): + yield + + def hyperram_gen(dut): + clk = "_____--__--__--__--___________" + cs_n = "----________________----------" + dq_oe = "____----------------__________" + dq_o = "000060000100000012340000000000" + rwds_oe = "______________________________" + rwds_o = "______________________________" + for i in range(len(clk)): + self.assertEqual(c2bool(clk[i]), (yield dut.pads.clk)) + self.assertEqual(c2bool(cs_n[i]), (yield dut.pads.cs_n)) + self.assertEqual(c2bool(dq_oe[i]), (yield dut.pads.dq.oe)) + if (yield dut.pads.dq.oe): + self.assertEqual(int(dq_o[2*(i//2):2*(i//2)+2], 16), (yield dut.pads.dq.o)) + self.assertEqual(c2bool(rwds_oe[i]), (yield dut.pads.rwds.oe)) + self.assertEqual(c2bool(rwds_o[i]), (yield dut.pads.rwds.o)) + yield + + dut = HyperRAM(HyperRamPads(), with_csr=False) + run_simulation(dut, [fpga_gen(dut), hyperram_gen(dut)], vcd_name="sim.vcd") \ No newline at end of file diff --git a/test/test_i2c.py b/test/test_i2c.py new file mode 100755 index 000000000..37f72abda --- /dev/null +++ b/test/test_i2c.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# +# This file is part of MiSoC and has been adapted/modified for Litex. +# +# Copyright 2007-2023 / M-Labs Ltd +# Copyright 2012-2015 / Enjoy-Digital +# Copyright from Misoc LICENCE file added above +# +# Copyright 2023 Andrew Dennison +# +# SPDX-License-Identifier: BSD-2-Clause + +import unittest + +from migen import * +from migen.fhdl.specials import Tristate + +from litex.soc.cores.i2c import * + + +class _MockPads: + def __init__(self): + self.scl = Signal() + self.sda = Signal() + + +class _MockTristateImpl(Module): + def __init__(self, t): + t.i_mock = Signal(reset=True) + self.comb += [ + If(t.oe, + t.target.eq(t.o), + t.i.eq(t.o), + ).Else( + t.target.eq(t.i_mock), + t.i.eq(t.i_mock), + ), + ] + + +class _MockTristate: + """A mock `Tristate` for simulation + + This simulation ensures the TriState input (_i) tracks the output (_o) when output enable + (_oe) = 1. A new i_mock `Signal` is added - this can be written to in the simulation to represent + input from the external device. + + Example usage: + + class TestMyModule(unittest.TestCase): + def test_mymodule(self): + dut = MyModule() + io = Signal() + dut.io_t = TSTriple() + self.io_tristate = self.io_t.get_tristate(io) + + dut.comb += [ + dut.io_t.oe.eq(signal_for_oe), + dut.io_t.o.eq(signal_for_o), + signal_for_i.eq(dut.io_t.i), + ] + + def generator() + yield dut.io_tristate.i_mock.eq(some_value) + if (yield dut.io_t.oe): + self.assertEqual((yield dut.scl_t.i), (yield dut.io_t.o)) + else: + self.assertEqual((yield dut.scl_t.i), some_value) + + """ + + @staticmethod + def lower(t): + return _MockTristateImpl(t) + + +class TestI2C(unittest.TestCase): + def test_i2c(self): + pads = _MockPads() + dut = I2CMaster(pads) + + def check_trans(scl, sda, msg=""): + scl, sda = int(scl), int(sda) + scl_init, sda_init = (yield dut.scl_t.i), (yield dut.sda_t.i) + timeout = 0 + while True: + scl_now, sda_now = (yield dut.scl_t.i), (yield dut.sda_t.i) + if scl_now == scl and sda_now == sda: + return + timeout += 1 + self.assertLess(timeout, 20, + f"\n*** {msg} timeout. Waiting for: " + + f"scl:{scl_now} checking:{scl_init}=>{scl} " + + f"sda:{sda_now} checking:{sda_init}=>{sda} ***" + ) + yield + + def wait_idle(do=lambda: ()): + timeout = 0 + while True: + timeout += 1 + self.assertLess(timeout, 20) + idle = ((yield from dut.bus.read(I2C_XFER_ADDR)) & I2C_IDLE) != 0 + if idle: + return + yield + + def write_bit(value): + # print(f"write_bit:{value}") + yield from check_trans(scl=False, sda=value) + yield from check_trans(scl=True, sda=value) + + def write_ack(value): + # print(f"write_ack:{value}") + yield from check_trans(scl=False, sda=not value) + yield from check_trans(scl=True, sda=not value) + yield from wait_idle() + + def read_bit(value): + print(f"read_bit:{value}") + yield dut.sda_tristate.i_mock.eq(value) + yield from check_trans(scl=True, sda=value) + yield from check_trans(scl=False, sda=value) + yield dut.sda_tristate.i_mock.eq(True) + + def read_ack(value): + #print(f"read_ack:{value}") + yield from check_trans(scl=False, sda=True) + yield dut.sda_tristate.i_mock.eq(not value) + yield from check_trans(scl=True, sda=not value) + yield from wait_idle() + yield dut.sda_tristate.i_mock.eq(True) + ack = ((yield from dut.bus.read(I2C_XFER_ADDR)) & I2C_ACK) != 0 + self.assertEqual(ack, value) + + def i2c_restart(): + yield from check_trans(scl=False, sda=True, msg="checking restart precondition") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) + yield from check_trans(scl=False, sda=True, msg="checking restart0") + yield from check_trans(scl=True, sda=True, msg="checking restart1") + yield from check_trans(scl=True, sda=False, msg="checking start0") + yield from wait_idle() + + def i2c_start(): + yield from check_trans(scl=True, sda=True, msg="checking start precondition") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_START) + yield from check_trans(scl=True, sda=False, msg="checking start0") + yield from wait_idle() + + def i2c_stop(): + yield from check_trans(scl=False, sda=True, msg="checking stop after read or write") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_STOP) + yield from check_trans(scl=False, sda=False, msg="checking STOP0") + yield from check_trans(scl=True, sda=False, msg="checking STOP1") + yield from check_trans(scl=True, sda=True, msg="checking STOP2") + yield from wait_idle() + + def i2c_write(value, ack=True): + value = int(value) + test_bin = "{0:08b}".format(value) + # print(f"I2C_WRITE | {hex(value)}:0x{test_bin}") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_WRITE | value) + for i in list(test_bin): + yield from write_bit(int(i)) + yield from read_ack(True) + + def i2c_read(value, ack=True): + value = int(value) + test_bin = "{0:08b}".format(value) + print(f"I2C_READ | {hex(value)}:0x{test_bin}") + yield from dut.bus.write(I2C_XFER_ADDR, I2C_READ | (I2C_ACK if ack else 0)) + for i in list(test_bin): + yield from read_bit(int(i)) + yield dut.sda_tristate.i_mock.eq(True) + data = (yield from dut.bus.read(I2C_XFER_ADDR)) & 0xFF + self.assertEqual(data, value) + yield from write_ack(ack) + + def check(): + yield from dut.bus.write(I2C_CONFIG_ADDR, 4) + data = (yield from dut.bus.read(I2C_CONFIG_ADDR)) & 0xFF + self.assertEqual(data, 4) + + print("write 1 byte 0x18 to address 0x41") + yield from i2c_start() + yield from i2c_write(0x41 << 1 | 0) + yield from i2c_write(0x18, ack=False) + yield from i2c_stop() + + print("read 1 byte from address 0x41") + yield from i2c_start() + yield from i2c_write(0x41 << 1 | 1) + yield from i2c_read(0x18, ack=False) + + print("write 2 bytes 0x10 0x00 to address 0x11") + yield from i2c_restart() + yield from i2c_write(0x11 << 1 | 0) + yield from i2c_write(0x10, ack=True) + yield from i2c_write(0x00, ack=False) + yield from i2c_stop() + + print("read 1 byte from address 0x11") + yield from i2c_start() + yield from i2c_write(0x11 << 1 | 1) + yield from i2c_read(0x81, ack=False) + + print("read 2 bytes from address 0x55") + yield from i2c_restart() + yield from i2c_write(0x55 << 1 | 1) + yield from i2c_read(0xDE, ack=True) + yield from i2c_read(0xAD, ack=False) + yield from i2c_stop() + + clocks = { + "sys": 10, + "async": (10, 3), + } + generators = { + "sys": [ + check(), + ], + } + run_simulation(dut, generators, clocks, special_overrides={Tristate: _MockTristate}, vcd_name="i2c.vcd") + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_spi_mmap.py b/test/test_spi_mmap.py index 8915eef23..f4d36eeb4 100644 --- a/test/test_spi_mmap.py +++ b/test/test_spi_mmap.py @@ -6,32 +6,77 @@ # SPDX-License-Identifier: BSD-2-Clause import unittest -import random +import inspect -from migen import * +from migen import Record -from litex.gen.sim import * +from litex.gen.sim import run_simulation + +from litex.soc.cores.spi.spi_mmap import ( + SPIMaster, + SPIMMAP, + SPI_SLOT_BITORDER_LSB_FIRST, + SPI_SLOT_BITORDER_MSB_FIRST, + SPI_SLOT_LENGTH_16B, + SPI_SLOT_LENGTH_24B, + SPI_SLOT_LENGTH_32B, + SPI_SLOT_LENGTH_8B, + SPI_SLOT_MODE_0, + SPI_SLOT_MODE_3, +) + +verbose = None + + +def unittest_verbosity(): + """Return the verbosity setting of the currently running unittest + program, or 0 if none is running. + + """ + frame = inspect.currentframe() + while frame: + self = frame.f_locals.get("self") + if isinstance(self, unittest.TestProgram): + return self.verbosity + frame = frame.f_back + return 0 + + +def vprint(*args): + global verbose + if verbose is None: + verbose = unittest_verbosity() + if verbose > 1: + print(*args) + + +def vvprint(*args): + global verbose + if verbose is None: + verbose = unittest_verbosity() + if verbose > 2: + print(*args) -from litex.soc.cores.spi.spi_mmap import SPIMaster class TestSPIMMAP(unittest.TestCase): def test_spi_master(self): pads = Record([("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)]) - dut = SPIMaster(pads=pads, data_width=32, sys_clk_freq=int(100e6)) + dut = SPIMaster(pads=pads, data_width=32, sys_clk_freq=int(100e6)) + def generator(dut): data = [ 0x12345678, - 0xdeadbeef, + 0xDEADBEEF, ] - #data = [ + # data = [ # 0x80000001, # 0x80000001, - #] + # ] # Config: Mode0, Loopback, Sys-Clk/4 yield dut.loopback.eq(1) yield dut.clk_divider.eq(4) - yield dut.mode.eq(0) + yield dut.mode.eq(SPI_SLOT_MODE_0) yield yield dut.mosi.eq(data[0]) yield dut.cs.eq(0b0001) @@ -49,7 +94,7 @@ class TestSPIMMAP(unittest.TestCase): # Config: Mode3, Loopback, Sys-Clk/4. yield dut.loopback.eq(1) yield dut.clk_divider.eq(4) - yield dut.mode.eq(3) + yield dut.mode.eq(SPI_SLOT_MODE_3) yield yield dut.mosi.eq(data[0]) yield dut.cs.eq(0b0001) @@ -67,7 +112,7 @@ class TestSPIMMAP(unittest.TestCase): # Config: Mode0, Loopback, Sys-Clk/8. yield dut.loopback.eq(1) yield dut.clk_divider.eq(8) - yield dut.mode.eq(0) + yield dut.mode.eq(SPI_SLOT_MODE_0) yield yield dut.mosi.eq(data[1]) yield dut.cs.eq(0b0001) @@ -85,7 +130,7 @@ class TestSPIMMAP(unittest.TestCase): # Config: Mode3, Loopback, Sys-Clk/8. yield dut.loopback.eq(1) yield dut.clk_divider.eq(8) - yield dut.mode.eq(3) + yield dut.mode.eq(SPI_SLOT_MODE_3) yield yield dut.mosi.eq(data[1]) yield dut.cs.eq(0b0001) @@ -101,3 +146,190 @@ class TestSPIMMAP(unittest.TestCase): print(f"mosi_data : {(yield dut.miso):08x}") run_simulation(dut, generator(dut), vcd_name="sim.vcd") + + def mmap_test(self, length, bitorder, data, vcd_name=None, sel_override=None, wait=0): + pads = Record([("clk", 1), ("cs_n", 4), ("mosi", 1), ("miso", 1)]) + dut = SPIMMAP( + pads=pads, + data_width=32, + sys_clk_freq=int(100e6), # only used for clock settle time! + tx_fifo_depth=32, + rx_fifo_depth=32, + ) + + def generator(dut): + # Minimal setup - spi_mmap ctrl defaults are everything enabled and: + # SPI_SLOT_MODE_3, SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, loopback, divider=2, wait=0 + version = yield dut.ctrl._version.status + vprint(f"version: {version}") + vprint(f"slot_count: {(yield dut.ctrl.slot_count.status)}") + # yield dut.ctrl.slot_control0.fields.enable.eq(1) + # yield dut.ctrl.slot_control0.fields.mode.eq(SPI_SLOT_MODE_3) + yield dut.ctrl.slot_control0.fields.length.eq(length) + yield dut.ctrl.slot_control0.fields.bitorder.eq(bitorder) + yield dut.ctrl.slot_control1.fields.length.eq(length) + yield dut.ctrl.slot_control1.fields.bitorder.eq(bitorder) + # yield dut.ctrl.slot_control0.fields.loopback.eq(1) + # yield dut.ctrl.slot_control0.fields.divider.eq(2) + # yield dut.ctrl.slot_control0.fields.enable.eq(1) + yield dut.ctrl.slot_control0.fields.wait.eq(wait) + if length == SPI_SLOT_LENGTH_32B: + spi_length = 32 + sel = 0b1111 + width = 8 + if length == SPI_SLOT_LENGTH_24B: + spi_length = 24 + sel = 0b1111 + width = 6 + if length == SPI_SLOT_LENGTH_16B: + spi_length = 16 + sel = 0b0011 + width = 4 + if length == SPI_SLOT_LENGTH_8B: + spi_length = 8 + sel = 0b0001 + width = 2 + if sel_override: + sel = sel_override + + vprint(f"spi_length {spi_length} width {width} sel {sel:b} len(data) {len(data)}") + + dut_tx_status = dut.ctrl.tx_status.fields + dut_rx_status = dut.ctrl.rx_status.fields + self.assertEqual((yield dut_tx_status.empty), 1) + self.assertEqual((yield dut_tx_status.full), 0) + self.assertEqual((yield dut_tx_status.ongoing), 0) + self.assertEqual((yield dut_tx_status.level), 0) + self.assertEqual((yield dut_rx_status.empty), 1) + self.assertEqual((yield dut_rx_status.full), 0) + self.assertEqual((yield dut_rx_status.ongoing), 0) + self.assertEqual((yield dut_rx_status.level), 0) + for slot, d in data: + vprint(f"write({slot}):{d:0{width}x}") + yield from dut.tx_mmap.bus.write(slot, d, sel) + yield + self.assertEqual((yield dut_tx_status.empty), 0) + self.assertEqual((yield dut_tx_status.full), 0) + self.assertEqual((yield dut_tx_status.ongoing), 1) + self.assertGreater((yield dut_tx_status.level), 0) + self.assertEqual((yield dut_rx_status.empty), 1) + self.assertEqual((yield dut_rx_status.full), 0) + self.assertEqual((yield dut_rx_status.ongoing), 1) + self.assertEqual((yield dut_rx_status.level), 0) + + tx_empty = -1 + rx_empty = -1 + miso = -1 + mosi = -1 + while (yield dut_rx_status.ongoing) == 0b1 or (yield dut_rx_status.level) != len(data): + if rx_empty != (rx_empty := (yield dut_rx_status.empty)): + vprint(f"rx_empty:{rx_empty}") + if tx_empty != (tx_empty := (yield dut_tx_status.empty)): + vprint(f"tx_empty:{tx_empty}") + if mosi != (mosi := (yield dut.tx_rx_engine.spi.mosi)): + vvprint(f"mosi => {mosi:0{width}x}") + if miso != (miso := (yield dut.tx_rx_engine.spi.miso)): + vvprint(f"miso <= {miso:0{width}x}") + yield + + yield + for slot, d in data: + read = yield from dut.rx_mmap.bus.read(slot) + self.assertEqual(read, d, f"read({slot}) {read:0{width}x} expect: {d:0{width}x}") + + run_simulation(dut, generator(dut), vcd_name=vcd_name) + + # 32 bit write to 32bit slot + def test_spi_mmap_32_lsb(self): + data = [(0, 0x12345678), (0, 0x9ABCDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_32_lsb.vcd") + + def test_spi_mmap_32_msb(self): + data = [(0, 0x12345678), (0, 0x9ABCDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_32_msb.vcd") + + def test_spi_mmap_32_slot0_1_lsb(self): + data = [ + (0, 0x12345678), (0, 0x9ABCDEF0), (0, 0x87654321), (0, 0x0FEDCBA9), + (1, 0x0FEDCBA9), (1, 0x87654321), (1, 0x9ABCDEF0), (1, 0x12345678) + ] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_32_slot_0_1_lsb.vcd") + + def test_spi_mmap_32_slot0_1_msb(self): + data = [ + (0, 0x12345678), (0, 0x9ABCDEF0), (0, 0x87654321), (0, 0x0FEDCBA9), + (1, 0x0FEDCBA9), (1, 0x87654321), (1, 0x9ABCDEF0), (1, 0x12345678) + ] + self.mmap_test(SPI_SLOT_LENGTH_32B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_32_slot_0_1_msb.vcd") + + def test_spi_mmap_24_lsb(self): + data = [(0, 0x123456), (0, 0x789ABC), (0, 0xDEF012)] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_24_lsb.vcd") + + def test_spi_mmap_24_msb(self): + data = [(0, 0x123456), (0, 0x789ABC), (0, 0xDEF012)] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_24_msb.vcd") + + def test_spi_mmap_24_slot0_1_lsb(self): + data = [ + (0, 0x123456), (0, 0x9ABCDE), (0, 0x876543), (0, 0x0FEDCB), + (1, 0x0FEDCB), (1, 0x876543), (1, 0x9ABCDE), (1, 0x123456) + ] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_24_slot_0_1_lsb.vcd") + + def test_spi_mmap_24_slot0_1_msb(self): + data = [ + (0, 0x123456), (0, 0x9ABCDE), (0, 0x876543), (0, 0x0FEDCB), + (1, 0x0FEDCB), (1, 0x876543), (1, 0x9ABCDE), (1, 0x123456) + ] + self.mmap_test(SPI_SLOT_LENGTH_24B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_24_slot_0_1_msb.vcd") + + # 16 bit write to 16bit slot + def test_spi_mmap_16_lsb(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_16B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_16_lsb.vcd") + + def test_spi_mmap_16_msb(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test(SPI_SLOT_LENGTH_16B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_16_msb.vcd") + + # 32 bit write to 16bit slot + def test_spi_mmap_16_lsb_wb32(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test( + SPI_SLOT_LENGTH_16B, + SPI_SLOT_BITORDER_LSB_FIRST, + data, + "mmap_16_lsb_wb32.vcd", + sel_override=0b1111, + ) + + def test_spi_mmap_16_msb_wb32(self): + data = [(0, 0x1234), (0, 0x5678), (0, 0x9ABC), (0, 0xDEF0)] + self.mmap_test( + SPI_SLOT_LENGTH_16B, + SPI_SLOT_BITORDER_MSB_FIRST, + data, + "mmap_16_msb_wb32.vcd", + sel_override=0b1111, + ) + + # 8 bit write to 8bit slot + def test_spi_mmap_8_lsb(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_LSB_FIRST, data, "mmap_8_lsb.vcd") + + def test_spi_mmap_8_msb(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb.vcd") + + def test_spi_mmap_8_msb_wait1(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb_wait1.vcd", wait=1) + + def test_spi_mmap_8_msb_wait8(self): + data = [(0, 0x12), (0, 0x34), (0, 0x56), (0, 0x78), (0, 0x9A), (0, 0xBC), (0, 0xDE), (0, 0xF0)] + self.mmap_test(SPI_SLOT_LENGTH_8B, SPI_SLOT_BITORDER_MSB_FIRST, data, "mmap_8_msb_wait8.vcd", wait=8) + +if __name__ == "__main__": + unittest.main()