diff --git a/data/rtl-config-verilog.json b/data/rtl-config-verilog.json index be9f0bd9c7..b1e5f8548b 100644 --- a/data/rtl-config-verilog.json +++ b/data/rtl-config-verilog.json @@ -905,9 +905,9 @@ }, { "name": "handshake.lsq", - "generator": "/usr/bin/env python3 $DYNAMATIC/tools/backend/lsq-generator-python/lsq-generator.py -o $OUTPUT_DIR -c $OUTPUT_DIR/$MODULE_NAME.json", + "generator": "/usr/bin/env python3 $DYNAMATIC/tools/backend/lsq-generator-python/lsq-generator.py -o $OUTPUT_DIR -c $OUTPUT_DIR/$MODULE_NAME.json --hdl verilog", "use-json-config": "$OUTPUT_DIR/$MODULE_NAME.json", - "hdl": "vhdl", + "hdl": "verilog", "io-kind": "flat", "io-map": [{ "clk": "clock" }, { "rst": "reset" }, { "*": "io_*" }], "io-signals": { diff --git a/data/rtl-config-vhdl.json b/data/rtl-config-vhdl.json index 9d9d286e33..9eb74a6656 100644 --- a/data/rtl-config-vhdl.json +++ b/data/rtl-config-vhdl.json @@ -55,7 +55,7 @@ }, { "name": "handshake.lsq", - "generator": "/usr/bin/env python3 $DYNAMATIC/tools/backend/lsq-generator-python/lsq-generator.py -o $OUTPUT_DIR -c $OUTPUT_DIR/$MODULE_NAME.json", + "generator": "/usr/bin/env python3 $DYNAMATIC/tools/backend/lsq-generator-python/lsq-generator.py -o $OUTPUT_DIR -c $OUTPUT_DIR/$MODULE_NAME.json --hdl vhdl", "use-json-config": "$OUTPUT_DIR/$MODULE_NAME.json", "hdl": "vhdl", "io-kind": "flat", @@ -374,7 +374,7 @@ }, { "name": "handshake.lsq", - "generator": "/usr/bin/env python3 $DYNAMATIC/tools/backend/lsq-generator-python/lsq-generator.py -o $OUTPUT_DIR -c $OUTPUT_DIR/$MODULE_NAME.json", + "generator": "/usr/bin/env python3 $DYNAMATIC/tools/backend/lsq-generator-python/lsq-generator.py -o $OUTPUT_DIR -c $OUTPUT_DIR/$MODULE_NAME.json --hdl vhdl", "use-json-config": "$OUTPUT_DIR/$MODULE_NAME.json", "hdl": "vhdl", "io-kind": "flat", diff --git a/tools/backend/lsq-generator-python/README.md b/tools/backend/lsq-generator-python/README.md index 020e5838e0..7cba7dc8db 100644 --- a/tools/backend/lsq-generator-python/README.md +++ b/tools/backend/lsq-generator-python/README.md @@ -20,7 +20,7 @@ This Python-based LSQ generator generates the LSQ design outlined in Hailin Wang ### Sampele usage ``` -usage: lsq-generator.py [-h] [--output-dir OUTPUT_PATH] --config-file CONFIG_FILES +usage: lsq-generator.py [-h] [--output-dir OUTPUT_PATH] --config-file CONFIG_FILES --hdl [vhdl|verilog] ``` ### Sample json configuration file (Example: Histogram) @@ -89,31 +89,27 @@ Configuration parameters needed for both chisel and Python based LSQ-generator c - **lsq-generator.py** Runs the tool. -- **vhdl_gen/** +- **core_gen/** - **\_\_init__.py** Re-exports a curated list of public API symbols (e.g. `main`, `generate`, `Logic`, `LSQ`). - - **cli.py** - Parses command-line arguments (with `argparse`), converts them into a `Configs` instance, and calls the core generator. - - **codegen.py** Implements the `codeGen(config: Configs)` function. - **configs.py** Defines the `Configs` class. - - **context.py** - Defines the `VHDLContext` class. It substitutes the previous `global` VHDL context variables. - - - **utils.py** - - Defines `VHDLLogicType`, `VHDLLogicVecType`, `VHDLLogicTypeArray`, `VHDLLogicVecTypeArray`, `OpTab`. - - `IntToBits`, `Zero`, `GetValue`, `MaskLess`, `isPow2`, `log2Ceil` helper functions. - - Classes and functions need to be relocated into other files later. + - **ir.py** + Defines the intermediate representation used to generate HDL + Defines `Statement`, `Type`, `Val`, `BinOp`, `Bin`, `UnOp`, `Un`, `Bit`, `CustomStatement`, `WhenElse`. - **signals.py** Defines the four signal classes: `Logic`, `LogicVec`, `LogicArray`, `LogicVecArray`. - - **vhdlgen/operators/** + - **utils.py** + - Defines `GetValue`, `isPow2`, `log2Ceil` helper functions. + + - **operators/** Low-level functions that generate VHDL snippets: - `assign.py`: `Op` - `arithmetic.py`: `WrapAdd`, `WrapAddConst`, `WrapSub` @@ -123,10 +119,17 @@ Configuration parameters needed for both chisel and Python based LSQ-generator c - `reduction.py`: `ReduceLogicVec`, `ReduceLogicArray`, `ReduceLogicVecArray`, `Reduce` - `shifts.py`: `RotateLogicVec`, `RotateLogicArray`, `RotateLogicVecArray`, `CyclicLeftShift` - - **vhdlgen/generators/** + - **generators/** High-level modules that build complete entities/architectures: - `dispatchers.py` : `PortToQueueDispatcher`, `QueueToPortDispatcher`, `PortToQueueDispatcherInit`, `QueueToPortDispatcherInit` - `group_allocator.py` : `GroupAllocator`, `GroupAllocatorInit` - `lsq.py` : `LSQ` + + - **emitters/** + Emitters used to emit either VHDL or Verilog code. + - `emmitter.py` : Abstract class `Emitter` + - `verilog_emitter.py` : `VerilogEmitter` + - `vhdl_emitter.py` : `VHDLEmitter` + diff --git a/tools/backend/lsq-generator-python/core_gen/__init__.py b/tools/backend/lsq-generator-python/core_gen/__init__.py new file mode 100644 index 0000000000..85abb9f2c6 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/__init__.py @@ -0,0 +1,13 @@ +# core_gen/__init__.py +from core_gen.configs import GetConfigs, Configs +from core_gen.codegen import codeGen + + +# from vhdlgen import * +__all__ = [ + # configs + "GetConfigs", + "Configs", + # codegen + "codeGen", +] diff --git a/tools/backend/lsq-generator-python/core_gen/codegen.py b/tools/backend/lsq-generator-python/core_gen/codegen.py new file mode 100644 index 0000000000..d26f198c7e --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/codegen.py @@ -0,0 +1,86 @@ +from core_gen.emitters import Emitter +import core_gen.generators.group_allocator as group_allocator +import core_gen.generators.dispatchers as dispatchers +import core_gen.generators.lsq as lsq + +import core_gen.generators.lsq_submodule_wrapper as lsq_submodule_wrapper + + +def codeGen(emitter: Emitter, path_rtl, configs): + # Initialize a wrapper object to hold all submodule generator instances. + lsq_submodules = lsq_submodule_wrapper.LSQ_Submodules() + + name = configs.name + "_core" + # empty the file + file = open(f"{path_rtl}/{name}.{emitter.get_file_suffix()}", "w").close() + + # Group Allocator + ga = group_allocator.GroupAllocator(name=name, suffix="_ga", configs=configs) + ga.generate(emitter.new(), path_rtl) + lsq_submodules.group_allocator = ga + + # When the condition "if configs.numLdPorts > 0:" is not true: + # Do not generating dispatching modules when there are zero load ports. + # + # - WARNING: This logic needs more testing + # - TODO: Also remove the load queue when there are zero load ports. + if configs.numLdPorts > 0: + # Load Address Port Dispatcher + ptq_dispatcher_lda = dispatchers.PortToQueueDispatcher( + name, + "_lda", + configs.numLdPorts, + configs.numLdqEntries, + configs.addrW, + configs.ldpAddrW, + ) + ptq_dispatcher_lda.generate(emitter.new(), path_rtl) + lsq_submodules.ptq_dispatcher_lda = ptq_dispatcher_lda + + # Load Data Port Dispatcher + qtp_dispatcher_ldd = dispatchers.QueueToPortDispatcher( + name, + "_ldd", + configs.numLdPorts, + configs.numLdqEntries, + configs.dataW, + configs.ldpAddrW, + ) + qtp_dispatcher_ldd.generate(emitter.new(), path_rtl) + lsq_submodules.qtp_dispatcher_ldd = qtp_dispatcher_ldd + + # Store Address Port Dispatcher + ptq_dispatcher_sta = dispatchers.PortToQueueDispatcher( + name, + "_sta", + configs.numStPorts, + configs.numStqEntries, + configs.addrW, + configs.stpAddrW, + ) + ptq_dispatcher_sta.generate(emitter.new(), path_rtl) + lsq_submodules.ptq_dispatcher_sta = ptq_dispatcher_sta + + # Store Data Port Dispatcher + ptq_dispatcher_std = dispatchers.PortToQueueDispatcher( + name, + "_std", + configs.numStPorts, + configs.numStqEntries, + configs.dataW, + configs.stpAddrW, + ) + ptq_dispatcher_std.generate(emitter.new(), path_rtl) + lsq_submodules.ptq_dispatcher_std = ptq_dispatcher_std + + # Store Backward Port Dispatcher + if configs.stResp: + qtp_dispatcher_stb = dispatchers.QueueToPortDispatcher( + name, "_stb", configs.numStPorts, configs.numStqEntries, 0, configs.stpAddrW + ) + qtp_dispatcher_stb.generate(emitter.new(), path_rtl) + lsq_submodules.qtp_dispatcher_stb = qtp_dispatcher_stb + + # Change the name of the following module to lsq_core + lsq_core = lsq.LSQ(name, "", configs) + lsq_core.generate(emitter, lsq_submodules, path_rtl) diff --git a/tools/backend/lsq-generator-python/core_gen/configs.py b/tools/backend/lsq-generator-python/core_gen/configs.py new file mode 100644 index 0000000000..fdbaa66713 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/configs.py @@ -0,0 +1,134 @@ +# +# Dynamatic is under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# + +import math +import json +import sys + +# read and parse the json file +# example json format in README + + +def GetConfigs(config_json_path: str): + with open(config_json_path, "r") as file: + configString = file.read() + configs = json.loads(configString) + return Configs(configs) + + +class Configs: + """ + Configuration object for LSQ code generation. + + This class is instantiated using 'GetConfigs(path_to_json_file)', which loads a JSON file + and overwrites all the values below using user-defined parameters. + + The values shown below are NOT the actual values used during generation; + they are one of possible default configurations. + """ + + name: str = "test" # Name prefix used for generated VHDL files + dataW: int = 16 # Data width (Number of bits for load/store data) + addrW: int = 13 # Address width (Number of bits for memory address) + idW: int = 2 # ID width (Number of bits for ID in the memory interface) + numLdqEntries: int = 3 # Load queue size (Number of entries in the load queue) + numStqEntries: int = 10 # Store queue size (Number of entries in the store queue) + numLdPorts: int = 3 # Number of load access ports + numStPorts: int = 3 # Number of store access ports + numGroups: int = 2 # Number of total Basic Blocks (BBs) + numLdMem: int = 1 # Number of load channels at memory interface (Fixed to 1) + numStMem: int = 1 # Number of store channels at memory interface (Fixed to 1) + + gaNumLoads: list = [2, 1] # Number of loads in each BB + gaNumStores: list = [2, 1] # Number of stores in each BB + gaLdOrder: list = [ + [2, 2], # The order matrix for each group + [0], + ] # Outer list (Row): Index for each BB + # Inner list (Column): List of store counts ahead of each load + # In this example -> BB0=[st0,st1,ld0,ld1], BB1=[ld2,st2] + gaLdPortIdx: list = [ + [0, 1], # The related access port index for each load in BB + [2], + ] + gaStPortIdx: list = [ + [0, 1], # The related access port index for each store in BB + [2], + ] + ldqAddrW: int = 2 # Load queue address width + stqAddrW: int = 4 # Store queue address width + ldpAddrW: int = 2 # Load port address width + stpAddrW: int = 2 # Store port address width + + pipe0: bool = False # Enable pipeline register 0 + pipe1: bool = False # Enable pipeline register 1 + pipeComp: bool = False # Enable pipeline register pipeComp + headLag: bool = False # Whether the head pointer of the load queue is updated + # one cycle later than the valid bits of entries + stResp: bool = ( + False # Whether store response channel in store access port is enabled + ) + gaMulti: bool = ( + False # Whether multiple groups are allowed to request an allocation at the same cycle + ) + + def __init__(self, config: dict) -> None: + self.name = config["name"] + self.dataW = config["dataWidth"] + self.addrW = config["addrWidth"] + self.idW = config["indexWidth"] + self.numLdqEntries = config["fifoDepth_L"] + self.numStqEntries = config["fifoDepth_S"] + self.numLdPorts = config["numLoadPorts"] + self.numStPorts = config["numStorePorts"] + self.numGroups = config["numBBs"] + self.numLdMem = config["numLdChannels"] + self.numStMem = config["numStChannels"] + self.master = bool(config["master"]) + + self.stResp = bool(config["stResp"]) + self.gaMulti = bool(config["groupMulti"]) + + self.gaNumLoads = config["numLoads"] + self.gaNumStores = config["numStores"] + self.gaLdOrder = config["ldOrder"] + self.gaLdPortIdx = config["ldPortIdx"] + self.gaStPortIdx = config["stPortIdx"] + + self.ldqAddrW = math.ceil(math.log2(self.numLdqEntries)) + self.stqAddrW = math.ceil(math.log2(self.numStqEntries)) + + # Original empty assignment assumes the size of load or store queue to be always a multiple of 2 + if self.numLdqEntries & (self.numLdqEntries % 2 == 0): + self.emptyLdAddrW = math.ceil(math.log2(self.numLdqEntries + 1)) + else: + self.emptyLdAddrW = math.ceil(math.log2(self.numLdqEntries)) + 1 + + if self.numStqEntries & (self.numStqEntries % 2 == 0): + self.emptyStAddrW = math.ceil(math.log2(self.numStqEntries + 1)) + else: + self.emptyStAddrW = math.ceil(math.log2(self.numStqEntries)) + 1 + # Check the number of ports, if num*Ports == 0, set it to 1 + self.ldpAddrW = math.ceil( + math.log2(self.numLdPorts if self.numLdPorts > 0 else 1) + ) + self.stpAddrW = math.ceil( + math.log2(self.numStPorts if self.numStPorts > 0 else 1) + ) + + self.pipe0 = bool(config["pipe0En"]) + self.pipe1 = bool(config["pipe1En"]) + self.pipeComp = bool(config["pipeCompEn"]) + self.headLag = bool(config["headLagEn"]) + + assert self.idW >= self.ldqAddrW + + # list size checking + assert len(self.gaNumLoads) == self.numGroups + assert len(self.gaNumStores) == self.numGroups + assert len(self.gaLdOrder) == self.numGroups + assert len(self.gaLdPortIdx) == self.numGroups + assert len(self.gaStPortIdx) == self.numGroups diff --git a/tools/backend/lsq-generator-python/core_gen/emitters/__init__.py b/tools/backend/lsq-generator-python/core_gen/emitters/__init__.py new file mode 100644 index 0000000000..a2e8077ed3 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/emitters/__init__.py @@ -0,0 +1,6 @@ +# core_gen/emitters/__init__.py +from core_gen.emitters.emitter import Emitter +from core_gen.emitters.vhdl_emitter import VHDLEmitter +from core_gen.emitters.verilog_emitter import VerilogEmitter + +__all__ = ["Emitter", "VHDLEmitter", "VerilogEmitter"] diff --git a/tools/backend/lsq-generator-python/core_gen/emitters/emitter.py b/tools/backend/lsq-generator-python/core_gen/emitters/emitter.py new file mode 100644 index 0000000000..1235edb51b --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/emitters/emitter.py @@ -0,0 +1,118 @@ +# ===----------------------------------------------------------------------===# +# Global Parameter Initialization +# ===----------------------------------------------------------------------===# +class Emitter: + """ + A context object to replace global variables for code generation. + Holds indentation level, temporary name counter, and initialization strings. + """ + + def __init__(self, clock_name: str = "clk", reset_name: str = "rst"): + self.tabLevel = 1 + self.tempCount = 0 + + self.signalInitString = "" + self.portInitString = "" + self.regInitString = "" + self.statementString = "" + + self.clock_name = clock_name + self.reset_name = reset_name + + self.inst_started = False + + # Keep Emitter abstract: prevent direct instantiation of the base class + if self.__class__ is Emitter: + raise NotImplementedError( + "Emitter is an abstract class and cannot be instantiated directly." + ) + + def get_current_indent(self) -> str: + return "\t" * self.tabLevel + + def increase_indent(self): + self.tabLevel += 1 + + def decrease_indent(self): + self.tabLevel = max(0, self.tabLevel - 1) + + def get_temp(self, name: str) -> str: + return f"TEMP_{self.tempCount}_{name}" + + def use_temp(self): + self.tempCount += 1 + + def add_signal_str(self, code: str): + self.signalInitString += code + + def add_port_str(self, code: str): + self.portInitString += code + + def add_statement(self, code: str): + self.statementString += self.get_current_indent() + code + + def add_reg_str(self, code: str): + raise NotImplementedError("Emitter subclasses must implement add_reg_str()") + + def add_assignment(self, out, statement: 'Statement', in_process: bool = False): + raise NotImplementedError("Emitter subclasses must implement add_assignment()") + + def add_comment(self, comment: str): + raise NotImplementedError("Emitter subclasses must implement add_comment()") + + def get_binop_str(self, op) -> str: + raise NotImplementedError("Emitter subclasses must implement get_binop_str()") + + def get_unop_str(self, op) -> str: + raise NotImplementedError("Emitter subclasses must implement get_unop_str()") + + def get_bit_str(self, bit) -> str: + raise NotImplementedError("Emitter subclasses must implement get_bit_str()") + + def bin_to_str(self, bin, meta: 'Meta') -> str: + raise NotImplementedError("Emitter subclasses must implement bin_to_str()") + + def un_to_str(self, un, meta: 'Meta') -> str: + raise NotImplementedError("Emitter subclasses must implement un_to_str()") + + def assigned_var_to_str(self, var): + from core_gen.signals import Logic + + size = 1 + if type(var) == tuple: + if len(var) == 2: + str_ret = f"{var[0].getNameWrite(var[1])}" + else: + str_ret = f"{var[0].getNameWrite(var[1], var[2])}" + else: + str_ret = f"{var.getNameWrite()}" + if type(var) != Logic: + size = var.size + + return str_ret, size + + @staticmethod + def _int_to_bin(val: int, size: int) -> str: + """ + Converts an integer to a binary string of the specified size. + Example: + int_to_bin(5, 8) # Output: 00000101 + int_to_bin(10, 4) # Output: 1010 + int_to_bin(3, 3) # Output: 011 + int_to_bin(0, 5) # Output: 00000 + """ + if val < 0 or val >= (1 << size): + raise ValueError(f"Value {val} out of range for the specified size {size}") + + return f"{val:0{size}b}" + + +class Meta: + """ + Contains the meta necessary to generate correct sub statements + """ + + def __init__(self, size, statement_type, precedence): + self.size = size + self.type = statement_type + self.precedence = precedence diff --git a/tools/backend/lsq-generator-python/core_gen/emitters/verilog_emitter.py b/tools/backend/lsq-generator-python/core_gen/emitters/verilog_emitter.py new file mode 100644 index 0000000000..8a2be64dac --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/emitters/verilog_emitter.py @@ -0,0 +1,383 @@ +from core_gen.emitters.emitter import Emitter, Meta +from core_gen.ir import Statement, Bin, Un, BinOp, UnOp, Bit, Type +from core_gen.signals import Logic, LogicVec, LogicArray, LogicVecArray + + +# ===----------------------------------------------------------------------===# +# Global Parameter Initialization +# ===----------------------------------------------------------------------===# +class VerilogEmitter(Emitter): + """ + A context object to replace global variables for VHDL code generation. + Holds indentation level, temporary name counter, and initialization strings. + """ + + def __init__(self, clock_name="clk", reset_name="rst"): + # Initialize common emitter fields + super().__init__(clock_name=clock_name, reset_name=reset_name) + + def get_reg_init_str(self) -> str: + return f"always @(posedge {self.clock_name}) begin\n" + + def get_reg_end_str(self) -> str: + return "end\n" + + def get_port_init_str(self) -> str: + return f"(\n\t\tinput {self.reset_name},\n\t\tinput {self.clock_name}" + + def get_port_end_str(self) -> str: + return "\n\t);" + + def add_reg_str(self, code: str): + self.regInitString += self.get_current_indent() + code + "\n" + + def add_comment(self, comment: str): + for line in comment.split("\n"): + self.statementString += self.get_current_indent() + f"// {line}\n" + + def add_assignment(self, out, statement: Statement, in_process=False): + out_str, size = self.assigned_var_to_str(out) + meta = Meta(size, Type.LOGIC, -1) + statement_str = statement.to_str(self, meta) + # Assume we only write to logic types + if in_process: + self.statementString += ( + self.get_current_indent() + f"{out_str} <= {statement_str};\n" + ) + else: + self.statementString += ( + self.get_current_indent() + f"assign {out_str} = {statement_str};\n" + ) + + def get_definition_str(self, module_name: str, write_regs=True) -> str: + return ( + f"module {module_name} " + + self.get_current_indent() + + self.get_port_init_str() + + self.portInitString + + self.get_port_end_str() + + "\n" + + "// SIGNAL INIT\n" + + self.signalInitString + + "\n" + + "// STATEMENTS\n" + + self.statementString + + "\n" + + ( + self.get_current_indent() + + self.get_reg_init_str() + + self.regInitString + + self.get_reg_end_str() + if write_regs and self.regInitString != "" + else "" + ) + + "endmodule\n" + ) + + def start_instantiation(self, module_name: str, instance_name: str = None) -> str: + if self.inst_started: # Sanity check to prevent overlapping instantiations + raise ValueError( + "start_instantiation called while another instantiation is in progress" + ) + + if instance_name is None: + instance_name = module_name + + self.inst_started = True + self.first_map = True + self.inst_str = f"{self.get_current_indent()}{module_name} {instance_name} (\n" + self.increase_indent() + + def add_map(self, port_name: str, signal_name: str = "") -> str: + if not self.inst_started: + raise ValueError("add_map can only be called after start_instantiation") + + assert isinstance(port_name, str) and isinstance( + signal_name, str + ), "port name and signal name must be strings" + + if not self.first_map: + self.inst_str += ",\n" + else: + self.first_map = False + + self.inst_str += f"{self.get_current_indent()}.{port_name}({signal_name})" + + def complete_instantiation(self) -> str: + self.inst_started = False + self.decrease_indent() + self.inst_str += self.get_current_indent() + ");\n" + self.statementString += self.inst_str + self.inst_str = "" + + BINOP_STRINGS = { + BinOp.ADD: "+", + BinOp.SUB: "-", + BinOp.AND: "&", + BinOp.OR: "|", + BinOp.XOR: "^", + BinOp.MUL: "*", + BinOp.GE: ">=", + BinOp.LE: "<=", + BinOp.GT: ">", + BinOp.LT: "<", + BinOp.EQ: "==", + BinOp.NEQ: "!=", + } + + def get_binop_str(self, op: Bin) -> str: + if op in self.BINOP_STRINGS: + return self.BINOP_STRINGS[op] + else: + raise ValueError("Invalid binary operator: " + str(op)) + + def get_unop_str(self, unop: UnOp) -> str: + if unop == UnOp.NOT: + return "~" + else: + raise ValueError("Invalid unary operator") + + def get_bit_str(self, bit: Bit) -> str: + if bit.value == 0: + return "1'b0" + elif bit.value == 1: + return "1'b1" + else: + raise ValueError("Invalid bit value") + + def bin_to_str(self, bin: Bin, meta: Meta) -> str: + meta = Meta(meta.size, bin.get_param_type(), bin.get_precedence()) + left_str = bin.left.to_str(self, meta) + right_str = bin.right.to_str(self, meta) + + if bin.op == BinOp.CONCAT: + return f"{{{left_str}, {right_str}}}" + + return f"{left_str} {self.get_binop_str(bin.op)} {right_str}" + + def un_to_str(self, un: Un, meta: Meta) -> str: + meta = Meta(meta.size, un.get_type(), un.get_precedence()) + val_str = un.val.to_str(self, meta) + return f"{self.get_unop_str(un.op)} {val_str}" + + def when_else_to_str(self, when_else, meta: Meta) -> str: + meta = Meta(meta.size, when_else.get_type(), when_else.get_precedence()) + true_str = when_else.true_statement.to_str(self, meta) + false_str = when_else.false_statement.to_str(self, meta) + cond_str = when_else.condition.to_str(self, meta) + + return f"{cond_str} ? {true_str} : {false_str}" + + def logic_signal_init(self, signal: Logic, sufix: str): + """ + Appends the appropriate declaration or port line for this signal to a global buffer. + """ + if signal.type == "w": + prefix = "reg" if signal.force_reg else "wire" + self.add_signal_str(f"\t{prefix} {signal.get_base_name(sufix)};\n") + elif signal.type == "r": + self.add_signal_str(f"\twire {signal.get_base_name(sufix)}_d;\n") + self.add_signal_str(f"\treg {signal.get_base_name(sufix)}_q;\n") + elif signal.type == "i": + self.add_port_str(",\n") + self.add_port_str( + f'\t\tinput {signal.get_base_name(sufix)}{'_i' if not signal.dyn_comp else ""}' + ) + elif signal.type == "o": + self.add_port_str(",\n") + self.add_port_str( + f'\t\toutput {signal.get_base_name(sufix)}{'_o' if not signal.dyn_comp else ""}' + ) + + def logicvec_signal_init(self, vec: LogicVec, sufix: str): + if vec.type == "w": + prefix = "reg" if vec.force_reg else "wire" + self.add_signal_str( + f"\t{prefix} [{vec.size-1}:0] {vec.get_base_name(sufix)};\n" + ) + elif vec.type == "r": + self.add_signal_str( + f"\twire [{vec.size-1}:0] {vec.get_base_name(sufix)}_d;\n" + ) + self.add_signal_str( + f"\treg [{vec.size-1}:0] {vec.get_base_name(sufix)}_q;\n" + ) + elif vec.type == "i": + self.add_port_str(",\n") + self.add_port_str( + f'\t\tinput [{vec.size-1}:0] {vec.get_base_name(sufix)}{'_i' if not vec.dyn_comp else ""}' + ) + elif vec.type == "o": + self.add_port_str(",\n") + self.add_port_str( + f'\t\toutput [{vec.size-1}:0] {vec.get_base_name(sufix)}{'_o' if not vec.dyn_comp else ""}' + ) + + def logic_reg_init(self, logic: Logic, enable=None, init=None) -> None: + """ + Generates a clocked process snippet that sets up the register's behavior. + For example, + + if (rst = '1') then + _q <= '0'; + elsif (rising_edge(clk)) then + _q <= _d; + end if; + """ + assert logic.type == "r" + if init is None: + init = 0 + self.increase_indent() + in_else = False + if init != None: + self.add_reg_str(f"if ({self.reset_name})") + self.add_reg_str(f"\t{logic.getNameRead()} <= {self.int_to_str(init)};") + self.add_reg_str("else begin") + in_else = True + self.increase_indent() + + if enable != None: + self.add_reg_str(f"if ({enable.getNameRead()})") + self.add_reg_str(f"\t{logic.getNameRead()} <= {logic.getNameWrite()};") + else: + self.add_reg_str(f"{logic.getNameRead()} <= {logic.getNameWrite()};") + + if in_else: + self.decrease_indent() + self.add_reg_str("end") + self.decrease_indent() + + def logicvec_reg_init(self, vec: LogicVec, enable=None, init=None) -> None: + assert vec.type == "r" + if init is None: + init = 0 + self.increase_indent() + in_else = False + if init != None: + self.add_reg_str(f"if ({self.reset_name})") + self.add_reg_str( + f"\t{vec.getNameRead()} <= {self.int_to_str(init, vec.size)};" + ) + self.add_reg_str("else begin") + in_else = True + self.increase_indent() + + if enable != None: + self.add_reg_str(f"if ({enable.getNameRead()})") + self.add_reg_str(f"\t{vec.getNameRead()} <= {vec.getNameWrite()};") + else: + self.add_reg_str(f"{vec.getNameRead()} <= {vec.getNameWrite()};") + + if in_else: + self.decrease_indent() + self.add_reg_str("end") + self.decrease_indent() + + def logicarray_reg_init(self, array: LogicArray, enable=None, init=None) -> None: + assert array.type == "r" + self.increase_indent() + if init is None: + init = [0] * array.length + in_else = False + if init != None: + self.add_reg_str(f"if ({self.reset_name}) begin") + for i in range(0, array.length): + self.add_reg_str( + f"\t{array.getNameRead(i)} <= {self.int_to_str(init[i])};" + ) + self.add_reg_str("end") + self.add_reg_str("else begin") + in_else = True + self.increase_indent() + + if enable != None: + for i in range(0, array.length): + self.add_reg_str(f"if ({enable.getNameRead(i)})") + self.add_reg_str( + f"\t{array.getNameRead(i)} <= {array.getNameWrite(i)};" + ) + else: + for i in range(0, array.length): + self.add_reg_str(f"{array.getNameRead(i)} <= {array.getNameWrite(i)};") + + if in_else: + self.decrease_indent() + self.add_reg_str("end") + self.decrease_indent() + + def logicvecarray_reg_init( + self, array: LogicVecArray, enable=None, init=None + ) -> None: + assert array.type == "r" + self.increase_indent() + if init is None: + init = [0] * array.length + in_else = False + if init != None: + self.add_reg_str(f"if ({self.reset_name}) begin") + for i in range(0, array.length): + self.add_reg_str( + f"\t{array.getNameRead(i)} <= {self.int_to_str(init[i], array.size)};" + ) + self.add_reg_str("end") + self.add_reg_str("else begin") + in_else = True + self.increase_indent() + + if enable != None: + for i in range(0, array.length): + self.add_reg_str(f"if ({enable.getNameRead(i)})") + self.add_reg_str( + f"\t{array.getNameRead(i)} <= {array.getNameWrite(i)};" + ) + else: + for i in range(0, array.length): + self.add_reg_str(f"{array.getNameRead(i)} <= {array.getNameWrite(i)};") + + if in_else: + self.decrease_indent() + self.add_reg_str("end") + self.decrease_indent() + + def get_file_suffix(self) -> str: + return "v" + + def index_var(self, var_name, index): + return f"{var_name}[{index}]" + + def slice_var(self, var_name, high, low): + return f"{var_name}[{high}:{low}]" + + @staticmethod + def int_to_str(din, size=None, meta=None) -> str: + if size == None: + size = 1 + + return f"{size}'b{Emitter._int_to_bin(din, size)}" + + @staticmethod + def mask_less(din, size) -> str: + """ + Example: + MaskLess(3, 5) # Output: "00111" + MaskLess(2, 6) # Output: "000011" + MaskLess(5, 5) # Output: "11111" + MaskLess(0, 4) # Output: "0000" + """ + if din > size: + raise ValueError("Unknown value!") + return f"{size}'b" + "0" * (size - din) + "1" * din + + @staticmethod + def new() -> Emitter: + return VerilogEmitter() + + def mux_index(self, din, sel) -> str: + """ + Generate a Verilog array-index expression for selecting an element + """ + return f"{din.getNameRead()}[{sel.getNameRead()}]" + + def add_custom_statement(self, custom_statement): + for line in custom_statement.verilog_str.splitlines(): + self.statementString += self.get_current_indent() + line + "\n" diff --git a/tools/backend/lsq-generator-python/core_gen/emitters/vhdl_emitter.py b/tools/backend/lsq-generator-python/core_gen/emitters/vhdl_emitter.py new file mode 100644 index 0000000000..0882adcef7 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/emitters/vhdl_emitter.py @@ -0,0 +1,454 @@ +from core_gen.emitters.emitter import Emitter, Meta +from core_gen.ir import Statement, Bin, Un, BinOp, UnOp, Bit, WhenElse, Type +from core_gen.signals import Logic, LogicVec, LogicArray, LogicVecArray + + +# ===----------------------------------------------------------------------===# +# Global Parameter Initialization +# ===----------------------------------------------------------------------===# +class VHDLEmitter(Emitter): + """ + A context object to replace global variables for VHDL code generation. + Holds indentation level, temporary name counter, and initialization strings. + """ + + def __init__(self, reset_name="rst", clock_name="clk"): + # Initialize common emitter fields + super().__init__(clock_name=clock_name, reset_name=reset_name) + + # Default library imports for VHDL + self.library = ( + "library IEEE;\nuse IEEE.std_logic_1164.all;\nuse IEEE.numeric_std.all;\n\n" + ) + + def get_port_init_str(self) -> str: + return f"port(\n\t\t{self.reset_name} : in std_logic;\n\t\t{self.clock_name} : in std_logic" + + def get_port_end_str(self) -> str: + return f"\n{self.get_current_indent()});" + + def get_reg_init_str(self) -> str: + return f"process ({self.clock_name}, {self.reset_name}) is\n{self.get_current_indent()}begin\n" + + def get_reg_end_str(self) -> str: + return f"end process;\n" + + def add_reg_str(self, code: str): + self.regInitString += code + + def add_comment(self, comment: str): + for line in comment.split("\n"): + self.statementString += self.get_current_indent() + f"-- {line}\n" + + def add_assignment(self, out, statement: Statement, in_process=False): + out_str, size = self.assigned_var_to_str(out) + meta = Meta(size, Type.LOGIC, -1) + statement_str = statement.to_str(self, meta) + # Assume we only write to logic types + statement_str = self.fix_type(Type.LOGIC, statement.get_type(), statement_str) + self.statementString += ( + self.get_current_indent() + f"{out_str} <= {statement_str};\n" + ) + + def get_definition_str(self, module_name: str, write_regs=True) -> str: + return ( + self.library + + f"entity {module_name} is\n" + + self.get_current_indent() + + self.get_port_init_str() + + self.portInitString + + self.get_port_end_str() + + "\nend entity;\n\n" + + f"architecture arch of {module_name} is\n" + + self._BOOL_TO_LOGIC_FUNC + + self.signalInitString + + "begin\n" + + self.statementString + + "\n" + + ( + ( + self.get_current_indent() + + self.get_reg_init_str() + + self.regInitString + + self.get_reg_end_str() + ) + if write_regs and self.regInitString != "" + else "" + ) + + "end architecture;\n" + ) + + def start_instantiation(self, module_name: str, instance_name: str = None) -> str: + if self.inst_started: # Sanity check to prevent overlapping instantiations + raise ValueError( + "start_instantiation called while another instantiation is in progress" + ) + + if instance_name is None: + instance_name = module_name + + self.inst_started = True + self.first_map = True + self.inst_str = ( + f"{self.get_current_indent()}{instance_name} : entity work.{module_name}\n" + ) + self.increase_indent() + self.inst_str += f"{self.get_current_indent()}port map(" + self.increase_indent() + + def add_map(self, port_name: str, signal_name: str = "open") -> str: + if not self.inst_started: + raise ValueError("add_map can only be called after start_instantiation") + + assert isinstance(port_name, str) and isinstance( + signal_name, str + ), "port name and signal name must be strings" + + if not self.first_map: + self.inst_str += "," + else: + self.first_map = False + + self.inst_str += f"\n{self.get_current_indent()}{port_name} => {signal_name}" + + def complete_instantiation(self) -> str: + self.inst_started = False + self.decrease_indent() + self.inst_str += f"\n{self.get_current_indent()});\n" + self.decrease_indent() + self.statementString += self.inst_str + self.inst_str = "" + + _BOOL_TO_LOGIC_FUNC = ( + "\tfunction to_sl(b : boolean) return std_logic is\n" + "\tbegin\n" + "\t\tif b then return '1'; else return '0'; end if;\n" + "\tend function;\n" + ) + + BINOP_STRINGS = { + BinOp.ADD: "+", + BinOp.SUB: "-", + BinOp.AND: "and", + BinOp.OR: "or", + BinOp.XOR: "xor", + BinOp.MUL: "*", + BinOp.GE: ">=", + BinOp.LE: "<=", + BinOp.GT: ">", + BinOp.LT: "<", + BinOp.EQ: "=", + BinOp.NEQ: "/=", + BinOp.CONCAT: "&", + } + + @staticmethod + def is_surrounded_by_parentheses(s: str) -> bool: + s = s.strip() + if not s.startswith("(") or not s.endswith(")"): + return False + + depth = 0 + for i, ch in enumerate(s): + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + if depth == 0: + return i == len(s) - 1 + if depth < 0: + return False + return False + + def get_binop_str(self, op: Bin) -> str: + if op in self.BINOP_STRINGS: + return self.BINOP_STRINGS[op] + else: + raise ValueError("Invalid binary operator: " + str(op)) + + def get_unop_str(self, unop: UnOp) -> str: + if unop == UnOp.NOT: + return "not" + else: + raise ValueError("Invalid unary operator") + + def get_bit_str(self, bit: Bit) -> str: + if bit.value == 0: + return "'0'" + elif bit.value == 1: + return "'1'" + else: + raise ValueError("Invalid bit value") + + def fix_type(self, super_type: Type, child_type: Type, child_str: str) -> str: + if super_type == Type.ARITH and child_type == Type.LOGIC: + return ( + f"unsigned{child_str}" + if self.is_surrounded_by_parentheses(child_str) + else f"unsigned({child_str})" + ) + elif super_type == Type.LOGIC and child_type == Type.ARITH: + return ( + f"std_logic_vector{child_str}" + if self.is_surrounded_by_parentheses(child_str) + else f"std_logic_vector({child_str})" + ) + elif super_type == Type.LOGIC and child_type == Type.BOOL: + return f"to_sl({child_str})" + else: + return child_str + + def bin_to_str(self, bin: Bin, meta: Meta) -> str: + meta = Meta(meta.size, bin.get_param_type(), bin.get_precedence()) + left_str = bin.left.to_str(self, meta) + right_str = bin.right.to_str(self, meta) + + left_str = self.fix_type(bin.get_param_type(), bin.left.get_type(), left_str) + right_str = self.fix_type(bin.get_param_type(), bin.right.get_type(), right_str) + + return f"{left_str} {self.get_binop_str(bin.op)} {right_str}" + + def un_to_str(self, un: Un, meta: Meta) -> str: + meta = Meta(meta.size, un.get_param_type(), un.get_precedence()) + val_str = un.val.to_str(self, meta) + val_str = self.fix_type(un.get_param_type(), un.val.get_type(), val_str) + return f"{self.get_unop_str(un.op)} {val_str}" + + def when_else_to_str(self, when_else, meta: Meta) -> str: + + # add a linebreak if the "else" statement is a when-else themselves + if isinstance(when_else.false_statement, WhenElse): + """ + a bit of a hack to force the inner when-else to not add parenthesis and an enter when chained + So the resulting when else will look like: + [val 1] when [cond 1] else + [val 2] when [cond 2] else + [val 3] when [cond 3] else + ... + """ + + enter = f"\n{self.get_current_indent()}\t" + self_precedence = -1 + else: + enter = " " + self_precedence = 0 + + meta = Meta(meta.size, when_else.get_type(), when_else.get_precedence()) + true_str = when_else.true_statement.to_str(self, meta) + false_str = when_else.false_statement.to_str( + self, Meta(meta.size, meta.type, self_precedence) + ) + cond_str = when_else.condition.to_str(self, meta) + + cond_str = self.fix_type(Type.BOOL, when_else.condition.get_type(), cond_str) + + return f"{true_str} when {cond_str} else{enter}{false_str}" + + def logic_signal_init(self, signal: Logic, sufix: str): + """ + Appends the appropriate declaration or port line for this signal to a global buffer. + """ + if signal.type == "w": + self.add_signal_str( + f"\tsignal {signal.get_base_name(sufix)} : std_logic;\n" + ) + elif signal.type == "r": + self.add_signal_str( + f"\tsignal {signal.get_base_name(sufix)}_d : std_logic;\n" + ) + self.add_signal_str( + f"\tsignal {signal.get_base_name(sufix)}_q : std_logic;\n" + ) + elif signal.type == "i": + self.add_port_str(";\n") + self.add_port_str( + f'\t\t{signal.get_base_name(sufix)}{"_i" if not signal.dyn_comp else ""} : in std_logic' + ) + elif signal.type == "o": + self.add_port_str(";\n") + self.add_port_str( + f'\t\t{signal.get_base_name(sufix)}{"_o" if not signal.dyn_comp else ""} : out std_logic' + ) + + def logicvec_signal_init(self, vec: LogicVec, sufix: str): + if vec.type == "w": + self.add_signal_str( + f"\tsignal {vec.get_base_name(sufix)} : std_logic_vector({vec.size-1} downto 0);\n" + ) + elif vec.type == "r": + self.add_signal_str( + f"\tsignal {vec.get_base_name(sufix)}_d : std_logic_vector({vec.size-1} downto 0);\n" + ) + self.add_signal_str( + f"\tsignal {vec.get_base_name(sufix)}_q : std_logic_vector({vec.size-1} downto 0);\n" + ) + elif vec.type == "i": + self.add_port_str(";\n") + self.add_port_str( + f'\t\t{vec.get_base_name(sufix)}{'_i' if not vec.dyn_comp else ""} : in std_logic_vector({vec.size-1} downto 0)' + ) + elif vec.type == "o": + self.add_port_str(";\n") + self.add_port_str( + f'\t\t{vec.get_base_name(sufix)}{'_o' if not vec.dyn_comp else ""} : out std_logic_vector({vec.size-1} downto 0)' + ) + + def logic_reg_init(self, logic: Logic, enable=None, init=None) -> None: + """ + Generates a clocked process snippet that sets up the register's behavior. + For example, + + if (rst = '1') then + _q <= '0'; + elsif (rising_edge(clk)) then + _q <= _d; + end if; + """ + assert logic.type == "r" + if init is None: + init = 0 + if init != None: + self.add_reg_str(f"\t\tif ({self.reset_name} = '1') then\n") + self.add_reg_str( + f"\t\t\t{logic.getNameRead()} <= {self.in_to_bits(init)};\n" + ) + self.add_reg_str(f"\t\telsif (rising_edge({self.clock_name})) then\n") + else: + self.add_reg_str(f"\t\tif (rising_edge({self.clock_name})) then\n") + if enable != None: + self.add_reg_str(f"\t\t\tif ({enable.getNameRead()} = '1') then\n") + self.add_reg_str( + f"\t\t\t\t{logic.getNameRead()} <= {logic.getNameWrite()};\n" + ) + self.add_reg_str("\t\t\tend if;\n") + else: + self.add_reg_str( + f"\t\t\t{logic.getNameRead()} <= {logic.getNameWrite()};\n" + ) + self.add_reg_str("\t\tend if;\n") + + def logicvec_reg_init(self, vec: LogicVec, enable=None, init=None) -> None: + assert vec.type == "r" + if init is None: + init = 0 + if init != None: + self.add_reg_str(f"\t\tif ({self.reset_name} = '1') then\n") + self.add_reg_str( + f"\t\t\t{vec.getNameRead()} <= {self.int_to_str(init, vec.size)};\n" + ) + self.add_reg_str(f"\t\telsif (rising_edge({self.clock_name})) then\n") + else: + self.add_reg_str(f"\t\tif (rising_edge({self.clock_name})) then\n") + if enable != None: + self.add_reg_str(f"\t\t\tif ({enable.getNameRead()} = '1') then\n") + self.add_reg_str(f"\t\t\t\t{vec.getNameRead()} <= {vec.getNameWrite()};\n") + self.add_reg_str("\t\t\tend if;\n") + else: + self.add_reg_str(f"\t\t\t{vec.getNameRead()} <= {vec.getNameWrite()};\n") + self.add_reg_str("\t\tend if;\n") + + def logicarray_reg_init(self, array: LogicArray, enable=None, init=None) -> None: + assert array.type == "r" + if init is None: + init = [0] * array.length + if init != None: + self.add_reg_str(f"\t\tif ({self.reset_name} = '1') then\n") + for i in range(0, array.length): + self.add_reg_str( + f"\t\t\t{array.getNameRead(i)} <= {self.int_to_str(init[i])};\n" + ) + self.add_reg_str(f"\t\telsif (rising_edge({self.clock_name})) then\n") + else: + self.add_reg_str(f"\t\tif (rising_edge({self.clock_name})) then\n") + if enable != None: + for i in range(0, array.length): + self.add_reg_str(f"\t\t\tif ({enable.getNameRead(i)} = '1') then\n") + self.add_reg_str( + f"\t\t\t\t{array.getNameRead(i)} <= {array.getNameWrite(i)};\n" + ) + self.add_reg_str("\t\t\tend if;\n") + else: + for i in range(0, array.length): + self.add_reg_str( + f"\t\t\t{array.getNameRead(i)} <= {array.getNameWrite(i)};\n" + ) + self.add_reg_str("\t\tend if;\n") + + def logicvecarray_reg_init( + self, array: LogicVecArray, enable=None, init=None + ) -> None: + assert array.type == "r" + if init is None: + init = [0] * array.length + if init != None: + self.add_reg_str(f"\t\tif ({self.reset_name} = '1') then\n") + for i in range(0, array.length): + self.add_reg_str( + f"\t\t\t{array.getNameRead(i)} <= {self.int_to_str(init[i], array.size)};\n" + ) + self.add_reg_str(f"\t\telsif (rising_edge({self.clock_name})) then\n") + else: + self.add_reg_str(f"\t\tif (rising_edge({self.clock_name})) then\n") + if enable != None: + for i in range(0, array.length): + self.add_reg_str(f"\t\t\tif ({enable.getNameRead(i)} = '1') then\n") + self.add_reg_str( + f"\t\t\t\t{array.getNameRead(i)} <= {array.getNameWrite(i)};\n" + ) + self.add_reg_str("\t\t\tend if;\n") + else: + for i in range(0, array.length): + self.add_reg_str( + f"\t\t\t{array.getNameRead(i)} <= {array.getNameWrite(i)};\n" + ) + self.add_reg_str("\t\tend if;\n") + + def get_file_suffix(self) -> str: + return "vhd" + + def index_var(self, var_name, index): + return f"{var_name}({index})" + + def slice_var(self, var_name, high, low): + return f"{var_name}({high} downto {low})" + + @staticmethod + def int_to_str(din: int, size=None, meta=None) -> str: + if meta is not None and meta.type == Type.ARITH: + return str(din) + + if size == None: + if din: + return "'1'" + else: + return "'0'" + else: + return f'"{Emitter._int_to_bin(din, size)}"' + + @staticmethod + def mask_less(din, size) -> str: + """ + Example: + MaskLess(3, 5) # Output: "00111" + MaskLess(2, 6) # Output: "000011" + MaskLess(5, 5) # Output: "11111" + MaskLess(0, 4) # Output: "0000" + """ + if din > size: + raise ValueError("Unknown value!") + return '"' + "0" * (size - din) + "1" * din + '"' + + @staticmethod + def new() -> Emitter: + return VHDLEmitter() + + @staticmethod + def mux_index(din, sel) -> str: + """ + Generate a VHDL array-index expression for selecting an element + """ + return f"{din.getNameRead()}(to_integer(unsigned({sel.getNameRead()})))" + + def add_custom_statement(self, custom_statement): + for line in custom_statement.vhdl_str.splitlines(): + self.statementString += self.get_current_indent() + line + "\n" diff --git a/tools/backend/lsq-generator-python/vhdl_gen/generators/README.md b/tools/backend/lsq-generator-python/core_gen/generators/README.md similarity index 96% rename from tools/backend/lsq-generator-python/vhdl_gen/generators/README.md rename to tools/backend/lsq-generator-python/core_gen/generators/README.md index acc1e646c3..5d631137e5 100644 --- a/tools/backend/lsq-generator-python/vhdl_gen/generators/README.md +++ b/tools/backend/lsq-generator-python/core_gen/generators/README.md @@ -1,29 +1,29 @@ -# `vhdl_gen.generators` — Folder Overview - -This folder contains all code‑generation helpers responsible for emitting -parameterized VHDL RTL used by the Load/Store Queue (LSQ) for the spatial computing and -its supporting structures. - - -## Quick Map - -| Module | Role in the pipeline | -| ------ | -------------------- | -| `dispatchers.py` | Generates Port-to-Queue/Queue-To-Port Dispatchers | -| `group_allocator.py` | Generates Group Allocator | -| `lsq.py` | Top‑level generator that produces the **complete LSQ RTL** and plugs in dispatchers + allocator | - -## Modules -- `dispatchers.py` - - `PortToQueueDispatcher`: Generates the `entity` and `architecture` for port-to-queue dispatchers (load/store address & store data ports) - - `QueueToPortDispatcher`: Generates the `entity` and `architecture` for queue-to-port dispatchers (load data & store-back ports). - - `PortToQueueDispatcherInst`: Produces the VHDL `port map` string to instantiate a Port-to-Queue dispatcher, connecting top-level signal (reset, clock, entries, and ports) to the dispatcher entity. - - `QueueToPortDispatcherInst`: Produces the VHDL `port map` string to instantiate a Queue-to-Port dispatcher, connecting top-level signal (reset, clock, entries, and ports) to the dispatcher entity. - - -- `group_allocator.py` - - `GroupAllocator`: Generates the `entity` and `architecture` for the group allocator, managing handshake between load and store groups based on free entry counts and load-store ordering. - - `GroupAllocatorInst`: Produces the VHDL `port map` string to instantiate the Group Allocator, connecting top-level signal to the group allocator entity. - -- `lsq.py` +# `core_gen.generators` — Folder Overview + +This folder contains all code‑generation helpers responsible for emitting +parameterized VHDL RTL used by the Load/Store Queue (LSQ) for the spatial computing and +its supporting structures. + + +## Quick Map + +| Module | Role in the pipeline | +| ------ | -------------------- | +| `dispatchers.py` | Generates Port-to-Queue/Queue-To-Port Dispatchers | +| `group_allocator.py` | Generates Group Allocator | +| `lsq.py` | Top‑level generator that produces the **complete LSQ RTL** and plugs in dispatchers + allocator | + +## Modules +- `dispatchers.py` + - `PortToQueueDispatcher`: Generates the `entity` and `architecture` for port-to-queue dispatchers (load/store address & store data ports) + - `QueueToPortDispatcher`: Generates the `entity` and `architecture` for queue-to-port dispatchers (load data & store-back ports). + - `PortToQueueDispatcherInst`: Produces the VHDL `port map` string to instantiate a Port-to-Queue dispatcher, connecting top-level signal (reset, clock, entries, and ports) to the dispatcher entity. + - `QueueToPortDispatcherInst`: Produces the VHDL `port map` string to instantiate a Queue-to-Port dispatcher, connecting top-level signal (reset, clock, entries, and ports) to the dispatcher entity. + + +- `group_allocator.py` + - `GroupAllocator`: Generates the `entity` and `architecture` for the group allocator, managing handshake between load and store groups based on free entry counts and load-store ordering. + - `GroupAllocatorInst`: Produces the VHDL `port map` string to instantiate the Group Allocator, connecting top-level signal to the group allocator entity. + +- `lsq.py` - `LSQ`: Top-Level generator that emits a complete LSQ VHDL design by instantiating the GroupAllocator and various dispatchers, connecting them to memory interfaces, load/store queues, and optional features (pipelining, master interface). \ No newline at end of file diff --git a/tools/backend/lsq-generator-python/core_gen/generators/__init__.py b/tools/backend/lsq-generator-python/core_gen/generators/__init__.py new file mode 100644 index 0000000000..83be66fcfb --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/generators/__init__.py @@ -0,0 +1,11 @@ +# core_gen/generators/__init__.py +from core_gen.generators.dispatchers import PortToQueueDispatcher, QueueToPortDispatcher +from core_gen.generators.group_allocator import GroupAllocator +from core_gen.generators.lsq import LSQ + +__all__ = [ + "PortToQueueDispatcher", + "QueueToPortDispatcher", + "GroupAllocator", + "LSQ", +] diff --git a/tools/backend/lsq-generator-python/vhdl_gen/generators/dispatchers.py b/tools/backend/lsq-generator-python/core_gen/generators/dispatchers.py similarity index 56% rename from tools/backend/lsq-generator-python/vhdl_gen/generators/dispatchers.py rename to tools/backend/lsq-generator-python/core_gen/generators/dispatchers.py index 0d1ea8ad1a..b9ca758a07 100644 --- a/tools/backend/lsq-generator-python/vhdl_gen/generators/dispatchers.py +++ b/tools/backend/lsq-generator-python/core_gen/generators/dispatchers.py @@ -1,637 +1,617 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.signals import * -from vhdl_gen.operators import * - - -class PortToQueueDispatcher: - def __init__( - self, - name: str, - suffix: str, - numPorts: int, - numEntries: int, - bitsW: int, - portAddrW: int - ): - """ - Port-to-Queue (Port-to-Entry) Dispatcher - - Models a dispatcher that routes signals from multiple ports to queue entries. - - This class encapsulates the logic for generating a VHDL module that takes - arguments from a specific access port and passes them to a corresponding - queue entry. - - This generates three main parts in the LSQ module: - 1. Load Address Port Dispatcher - 2. Store Address Port Dispatcher - 3. Store Data Port Dispatcher - - Initilization Parameters: - name : Base name of the dispatcher. - suffix : Suffix appended to the entity name. - - lda: Load Address Port Dispatcher - - sta: Store Address Port Dispatcher - - std: Store Data Port Dispatcher - numPorts : Number of access ports. - numEntries : Number of queue entries. - bitsW : Width of each data/address bus. - portAddrW : Width of the port index bus. - - Instance Variable: - self.module_name = name + suffix : Entity and architecture identifier - - Example (Load Address Port Dispatcher): - ptq_dispatcher_lda = PortToQueueDispatcher( - "config_0_core", - "_lda", - configs.numLdPorts, - configs.numLdqEntries, - configs.addrW, - configs.ldpAddrW - ) - - # You can later generate VHDL entity and architecture by - # ptq_dispatcher_lda.generate(...) - # You can later instantiate VHDL entity by - # ptq_dispatcher_lda.instantiate(...) - - """ - - self.name = name - self.module_name = name + suffix - self.numPorts = numPorts - self.numEntries = numEntries - self.bitsW = bitsW - self.portAddrW = portAddrW - - def generate(self, path_rtl) -> None: - """ - Generates the VHDL 'entity' and 'architecture' sections for a dispatcher - that passes arguments from a specific access port to a corresponding queue entry. - - Parameters: - path_rtl : Output directory for VHDL files. - - Output: - Appends the 'entity' and 'architecture' definitions - to the .vhd file at /.vhd. - Entity and architecture use the identifier: - - Example (Load Address Port Dispatcher): - ptq_dispatcher_lda.generate(path_rtl="rtl") - - produces in rtl/config_0_core.vhd: - - entity config_0_core_lda is - port( - rst : in std_logic; - clk : in std_logic; - ... - ); - end entity; - - architecture arch of config_0_core_lda is - -- signals generated here - begin - -- dispatcher logic here - end architecture; - """ - - # ctx: VHDLContext for code generation state. - # When we generate VHDL entity and architecture, we can use this context as a local variable. - # We only need to get the context as a parameter when we instantiate the module. - # It saves all information we need when we generate VHDL entity and architecture code. - ctx = VHDLContext() - - ctx.tabLevel = 1 - ctx.tempCount = 0 - ctx.signalInitString = '' - ctx.portInitString = '\tport(\n\t\trst : in std_logic;\n\t\tclk : in std_logic' - arch = '' - - # IOs - port_payload_i = LogicVecArray( - ctx, 'port_payload', 'i', self.numPorts, self.bitsW) - port_valid_i = LogicArray(ctx, 'port_valid', 'i', self.numPorts) - port_ready_o = LogicArray(ctx, 'port_ready', 'o', self.numPorts) - entry_alloc_i = LogicArray(ctx, 'entry_alloc', 'i', self.numEntries) - entry_payload_valid_i = LogicArray( - ctx, 'entry_payload_valid', 'i', self.numEntries) - if (self.numPorts != 1): - entry_port_idx_i = LogicVecArray( - ctx, 'entry_port_idx', 'i', self.numEntries, self.portAddrW) - entry_payload_o = LogicVecArray( - ctx, 'entry_payload', 'o', self.numEntries, self.bitsW) - entry_wen_o = LogicArray(ctx, 'entry_wen', 'o', self.numEntries) - queue_head_oh_i = LogicVec(ctx, 'queue_head_oh', 'i', self.numEntries) - - # one-hot port index - entry_port_idx_oh = LogicVecArray( - ctx, 'entry_port_idx_oh', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - if (self.numPorts == 1): - arch += Op(ctx, entry_port_idx_oh[i], 1) - else: - arch += BitsToOH(ctx, entry_port_idx_oh[i], entry_port_idx_i[i]) - - # Mux for the data/addr - for i in range(0, self.numEntries): - arch += Mux1H(ctx, entry_payload_o[i], - port_payload_i, entry_port_idx_oh[i]) - - # Entries that request data/address from a any port - entry_ptq_ready = LogicArray( - ctx, 'entry_ptq_ready', 'w', self.numEntries) - for i in range(0, self.numEntries): - arch += Op(ctx, entry_ptq_ready[i], entry_alloc_i[i], - 'and', 'not', entry_payload_valid_i[i]) - - # Entry-port pairs that the entry request the data/address from the port - entry_waiting_for_port = LogicVecArray( - ctx, 'entry_waiting_for_port', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - arch += Op(ctx, entry_waiting_for_port[i], entry_port_idx_oh[i], - 'when', entry_ptq_ready[i], 'else', 0) - - # Reduce the matrix for each entry to get the ready signal: - # If one or more entries is requesting data/address from a certain port, ready is set high. - port_ready_vec = LogicVec(ctx, 'port_ready_vec', 'w', self.numPorts) - arch += Reduce(ctx, port_ready_vec, entry_waiting_for_port, 'or') - arch += VecToArray(ctx, port_ready_o, port_ready_vec) - - # AND the request signal with valid, it shows entry-port pairs that are both valid and ready. - entry_port_options = LogicVecArray( - ctx, 'entry_port_options', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - for j in range(0, self.numPorts): - arch += ctx.get_current_indent() + f'{entry_port_options.getNameWrite(i, j)} <= ' \ - f'{entry_waiting_for_port.getNameRead(i, j)} and {port_valid_i.getNameRead(j)};\n' - - # For each port, the oldest entry receives bit this cycle. The priority masking per port(column) - # generates entry-port pairs that will tranfer data/address this cycle. - entry_port_transfer = LogicVecArray( - ctx, 'entry_port_transfer', 'w', self.numEntries, self.numPorts) - arch += CyclicPriorityMasking(ctx, entry_port_transfer, - entry_port_options, queue_head_oh_i) - - # Reduce for each entry(row), which generates write enable signal for entries - for i in range(0, self.numEntries): - arch += Reduce(ctx, entry_wen_o[i], entry_port_transfer[i], 'or') - - ###### Write To File ###### - ctx.portInitString += '\n\t);' - - # Write to the file - with open(f'{path_rtl}/{self.name}.vhd', 'a') as file: - file.write('\n\n') - file.write(ctx.library) - file.write(f'entity {self.module_name} is\n') - file.write(ctx.portInitString) - file.write('\nend entity;\n\n') - file.write(f'architecture arch of {self.module_name} is\n') - file.write(ctx.signalInitString) - file.write('begin\n' + arch + '\n') - file.write('end architecture;\n') - - def instantiate( - self, - ctx: VHDLContext, - port_payload_i: LogicVecArray, - port_valid_i: LogicArray, - port_ready_o: LogicArray, - entry_alloc_i: LogicArray, - entry_payload_valid_i: LogicArray, - entry_port_idx_i: LogicVecArray, - entry_payload_o: LogicVecArray, - entry_wen_o: LogicArray, - queue_head_oh_i: LogicVec - ) -> str: - """ - Port-to-Queue Dispatcher Instantiation - - Creates the VHDL port mapping for the Port-to-Queue dispatcher entity. - Connects the top-level signals (reset, clock, port and entry signals) - to the internal dispatcher instance named _dispatcher. - - Parameters: - ctx : VHDLContext for code generation state. - port_payload_i : Input data or address bits from each port - port_valid_i : Valid signal for each input port (Valid data/address) - port_ready_o : Ready signal indicating the queue is ready to receive data/address - entry_alloc_i : Allocation bit for a queue entry - entry_payload_valid_i: Valid bit for the data/address of a queue entry - entry_port_idx_i : Indicates to which port the entry is assigned - entry_payload_o : Output bits written to the entry - entry_wen_o : Write enable for each entry - queue_head_oh_i : One-hot vector indicating the current head index of the queue. - - Returns: - VHDL instantiation string for inclusion in the architecture body. - - Example (Load Address Port Dispatcher): - arch += ptq_dispatcher_lda.instantiate( - ctx, - port_payload_i = ldp_addr_i, - port_valid_i = ldp_addr_valid_i, - port_ready_o = ldp_addr_ready_o, - entry_alloc_i = ldq_valid, - entry_payload_valid_i = ldq_addr_valid, - entry_port_idx_i = ldq_port_idx, - entry_payload_o = ldq_addr, - entry_wen_o = ldq_addr_wen, - queue_head_oh_i = ldq_head_oh - ) - - This generates, inside 'config_0_core.vhd' and under the 'architecture config_0_core', the following instantiation - - architecture arch of config_0_core is - signal ... - begin - ... - - config_0_core_lda_dispatcher : entity work.config_0_core_lda - port map( - rst => rst, - clk => clk, - port_payload_0_i => ldp_addr_0_i, - port_payload_1_i => ldp_addr_1_i, - port_ready_0_o => ldp_addr_ready_0_o, - port_ready_1_o => ldp_addr_ready_1_o, - port_valid_0_i => ldp_addr_valid_0_i, - port_valid_1_i => ldp_addr_valid_1_i, - entry_alloc_0_i => ldq_valid_0_q, - entry_alloc_1_i => ldq_valid_1_q, - entry_payload_valid_0_i => ldq_addr_valid_0_q, - entry_payload_valid_1_i => ldq_addr_valid_1_q, - entry_port_idx_0_i => ldq_port_idx_0_q, - entry_port_idx_1_i => ldq_port_idx_1_q, - entry_payload_0_o => ldq_addr_0_d, - entry_payload_1_o => ldq_addr_1_d, - entry_wen_0_o => ldq_addr_wen_0, - entry_wen_1_o => ldq_addr_wen_1, - queue_head_oh_i => ldq_head_oh - ); - ... - end architecture; - - """ - - arch = ctx.get_current_indent( - ) + f'{self.module_name}_dispatcher : entity work.{self.module_name}\n' - ctx.tabLevel += 1 - arch += ctx.get_current_indent() + f'port map(\n' - ctx.tabLevel += 1 - arch += ctx.get_current_indent() + f'rst => rst,\n' - arch += ctx.get_current_indent() + f'clk => clk,\n' - for i in range(0, self.numPorts): - arch += ctx.get_current_indent() + \ - f'port_payload_{i}_i => {port_payload_i.getNameRead(i)},\n' - for i in range(0, self.numPorts): - arch += ctx.get_current_indent() + \ - f'port_ready_{i}_o => {port_ready_o.getNameWrite(i)},\n' - for i in range(0, self.numPorts): - arch += ctx.get_current_indent() + \ - f'port_valid_{i}_i => {port_valid_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_alloc_{i}_i => {entry_alloc_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_payload_valid_{i}_i => {entry_payload_valid_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - if (self.numPorts != 1): - arch += ctx.get_current_indent() + \ - f'entry_port_idx_{i}_i => {entry_port_idx_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_payload_{i}_o => {entry_payload_o.getNameWrite(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_wen_{i}_o => {entry_wen_o.getNameWrite(i)},\n' - arch += ctx.get_current_indent() + \ - f'queue_head_oh_i => {queue_head_oh_i.getNameRead()}\n' - ctx.tabLevel -= 1 - arch += ctx.get_current_indent() + f');\n' - ctx.tabLevel -= 1 - return arch - - -class QueueToPortDispatcher: - def __init__( - self, - name: str, - suffix: str, - numPorts: int, - numEntries: int, - bitsW: int, - portAddrW: int - ): - """ - Queue-to-Port (Entry-to-Port) Dispatcher - - Models a dispatcher that routes signals from queue entries to access ports. - - This class encapsulates the logic for generating a VHDL module that takes - data from queue entries and routes it to the correct outgoing port based on - priority. - - This generates one main part in the LSQ module: - 1. Load Data Port Dispatcher - 2. (Optionally) Store Backward Port Dispatcher - - Initialization Parameters: - name : Base name of the dispatcher. - suffix : Suffix appended to the entity name. - numPorts : Number of access ports. - numEntries : Number of queue entries. - bitsW : Width of each data bus. - portAddrW : Width of the port index bus. - - Instance Variable: - self.module_name = name + suffix : Entity and architecture identifier - - Example (Load Data Port Dispatcher): - qtp_dispatcher_ldd = QueueToPortDispatcher( - name="config_0_core", - suffix="_ldd", - numPorts=configs.numLdPorts, - numEntries=configs.numLdqEntries, - bitsW=configs.addrW, - portAddrW=configs.ldpAddrW - ) - - # You can later generate VHDL entity and architecture by - # qtp_dispatcher_ldd.generate(...) - # You can later instantiate VHDL entity by - # qtp_dispatcher_ldd.instantiate(...) - """ - - self.name = name - self.module_name = name + suffix - - self.numPorts = numPorts - self.numEntries = numEntries - self.bitsW = bitsW - self.portAddrW = portAddrW - - def generate(self, path_rtl) -> None: - """ - Queue-to-Port (Entry-to-Port) Dispatcher - - Generates the VHDL 'entity' and 'architecture' sections for a dispatcher - that routes data from queue entries to their access ports. - - Parameters: - path_rtl : Output directory for VHDL files. - - Output: - Appends the 'entity' and 'architecture' definitions - to the .vhd file at /_core.vhd. - Entity and architecture use the identifier: - - Example (Load Data Port Dispatcher): - qtp_dispatcher_ldd.generate(path_rtl="rtl") - - produces in rtl/config_0_core.vhd: - - entity config_0_core_ldd is - port( - rst : in std_logic; - clk : in std_logic; - ... - ); - end entity; - - architecture arch of config_0_core_ldd is - -- signals generated here - begin - -- dispatcher logic here - end architecture; - - """ - - # ctx: VHDLContext for code generation state. - # When we generate VHDL entity and architecture, we can use this context as a local variable. - # We only need to get the context as a parameter when we instantiate the module. - # It saves all information we need when we generate VHDL entity and architecture code. - ctx = VHDLContext() - - ctx.tabLevel = 1 - ctx.tempCount = 0 - ctx.signalInitString = '' - ctx.portInitString = '\tport(\n\t\trst : in std_logic;\n\t\tclk : in std_logic' - arch = '' - - # IOs - if (self.bitsW != 0): - port_payload_o = LogicVecArray( - ctx, 'port_payload', 'o', self.numPorts, self.bitsW) - port_valid_o = LogicArray(ctx, 'port_valid', 'o', self.numPorts) - port_ready_i = LogicArray(ctx, 'port_ready', 'i', self.numPorts) - entry_alloc_i = LogicArray(ctx, 'entry_alloc', 'i', self.numEntries) - entry_payload_valid_i = LogicArray( - ctx, 'entry_payload_valid', 'i', self.numEntries) - if (self.numPorts != 1): - entry_port_idx_i = LogicVecArray( - ctx, 'entry_port_idx', 'i', self.numEntries, self.portAddrW) - if (self.bitsW != 0): - entry_payload_i = LogicVecArray( - ctx, 'entry_payload', 'i', self.numEntries, self.bitsW) - entry_reset_o = LogicArray(ctx, 'entry_reset', 'o', self.numEntries) - queue_head_oh_i = LogicVec(ctx, 'queue_head_oh', 'i', self.numEntries) - - # one-hot port index - entry_port_idx_oh = LogicVecArray( - ctx, 'entry_port_idx_oh', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - if (self.numPorts == 1): - arch += Op(ctx, entry_port_idx_oh[i], 1) - else: - arch += BitsToOH(ctx, entry_port_idx_oh[i], entry_port_idx_i[i]) - - # This matrix shows entry-port pairs that the entry is linked with the port - entry_allocated_for_port = LogicVecArray( - ctx, 'entry_allocated_for_port', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - arch += Op(ctx, entry_allocated_for_port[i], entry_port_idx_oh[i], - 'when', entry_alloc_i[i], 'else', 0) - - # For each port, the oldest entry send bits this cycle. The priority masking per port(column) - # generates entry-port pairs that will tranfer data/address this cycle. - # It is also used as one-hot select signal for data Mux. - oldest_entry_allocated_per_port = LogicVecArray( - ctx, 'oldest_entry_allocated_per_port', 'w', self.numEntries, self.numPorts) - arch += CyclicPriorityMasking(ctx, oldest_entry_allocated_per_port, - entry_allocated_for_port, queue_head_oh_i) - - if (self.bitsW != 0): - for j in range(0, self.numPorts): - arch += Mux1H(ctx, port_payload_o[j], - entry_payload_i, oldest_entry_allocated_per_port, j) - - # Mask the matrix with dataValid - entry_waiting_for_port_valid = LogicVecArray( - ctx, 'entry_waiting_for_port_valid', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - arch += Op(ctx, entry_waiting_for_port_valid[i], oldest_entry_allocated_per_port[i], - 'when', entry_payload_valid_i[i], 'else', 0) - - # Reduce the matrix for each port to get the valid signal: - # If an entry is providing data/address from a certain port, valid is set high. - port_valid_vec = LogicVec(ctx, 'port_valid_vec', 'w', self.numPorts) - arch += Reduce(ctx, port_valid_vec, entry_waiting_for_port_valid, 'or') - arch += VecToArray(ctx, port_valid_o, port_valid_vec) - - # AND the request signal with ready, it shows entry-port pairs that are both valid and ready. - entry_port_transfer = LogicVecArray( - ctx, 'entry_port_transfer', 'w', self.numEntries, self.numPorts) - for i in range(0, self.numEntries): - for j in range(0, self.numPorts): - arch += ctx.get_current_indent() + f'{entry_port_transfer.getNameWrite(i, j)} <= ' \ - f'{entry_waiting_for_port_valid.getNameRead(i, j)} and {port_ready_i.getNameRead(j)};\n' - - # Reduce for each entry(row), which generates reset signal for entries - for i in range(0, self.numEntries): - arch += Reduce(ctx, entry_reset_o[i], entry_port_transfer[i], 'or') - - ###### Write To File ###### - ctx.portInitString += '\n\t);' - - # Write to the file - with open(f'{path_rtl}/{self.name}.vhd', 'a') as file: - file.write('\n\n') - file.write(ctx.library) - file.write(f'entity {self.module_name} is\n') - file.write(ctx.portInitString) - file.write('\nend entity;\n\n') - file.write(f'architecture arch of {self.module_name} is\n') - file.write(ctx.signalInitString) - file.write('begin\n' + arch + '\n') - file.write('end architecture;\n') - - def instantiate( - self, - ctx: VHDLContext, - port_payload_o: LogicVecArray, - port_valid_o: LogicArray, - port_ready_i: LogicArray, - entry_alloc_i: LogicArray, - entry_payload_valid_i: LogicArray, - entry_port_idx_i: LogicVecArray, - entry_payload_i: LogicVecArray, - entry_reset_o: LogicArray, - queue_head_oh_i: LogicVec - ) -> str: - """ - Queue-to-Port Dispatcher Instantiation - - Creates the VHDL port mapping for the Queue-to-Port dispatcher entity. - Connects the top-level signals (reset, clock, entry and port signals) - to the internal dispatcher instance named _dispatcher. - - Parameters: - ctx : VHDLContext for code generation state. - port_payload_o : Output data bits from each queue entry - port_valid_o : Valid signal for each input port (Valid data) - port_ready_i : Ready signal indicating the queue is ready to send data - entry_alloc_i : Valid bit for a queue entry - entry_payload_valid_i : Valid bit for the contents of a queue entry - entry_port_idx_i : Indicates to which port the entry is assigned - entry_payload_i : Input data bits which is written in the queue entry - entry_reset_o : Array of reset outputs for entries. - queue_head_oh_i : One-hot vector indicating the current head index of the queue. - - Returns: - VHDL instantiation string for inclusion in the architecture body. - - - Example: - # Base architecture: 'config_0_core' - # suffix for Load Data Dispatcher instantiation: '_ldd' - - arch += qtp_dispatcher_ldd.instantiate( - ctx, - port_payload_o = ldp_data_o, - port_valid_o = ldp_data_valid_o, - port_ready_i = ldp_data_ready_i, - entry_alloc_i = ldq_valid, - entry_payload_valid_i = ldq_data_valid, - entry_port_idx_i = ldq_port_idx, - entry_payload_i = ldq_data, - entry_reset_o = ldq_reset, - queue_head_oh_i = ldq_head_oh - ) - - This generates, inside 'config_0_core.vhd' and under the 'architecture config_0_core', the following instantiation - - architecture arch of config_0_core is - signal ... - begin - ... - config_0_core_ldd_dispatcher : entity work.config_0_core_ldd - port map( - rst => rst, - clk => clk, - port_payload_0_o => ldp_data_0_o, - port_payload_1_o => ldp_data_1_o, - port_ready_0_i => ldp_data_ready_0_i, - port_ready_1_i => ldp_data_ready_1_i, - port_valid_0_o => ldp_data_valid_0_o, - port_valid_1_o => ldp_data_valid_1_o, - entry_alloc_0_i => ldq_valid_0_q, - entry_alloc_1_i => ldq_valid_1_q, - entry_payload_valid_0_i => ldq_data_valid_0_q, - entry_payload_valid_1_i => ldq_data_valid_1_q, - entry_port_idx_0_i => ldq_port_idx_0_q, - entry_port_idx_1_i => ldq_port_idx_1_q, - entry_payload_0_i => ldq_data_0_q, - entry_payload_1_i => ldq_data_1_q, - entry_reset_0_o => ldq_reset_0, - entry_reset_1_o => ldq_reset_1, - queue_head_oh_i => ldq_head_oh - ); - ... - end architecture; - """ - - arch = ctx.get_current_indent( - ) + f'{self.module_name}_dispatcher : entity work.{self.module_name}\n' - ctx.tabLevel += 1 - arch += ctx.get_current_indent() + f'port map(\n' - ctx.tabLevel += 1 - arch += ctx.get_current_indent() + f'rst => rst,\n' - arch += ctx.get_current_indent() + f'clk => clk,\n' - for i in range(0, self.numPorts): - if (port_payload_o != None): - arch += ctx.get_current_indent() + \ - f'port_payload_{i}_o => {port_payload_o.getNameWrite(i)},\n' - for i in range(0, self.numPorts): - arch += ctx.get_current_indent() + \ - f'port_ready_{i}_i => {port_ready_i.getNameRead(i)},\n' - for i in range(0, self.numPorts): - arch += ctx.get_current_indent() + \ - f'port_valid_{i}_o => {port_valid_o.getNameWrite(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_alloc_{i}_i => {entry_alloc_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_payload_valid_{i}_i => {entry_payload_valid_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - if (self.numPorts != 1): - arch += ctx.get_current_indent() + \ - f'entry_port_idx_{i}_i => {entry_port_idx_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - if (entry_payload_i != None): - arch += ctx.get_current_indent() + \ - f'entry_payload_{i}_i => {entry_payload_i.getNameRead(i)},\n' - for i in range(0, self.numEntries): - arch += ctx.get_current_indent() + \ - f'entry_reset_{i}_o => {entry_reset_o.getNameWrite(i)},\n' - arch += ctx.get_current_indent() + \ - f'queue_head_oh_i => {queue_head_oh_i.getNameRead()}\n' - ctx.tabLevel -= 1 - arch += ctx.get_current_indent() + f');\n' - ctx.tabLevel -= 1 - return arch +from core_gen.signals import LogicArray, LogicVec, LogicVecArray +from core_gen.ir import BinOp, Val, Bit +from core_gen.emitters import Emitter +from core_gen.operators import ( + BitsToOH, + Mux1H, + Reduce, + VecToArray, + CyclicPriorityMasking, +) + + +class PortToQueueDispatcher: + def __init__( + self, + name: str, + suffix: str, + numPorts: int, + numEntries: int, + bitsW: int, + portAddrW: int, + ): + """ + Port-to-Queue (Port-to-Entry) Dispatcher + + Models a dispatcher that routes signals from multiple ports to queue entries. + + This class encapsulates the logic for generating a VHDL module that takes + arguments from a specific access port and passes them to a corresponding + queue entry. + + This generates three main parts in the LSQ module: + 1. Load Address Port Dispatcher + 2. Store Address Port Dispatcher + 3. Store Data Port Dispatcher + + Initilization Parameters: + name : Base name of the dispatcher. + suffix : Suffix appended to the entity name. + - lda: Load Address Port Dispatcher + - sta: Store Address Port Dispatcher + - std: Store Data Port Dispatcher + numPorts : Number of access ports. + numEntries : Number of queue entries. + bitsW : Width of each data/address bus. + portAddrW : Width of the port index bus. + + Instance Variable: + self.module_name = name + suffix : Entity and architecture identifier + + Example (Load Address Port Dispatcher): + ptq_dispatcher_lda = PortToQueueDispatcher( + "config_0_core", + "_lda", + configs.numLdPorts, + configs.numLdqEntries, + configs.addrW, + configs.ldpAddrW + ) + + # You can later generate VHDL entity and architecture by + # ptq_dispatcher_lda.generate(...) + # You can later instantiate VHDL entity by + # ptq_dispatcher_lda.instantiate(...) + + """ + + self.name = name + self.module_name = name + suffix + self.numPorts = numPorts + self.numEntries = numEntries + self.bitsW = bitsW + self.portAddrW = portAddrW + + def generate(self, em: Emitter, path_rtl) -> None: + """ + Generates the VHDL 'entity' and 'architecture' sections for a dispatcher + that passes arguments from a specific access port to a corresponding queue entry. + + Parameters: + em : The emitter used to generate the code + path_rtl : Output directory for VHDL files. + + Output: + Appends the 'entity' and 'architecture' definitions + to the .vhd file at /.vhd. + Entity and architecture use the identifier: + + Example (Load Address Port Dispatcher): + ptq_dispatcher_lda.generate(path_rtl="rtl") + + produces in rtl/config_0_core.vhd: + + entity config_0_core_lda is + port( + rst : in std_logic; + clk : in std_logic; + ... + ); + end entity; + + architecture arch of config_0_core_lda is + -- signals generated here + begin + -- dispatcher logic here + end architecture; + """ + + # IOs + port_payload_i = LogicVecArray( + em, "port_payload", "i", self.numPorts, self.bitsW + ) + port_valid_i = LogicArray(em, "port_valid", "i", self.numPorts) + port_ready_o = LogicArray(em, "port_ready", "o", self.numPorts) + entry_alloc_i = LogicArray(em, "entry_alloc", "i", self.numEntries) + entry_payload_valid_i = LogicArray( + em, "entry_payload_valid", "i", self.numEntries + ) + if self.numPorts != 1: + entry_port_idx_i = LogicVecArray( + em, "entry_port_idx", "i", self.numEntries, self.portAddrW + ) + entry_payload_o = LogicVecArray( + em, "entry_payload", "o", self.numEntries, self.bitsW + ) + entry_wen_o = LogicArray(em, "entry_wen", "o", self.numEntries) + queue_head_oh_i = LogicVec(em, "queue_head_oh", "i", self.numEntries) + + # one-hot port index + entry_port_idx_oh = LogicVecArray( + em, "entry_port_idx_oh", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + if self.numPorts == 1: + em.add_assignment(entry_port_idx_oh[i], Val(1)) + else: + BitsToOH(em, entry_port_idx_oh[i], entry_port_idx_i[i]) + + # Mux for the data/addr + for i in range(0, self.numEntries): + Mux1H(em, entry_payload_o[i], port_payload_i, entry_port_idx_oh[i]) + + # Entries that request data/address from a any port + entry_ptq_ready = LogicArray(em, "entry_ptq_ready", "w", self.numEntries) + for i in range(0, self.numEntries): + em.add_assignment( + entry_ptq_ready[i], entry_alloc_i[i] & ~entry_payload_valid_i[i] + ) + + # Entry-port pairs that the entry request the data/address from the port + entry_waiting_for_port = LogicVecArray( + em, "entry_waiting_for_port", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + em.add_assignment( + entry_waiting_for_port[i], + entry_port_idx_oh[i].when(entry_ptq_ready[i]).else_(Val(0)), + ) + + # Reduce the matrix for each entry to get the ready signal: + # If one or more entries is requesting data/address from a certain port, ready is set high. + port_ready_vec = LogicVec(em, "port_ready_vec", "w", self.numPorts) + Reduce(em, port_ready_vec, entry_waiting_for_port, BinOp.OR) + VecToArray(em, port_ready_o, port_ready_vec) + + # AND the request signal with valid, it shows entry-port pairs that are both valid and ready. + entry_port_options = LogicVecArray( + em, "entry_port_options", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + for j in range(0, self.numPorts): + em.add_assignment( + (entry_port_options, i, j), + Val(entry_waiting_for_port, i, j) & Val(port_valid_i, j), + ) + + # For each port, the oldest entry receives bit this cycle. The priority masking per port(column) + # generates entry-port pairs that will tranfer data/address this cycle. + entry_port_transfer = LogicVecArray( + em, "entry_port_transfer", "w", self.numEntries, self.numPorts + ) + CyclicPriorityMasking( + em, entry_port_transfer, entry_port_options, queue_head_oh_i + ) + + # Reduce for each entry(row), which generates write enable signal for entries + for i in range(0, self.numEntries): + Reduce(em, entry_wen_o[i], entry_port_transfer[i], BinOp.OR) + + ###### Write To File ###### + output_str = em.get_definition_str(self.module_name) + with open(f"{path_rtl}/{self.name}.{em.get_file_suffix()}", "a") as file: + file.write(output_str) + + def instantiate( + self, + em: Emitter, + port_payload_i: LogicVecArray, + port_valid_i: LogicArray, + port_ready_o: LogicArray, + entry_alloc_i: LogicArray, + entry_payload_valid_i: LogicArray, + entry_port_idx_i: LogicVecArray, + entry_payload_o: LogicVecArray, + entry_wen_o: LogicArray, + queue_head_oh_i: LogicVec, + ) -> str: + """ + Port-to-Queue Dispatcher Instantiation + + Creates the VHDL port mapping for the Port-to-Queue dispatcher entity. + Connects the top-level signals (reset, clock, port and entry signals) + to the internal dispatcher instance named _dispatcher. + + Parameters: + em : Emitter for code generation + port_payload_i : Input data or address bits from each port + port_valid_i : Valid signal for each input port (Valid data/address) + port_ready_o : Ready signal indicating the queue is ready to receive data/address + entry_alloc_i : Allocation bit for a queue entry + entry_payload_valid_i: Valid bit for the data/address of a queue entry + entry_port_idx_i : Indicates to which port the entry is assigned + entry_payload_o : Output bits written to the entry + entry_wen_o : Write enable for each entry + queue_head_oh_i : One-hot vector indicating the current head index of the queue. + + Returns: + VHDL instantiation string for inclusion in the architecture body. + + Example (Load Address Port Dispatcher): + arch += ptq_dispatcher_lda.instantiate( + em, + port_payload_i = ldp_addr_i, + port_valid_i = ldp_addr_valid_i, + port_ready_o = ldp_addr_ready_o, + entry_alloc_i = ldq_valid, + entry_payload_valid_i = ldq_addr_valid, + entry_port_idx_i = ldq_port_idx, + entry_payload_o = ldq_addr, + entry_wen_o = ldq_addr_wen, + queue_head_oh_i = ldq_head_oh + ) + + This generates, inside 'config_0_core.vhd' and under the 'architecture config_0_core', the following instantiation + + architecture arch of config_0_core is + signal ... + begin + ... + + config_0_core_lda_dispatcher : entity work.config_0_core_lda + port map( + rst => rst, + clk => clk, + port_payload_0_i => ldp_addr_0_i, + port_payload_1_i => ldp_addr_1_i, + port_ready_0_o => ldp_addr_ready_0_o, + port_ready_1_o => ldp_addr_ready_1_o, + port_valid_0_i => ldp_addr_valid_0_i, + port_valid_1_i => ldp_addr_valid_1_i, + entry_alloc_0_i => ldq_valid_0_q, + entry_alloc_1_i => ldq_valid_1_q, + entry_payload_valid_0_i => ldq_addr_valid_0_q, + entry_payload_valid_1_i => ldq_addr_valid_1_q, + entry_port_idx_0_i => ldq_port_idx_0_q, + entry_port_idx_1_i => ldq_port_idx_1_q, + entry_payload_0_o => ldq_addr_0_d, + entry_payload_1_o => ldq_addr_1_d, + entry_wen_0_o => ldq_addr_wen_0, + entry_wen_1_o => ldq_addr_wen_1, + queue_head_oh_i => ldq_head_oh + ); + ... + end architecture; + + """ + + em.start_instantiation(self.module_name, f"{self.module_name}_dispatcher") + + em.add_map("rst", "rst") + em.add_map("clk", "clk") + + for i in range(0, self.numPorts): + em.add_map(f"port_payload_{i}_i", port_payload_i.getNameRead(i)) + for i in range(0, self.numPorts): + em.add_map(f"port_ready_{i}_o", port_ready_o.getNameWrite(i)) + for i in range(0, self.numPorts): + em.add_map(f"port_valid_{i}_i", port_valid_i.getNameRead(i)) + for i in range(0, self.numEntries): + em.add_map(f"entry_alloc_{i}_i", entry_alloc_i.getNameRead(i)) + for i in range(0, self.numEntries): + em.add_map( + f"entry_payload_valid_{i}_i", entry_payload_valid_i.getNameRead(i) + ) + for i in range(0, self.numEntries): + if self.numPorts != 1: + em.add_map(f"entry_port_idx_{i}_i", entry_port_idx_i.getNameRead(i)) + for i in range(0, self.numEntries): + em.add_map(f"entry_payload_{i}_o", entry_payload_o.getNameWrite(i)) + for i in range(0, self.numEntries): + em.add_map(f"entry_wen_{i}_o", entry_wen_o.getNameWrite(i)) + em.add_map(f"queue_head_oh_i", queue_head_oh_i.getNameRead()) + em.complete_instantiation() + return em + + +class QueueToPortDispatcher: + def __init__( + self, + name: str, + suffix: str, + numPorts: int, + numEntries: int, + bitsW: int, + portAddrW: int, + ): + """ + Queue-to-Port (Entry-to-Port) Dispatcher + + Models a dispatcher that routes signals from queue entries to access ports. + + This class encapsulates the logic for generating a VHDL module that takes + data from queue entries and routes it to the correct outgoing port based on + priority. + + This generates one main part in the LSQ module: + 1. Load Data Port Dispatcher + 2. (Optionally) Store Backward Port Dispatcher + + Initialization Parameters: + name : Base name of the dispatcher. + suffix : Suffix appended to the entity name. + numPorts : Number of access ports. + numEntries : Number of queue entries. + bitsW : Width of each data bus. + portAddrW : Width of the port index bus. + + Instance Variable: + self.module_name = name + suffix : Entity and architecture identifier + + Example (Load Data Port Dispatcher): + qtp_dispatcher_ldd = QueueToPortDispatcher( + name="config_0_core", + suffix="_ldd", + numPorts=configs.numLdPorts, + numEntries=configs.numLdqEntries, + bitsW=configs.addrW, + portAddrW=configs.ldpAddrW + ) + + # You can later generate VHDL entity and architecture by + # qtp_dispatcher_ldd.generate(...) + # You can later instantiate VHDL entity by + # qtp_dispatcher_ldd.instantiate(...) + """ + + self.name = name + self.module_name = name + suffix + + self.numPorts = numPorts + self.numEntries = numEntries + self.bitsW = bitsW + self.portAddrW = portAddrW + + def generate(self, em: Emitter, path_rtl) -> None: + """ + Queue-to-Port (Entry-to-Port) Dispatcher + + Generates the VHDL 'entity' and 'architecture' sections for a dispatcher + that routes data from queue entries to their access ports. + + Parameters: + em : Emitter used for code generation + path_rtl : Output directory for VHDL files. + + Output: + Appends the 'entity' and 'architecture' definitions + to the .vhd file at /_core.vhd. + Entity and architecture use the identifier: + + Example (Load Data Port Dispatcher): + qtp_dispatcher_ldd.generate(path_rtl="rtl") + + produces in rtl/config_0_core.vhd: + + entity config_0_core_ldd is + port( + rst : in std_logic; + clk : in std_logic; + ... + ); + end entity; + + architecture arch of config_0_core_ldd is + -- signals generated here + begin + -- dispatcher logic here + end architecture; + + """ + + # IOs + if self.bitsW != 0: + port_payload_o = LogicVecArray( + em, "port_payload", "o", self.numPorts, self.bitsW + ) + port_valid_o = LogicArray(em, "port_valid", "o", self.numPorts) + port_ready_i = LogicArray(em, "port_ready", "i", self.numPorts) + entry_alloc_i = LogicArray(em, "entry_alloc", "i", self.numEntries) + entry_payload_valid_i = LogicArray( + em, "entry_payload_valid", "i", self.numEntries + ) + if self.numPorts != 1: + entry_port_idx_i = LogicVecArray( + em, "entry_port_idx", "i", self.numEntries, self.portAddrW + ) + if self.bitsW != 0: + entry_payload_i = LogicVecArray( + em, "entry_payload", "i", self.numEntries, self.bitsW + ) + entry_reset_o = LogicArray(em, "entry_reset", "o", self.numEntries) + queue_head_oh_i = LogicVec(em, "queue_head_oh", "i", self.numEntries) + + # one-hot port index + entry_port_idx_oh = LogicVecArray( + em, "entry_port_idx_oh", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + if self.numPorts == 1: + em.add_assignment(entry_port_idx_oh[i], Val(1)) + else: + BitsToOH(em, entry_port_idx_oh[i], entry_port_idx_i[i]) + + # This matrix shows entry-port pairs that the entry is linked with the port + entry_allocated_for_port = LogicVecArray( + em, "entry_allocated_for_port", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + em.add_assignment( + entry_allocated_for_port[i], + entry_port_idx_oh[i].when(entry_alloc_i[i]).else_(Val(0)), + ) + + # For each port, the oldest entry send bits this cycle. The priority masking per port(column) + # generates entry-port pairs that will tranfer data/address this cycle. + # It is also used as one-hot select signal for data Mux. + oldest_entry_allocated_per_port = LogicVecArray( + em, "oldest_entry_allocated_per_port", "w", self.numEntries, self.numPorts + ) + CyclicPriorityMasking( + em, + oldest_entry_allocated_per_port, + entry_allocated_for_port, + queue_head_oh_i, + ) + + if self.bitsW != 0: + for j in range(0, self.numPorts): + Mux1H( + em, + port_payload_o[j], + entry_payload_i, + oldest_entry_allocated_per_port, + j, + ) + + # Mask the matrix with dataValid + entry_waiting_for_port_valid = LogicVecArray( + em, "entry_waiting_for_port_valid", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + em.add_assignment( + entry_waiting_for_port_valid[i], + oldest_entry_allocated_per_port[i] + .when(entry_payload_valid_i[i]) + .else_(Val(0)), + ) + + # Reduce the matrix for each port to get the valid signal: + # If an entry is providing data/address from a certain port, valid is set high. + port_valid_vec = LogicVec(em, "port_valid_vec", "w", self.numPorts) + Reduce(em, port_valid_vec, entry_waiting_for_port_valid, BinOp.OR) + VecToArray(em, port_valid_o, port_valid_vec) + + # AND the request signal with ready, it shows entry-port pairs that are both valid and ready. + entry_port_transfer = LogicVecArray( + em, "entry_port_transfer", "w", self.numEntries, self.numPorts + ) + for i in range(0, self.numEntries): + for j in range(0, self.numPorts): + em.add_assignment( + (entry_port_transfer, i, j), + Val(entry_waiting_for_port_valid, i, j) & Val(port_ready_i, j), + ) + + # Reduce for each entry(row), which generates reset signal for entries + for i in range(0, self.numEntries): + Reduce(em, entry_reset_o[i], entry_port_transfer[i], BinOp.OR) + + output_str = em.get_definition_str(self.module_name) + with open(f"{path_rtl}/{self.name}.{em.get_file_suffix()}", "a") as file: + file.write(output_str) + + def instantiate( + self, + em: Emitter, + port_payload_o: LogicVecArray, + port_valid_o: LogicArray, + port_ready_i: LogicArray, + entry_alloc_i: LogicArray, + entry_payload_valid_i: LogicArray, + entry_port_idx_i: LogicVecArray, + entry_payload_i: LogicVecArray, + entry_reset_o: LogicArray, + queue_head_oh_i: LogicVec, + ) -> str: + """ + Queue-to-Port Dispatcher Instantiation + + Creates the VHDL port mapping for the Queue-to-Port dispatcher entity. + Connects the top-level signals (reset, clock, entry and port signals) + to the internal dispatcher instance named _dispatcher. + + Parameters: + em : Emitter for code generation + port_payload_o : Output data bits from each queue entry + port_valid_o : Valid signal for each input port (Valid data) + port_ready_i : Ready signal indicating the queue is ready to send data + entry_alloc_i : Valid bit for a queue entry + entry_payload_valid_i : Valid bit for the contents of a queue entry + entry_port_idx_i : Indicates to which port the entry is assigned + entry_payload_i : Input data bits which is written in the queue entry + entry_reset_o : Array of reset outputs for entries. + queue_head_oh_i : One-hot vector indicating the current head index of the queue. + + Returns: + VHDL instantiation string for inclusion in the architecture body. + + + Example: + # Base architecture: 'config_0_core' + # suffix for Load Data Dispatcher instantiation: '_ldd' + + arch += qtp_dispatcher_ldd.instantiate( + em, + port_payload_o = ldp_data_o, + port_valid_o = ldp_data_valid_o, + port_ready_i = ldp_data_ready_i, + entry_alloc_i = ldq_valid, + entry_payload_valid_i = ldq_data_valid, + entry_port_idx_i = ldq_port_idx, + entry_payload_i = ldq_data, + entry_reset_o = ldq_reset, + queue_head_oh_i = ldq_head_oh + ) + + This generates, inside 'config_0_core.vhd' and under the 'architecture config_0_core', the following instantiation + + architecture arch of config_0_core is + signal ... + begin + ... + config_0_core_ldd_dispatcher : entity work.config_0_core_ldd + port map( + rst => rst, + clk => clk, + port_payload_0_o => ldp_data_0_o, + port_payload_1_o => ldp_data_1_o, + port_ready_0_i => ldp_data_ready_0_i, + port_ready_1_i => ldp_data_ready_1_i, + port_valid_0_o => ldp_data_valid_0_o, + port_valid_1_o => ldp_data_valid_1_o, + entry_alloc_0_i => ldq_valid_0_q, + entry_alloc_1_i => ldq_valid_1_q, + entry_payload_valid_0_i => ldq_data_valid_0_q, + entry_payload_valid_1_i => ldq_data_valid_1_q, + entry_port_idx_0_i => ldq_port_idx_0_q, + entry_port_idx_1_i => ldq_port_idx_1_q, + entry_payload_0_i => ldq_data_0_q, + entry_payload_1_i => ldq_data_1_q, + entry_reset_0_o => ldq_reset_0, + entry_reset_1_o => ldq_reset_1, + queue_head_oh_i => ldq_head_oh + ); + ... + end architecture; + """ + + em.start_instantiation(self.module_name, f"{self.module_name}_dispatcher") + + em.add_map("rst", "rst") + em.add_map("clk", "clk") + + for i in range(0, self.numPorts): + if port_payload_o != None: + em.add_map(f"port_payload_{i}_o", port_payload_o.getNameWrite(i)) + for i in range(0, self.numPorts): + em.add_map(f"port_ready_{i}_i", port_ready_i.getNameRead(i)) + for i in range(0, self.numPorts): + em.add_map(f"port_valid_{i}_o", port_valid_o.getNameWrite(i)) + for i in range(0, self.numEntries): + em.add_map(f"entry_alloc_{i}_i", entry_alloc_i.getNameRead(i)) + for i in range(0, self.numEntries): + em.add_map( + f"entry_payload_valid_{i}_i", entry_payload_valid_i.getNameRead(i) + ) + for i in range(0, self.numEntries): + if self.numPorts != 1: + em.add_map(f"entry_port_idx_{i}_i", entry_port_idx_i.getNameRead(i)) + for i in range(0, self.numEntries): + if entry_payload_i != None: + em.add_map(f"entry_payload_{i}_i", entry_payload_i.getNameRead(i)) + for i in range(0, self.numEntries): + em.add_map(f"entry_reset_{i}_o", entry_reset_o.getNameWrite(i)) + em.add_map(f"queue_head_oh_i", queue_head_oh_i.getNameRead()) + em.complete_instantiation() + return em diff --git a/tools/backend/lsq-generator-python/core_gen/generators/group_allocator.py b/tools/backend/lsq-generator-python/core_gen/generators/group_allocator.py new file mode 100644 index 0000000000..2c28946055 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/generators/group_allocator.py @@ -0,0 +1,416 @@ +from core_gen.signals import Logic, LogicArray, LogicVec, LogicVecArray +from core_gen.ir import Val, WhenElse, Bit +from core_gen.operators import WrapSub, Mux1HROM, CyclicLeftShift, CyclicPriorityMasking +from core_gen.configs import Configs +from core_gen.emitters import Emitter + + +class GroupAllocator: + def __init__(self, name: str, suffix: str, configs: Configs): + """ + Group Allocator + + Models a group allocator for a Load-Store Queue (LSQ) system. + + This class encapsulates the logic for generating a VHDL module that allocates + space for groups of memory operations (loads and stores) in the load queue and + the store queue. + + Parameters: + name : Base name of the group allocator. + suffix : Suffix appended to the entity name. + configs : configuration generated from JSON + + Instance Variable: + self.module_name = name + suffix : Entity and architecture identifier + + Example: + ga = GroupAllocator( + name="config_0_core", + suffix="_ga", + configs=configs + ) + + # You can later generate VHDL entity and architecture by + # ga.generate(...) + # You can later instantiate VHDL entity by + # ga.instantiate(...) + """ + + self.name = name + self.configs = configs + self.module_name = name + suffix + + def generate(self, em: Emitter, path_rtl: str) -> None: + """ + Generates the VHDL 'entity' and 'architecture' sections for a group allocator. + + Parameters: + em : Emitter used for code generation + path_rtl : Output directory for VHDL files. + + Output: + Appends the 'entity' and 'architecture' definitions + to the .vhd file at /.vhd. + Entity and architecture use the identifier: + + Example (Group Allocator): + ga.generate(path_rtl) + + produces in rtl/config_0_core.vhd: + + entity config_0_core_ga is + port( + rst : in std_logic; + clk : in std_logic; + ... + ); + end entity; + + architecture arch of config_0_core_ga is + -- signals generated here + begin + -- group allocator logic here + end architecture; + + """ + # IOs + group_init_valid_i = LogicArray( + em, "group_init_valid", "i", self.configs.numGroups + ) + group_init_ready_o = LogicArray( + em, "group_init_ready", "o", self.configs.numGroups + ) + + ldq_tail_i = LogicVec(em, "ldq_tail", "i", self.configs.ldqAddrW) + ldq_head_i = LogicVec(em, "ldq_head", "i", self.configs.ldqAddrW) + ldq_empty_i = Logic(em, "ldq_empty", "i") + + stq_tail_i = LogicVec(em, "stq_tail", "i", self.configs.stqAddrW) + stq_head_i = LogicVec(em, "stq_head", "i", self.configs.stqAddrW) + stq_empty_i = Logic(em, "stq_empty", "i") + + ldq_wen_o = LogicArray(em, "ldq_wen", "o", self.configs.numLdqEntries) + num_loads_o = LogicVec(em, "num_loads", "o", self.configs.ldqAddrW) + num_loads = LogicVec(em, "num_loads", "w", self.configs.ldqAddrW) + if self.configs.ldpAddrW > 0: + ldq_port_idx_o = LogicVecArray( + em, + "ldq_port_idx", + "o", + self.configs.numLdqEntries, + self.configs.ldpAddrW, + ) + + stq_wen_o = LogicArray(em, "stq_wen", "o", self.configs.numStqEntries) + num_stores_o = LogicVec(em, "num_stores", "o", self.configs.stqAddrW) + num_stores = LogicVec(em, "num_stores", "w", self.configs.stqAddrW) + if self.configs.stpAddrW > 0: + stq_port_idx_o = LogicVecArray( + em, + "stq_port_idx", + "o", + self.configs.numStqEntries, + self.configs.stpAddrW, + ) + + ga_ls_order_o = LogicVecArray( + em, + "ga_ls_order", + "o", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + # The number of empty load and store is calculated with cyclic subtraction. + # If the empty signal is high, then set the number to max value. + loads_sub = LogicVec(em, "loads_sub", "w", self.configs.ldqAddrW) + stores_sub = LogicVec(em, "stores_sub", "w", self.configs.stqAddrW) + empty_loads = LogicVec(em, "empty_loads", "w", self.configs.emptyLdAddrW) + empty_stores = LogicVec(em, "empty_stores", "w", self.configs.emptyStAddrW) + + WrapSub(em, loads_sub, ldq_head_i, ldq_tail_i, self.configs.numLdqEntries) + WrapSub(em, stores_sub, stq_head_i, stq_tail_i, self.configs.numStqEntries) + + em.add_assignment( + empty_loads, + Val(self.configs.numLdqEntries) + .when(ldq_empty_i) + .else_(Bit(0).concat(loads_sub)), + ) + em.add_assignment( + empty_stores, + Val(self.configs.numStqEntries) + .when(stq_empty_i) + .else_(Bit(0).concat(stores_sub)), + ) + + # Generate handshake signals + group_init_ready = LogicArray( + em, "group_init_ready", "w", self.configs.numGroups + ) + group_init_hs = LogicArray(em, "group_init_hs", "w", self.configs.numGroups) + + for i in range(0, self.configs.numGroups): + em.add_assignment( + group_init_ready[i], + Bit(1) + .when( + ( + empty_loads + >= Val(self.configs.gaNumLoads[i], self.configs.emptyLdAddrW) + ) + & ( + empty_stores + >= Val(self.configs.gaNumStores[i], self.configs.emptyStAddrW) + ) + ) + .else_(Bit(0)), + ) + + if self.configs.gaMulti: + group_init_and = LogicArray( + em, "group_init_and", "w", self.configs.numGroups + ) + ga_rr_mask = LogicVec(em, "ga_rr_mask", "r", self.configs.numGroups) + ga_rr_mask.regInit() + for i in range(0, self.configs.numGroups): + em.add_assignment( + group_init_and[i], group_init_ready[i] & group_init_valid_i[i] + ) + em.add_assignment(group_init_ready_o[i], group_init_hs[i]) + CyclicPriorityMasking(em, group_init_hs, group_init_and, ga_rr_mask) + for i in range(0, self.configs.numGroups): + em.add_assignment( + (ga_rr_mask, (i + 1) % self.configs.numGroups), + Val(group_init_hs, i), + ) + else: + for i in range(0, self.configs.numGroups): + em.add_assignment(group_init_ready_o[i], group_init_ready[i]) + em.add_assignment( + group_init_hs[i], group_init_ready[i] & group_init_valid_i[i] + ) + + # ROM value + if self.configs.ldpAddrW > 0: + ldq_port_idx_rom = LogicVecArray( + em, + "ldq_port_idx_rom", + "w", + self.configs.numLdqEntries, + self.configs.ldpAddrW, + ) + if self.configs.stpAddrW > 0: + stq_port_idx_rom = LogicVecArray( + em, + "stq_port_idx_rom", + "w", + self.configs.numStqEntries, + self.configs.stpAddrW, + ) + ga_ls_order_rom = LogicVecArray( + em, + "ga_ls_order_rom", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + ga_ls_order_temp = LogicVecArray( + em, + "ga_ls_order_temp", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + if self.configs.ldpAddrW > 0: + Mux1HROM(em, ldq_port_idx_rom, self.configs.gaLdPortIdx, group_init_hs) + if self.configs.stpAddrW > 0: + Mux1HROM(em, stq_port_idx_rom, self.configs.gaStPortIdx, group_init_hs) + Mux1HROM( + em, ga_ls_order_rom, self.configs.gaLdOrder, group_init_hs, em.mask_less + ) + Mux1HROM(em, num_loads, self.configs.gaNumLoads, group_init_hs) + Mux1HROM(em, num_stores, self.configs.gaNumStores, group_init_hs) + em.add_assignment(num_loads_o, num_loads) + em.add_assignment(num_stores_o, num_stores) + + ldq_wen_unshifted = LogicArray( + em, "ldq_wen_unshifted", "w", self.configs.numLdqEntries + ) + stq_wen_unshifted = LogicArray( + em, "stq_wen_unshifted", "w", self.configs.numStqEntries + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + ldq_wen_unshifted[i], + Bit(1) + .when(Val(num_loads) > Val(i, self.configs.ldqAddrW)) + .else_(Bit(0)), + ) + for i in range(0, self.configs.numStqEntries): + em.add_assignment( + stq_wen_unshifted[i], + Bit(1) + .when(Val(num_stores) > Val(i, self.configs.stqAddrW)) + .else_(Bit(0)), + ) + + # Shift the arrays + if self.configs.ldpAddrW > 0: + CyclicLeftShift(em, ldq_port_idx_o, ldq_port_idx_rom, ldq_tail_i) + if self.configs.stpAddrW > 0: + CyclicLeftShift(em, stq_port_idx_o, stq_port_idx_rom, stq_tail_i) + CyclicLeftShift(em, ldq_wen_o, ldq_wen_unshifted, ldq_tail_i) + CyclicLeftShift(em, stq_wen_o, stq_wen_unshifted, stq_tail_i) + for i in range(0, self.configs.numLdqEntries): + CyclicLeftShift(em, ga_ls_order_temp[i], ga_ls_order_rom[i], stq_tail_i) + CyclicLeftShift(em, ga_ls_order_o, ga_ls_order_temp, ldq_tail_i) + + # Write to the file + output_str = em.get_definition_str( + self.module_name, write_regs=self.configs.gaMulti + ) + with open(f"{path_rtl}/{self.name}.{em.get_file_suffix()}", "a") as file: + file.write(output_str) + + def instantiate( + self, + em: Emitter, + group_init_valid_i: LogicArray, + group_init_ready_o: LogicArray, + ldq_tail_i: LogicVec, + ldq_head_i: LogicVec, + ldq_empty_i: Logic, + stq_tail_i: LogicVec, + stq_head_i: LogicVec, + stq_empty_i: Logic, + ldq_wen_o: LogicArray, + num_loads_o: LogicVec, + ldq_port_idx_o: LogicVecArray, + stq_wen_o: LogicArray, + num_stores_o: LogicVec, + stq_port_idx_o: LogicVecArray, + ga_ls_order_o: LogicVecArray, + ) -> str: + """ + Group Allocator Instantiation + + Creates the VHDL port mapping for the group allocator entity. + + Parameters: + em : Emitter for code generation + group_init_valid_i : Group Allocator handshake valid signal + group_init_ready_o : Group Allocator handshake ready signal + ldq_tail_i : Load queue tail + ldq_head_i : Load queue head + ldq_empty_i : (boolean) load queue empty + stq_tail_i : Store queue tail + stq_head_i : Store queue head + stq_empty_i : (boolean) store queue empty + ldq_wen_o : Load queue write enable + num_loads_o : The number of loads + ldq_port_idx_o : Load queue port index + stq_wen_o : Store queue write enable + num_stores_o : The number of stores + stq_port_idx_o : Store queue port index + ga_ls_order_o : Group Allocator load-store order matrix + + Returns: + VHDL instantiation string for inclusion in the architecture body. + + Example: + arch += ga.instantiate( + ctx, + group_init_valid_i = group_init_valid_i, + group_init_ready_o = group_init_ready_o, + ldq_tail_i = ldq_tail, + ldq_head_i = ldq_head, + ldq_empty_i = ldq_empty, + stq_tail_i = stq_tail, + stq_head_i = stq_head, + stq_empty_i = stq_empty, + ldq_wen_o = ldq_wen, + num_loads_o = num_loads, + ldq_port_idx_o = ldq_port_idx, + stq_wen_o = stq_wen, + num_stores_o = num_stores, + stq_port_idx_o = stq_port_idx, + ga_ls_order_o = ga_ls_order + ) + + This generates, inside 'config_0_core.vhd' and under the 'architecture config_0_core', the following instantiation + + architecture arch of config_0_core is + signal ... + begin + ... + config_0_core_ga : entity work.config_0_core_ga + port map( + rst => rst, + clk => clk, + group_init_valid_0_i => group_init_valid_0_i, + group_init_ready_0_o => group_init_ready_0_o, + ldq_tail_i => ldq_tail_q, + ldq_head_i => ldq_head_q, + ldq_empty_i => ldq_empty, + stq_tail_i => stq_tail_q, + stq_head_i => stq_head_q, + stq_empty_i => stq_empty, + ldq_wen_0_o => ldq_wen_0, + ldq_wen_1_o => ldq_wen_1, + num_loads_o => num_loads, + ldq_port_idx_0_o => ldq_port_idx_0_d, + ldq_port_idx_1_o => ldq_port_idx_1_d, + stq_wen_0_o => stq_wen_0, + stq_wen_1_o => stq_wen_1, + stq_port_idx_0_o => stq_port_idx_0_d, + stq_port_idx_1_o => stq_port_idx_1_d, + ga_ls_order_0_o => ga_ls_order_0, + ga_ls_order_1_o => ga_ls_order_1, + num_stores_o => num_stores + ); + ... + end architecture; + """ + + em.start_instantiation(self.module_name) + + em.add_map("rst", "rst") + em.add_map("clk", "clk") + + for i in range(0, self.configs.numGroups): + em.add_map(f"group_init_valid_{i}_i", group_init_valid_i.getNameRead(i)) + + for i in range(0, self.configs.numGroups): + em.add_map(f"group_init_ready_{i}_o", group_init_ready_o.getNameWrite(i)) + + em.add_map("ldq_tail_i", ldq_tail_i.getNameRead()) + em.add_map("ldq_head_i", ldq_head_i.getNameRead()) + em.add_map("ldq_empty_i", ldq_empty_i.getNameRead()) + + em.add_map("stq_tail_i", stq_tail_i.getNameRead()) + em.add_map("stq_head_i", stq_head_i.getNameRead()) + em.add_map("stq_empty_i", stq_empty_i.getNameRead()) + + for i in range(0, self.configs.numLdqEntries): + em.add_map(f"ldq_wen_{i}_o", ldq_wen_o.getNameWrite(i)) + + em.add_map(f"num_loads_o", num_loads_o.getNameWrite()) + + if self.configs.ldpAddrW > 0: + for i in range(0, self.configs.numLdqEntries): + em.add_map(f"ldq_port_idx_{i}_o", ldq_port_idx_o.getNameWrite(i)) + + for i in range(0, self.configs.numStqEntries): + em.add_map(f"stq_wen_{i}_o", stq_wen_o.getNameWrite(i)) + if self.configs.stpAddrW > 0: + for i in range(0, self.configs.numStqEntries): + em.add_map(f"stq_port_idx_{i}_o", stq_port_idx_o.getNameWrite(i)) + + for i in range(0, self.configs.numLdqEntries): + em.add_map(f"ga_ls_order_{i}_o", ga_ls_order_o.getNameWrite(i)) + + em.add_map("num_stores_o", num_stores_o.getNameWrite()) + em.complete_instantiation() + return em diff --git a/tools/backend/lsq-generator-python/core_gen/generators/lsq.py b/tools/backend/lsq-generator-python/core_gen/generators/lsq.py new file mode 100644 index 0000000000..db92fb4096 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/generators/lsq.py @@ -0,0 +1,1873 @@ +from core_gen.emitters import Emitter +from core_gen.signals import * +from core_gen.operators import * +from core_gen.configs import Configs +from core_gen.ir import BinOp, Bin, Val, Bit, CustomStatement, reduce_bin + +import core_gen.generators.lsq_submodule_wrapper as lsq_submodule_wrapper + + +class LSQ: + def __init__(self, name: str, suffix: str, configs: Configs): + """ + LSQ + + Models the top-level Load-Store Queue (LSQ) module. + + This class integrates all necessary sub-components to form a complete LSQ. + It is responsible for generating the top-level VHDL entity that wires + together the Group Allocator, various Port/Queue Dispatchers, and the core + queue logic with dependency checking. + + Parameters: + name : Base name of the LSQ. "_core" + suffix : Suffix appended to the name to form the VHDL entity name. + Since LSQ is the top module, you do not need to add any suffix. + configs : configuration generated from JSON + + + Instance Variable: + self.module_name = name + suffix : Entity and architecture identifier + + + Example: + lsq_core = LSQ("config_0_core", '', configs) + + # You can later generate VHDL entity and architecture by + # lsq_core.generate(...) + + # Instantiation of the LSQ module does not use this class. + # It considers more conditions, and it is done in lsq-generator.py. + + """ + + self.name = name + self.module_name = name + suffix + self.configs = configs + + def generate(self, em: Emitter, lsq_submodules, path_rtl) -> None: + """ + Generates the VHDL 'entity' and 'architecture' sections for an LSQ. + + This function appends the following to the file '/.vhd: + 1. 'entity ' declaration + 2. 'architecture arch of ' implementation + + The generated code also instantitates: + - Group Allocator + - Port-to-Queue Dispatcher + - Load Address Port Dispatcher + - Store Address Port Dispatcher + - Store Data Port Dispatcher + - Queue-to-Port Dispatcher + - Load Data Port Dispatcher + - (Optionally) Store Backward Port Dispatcher + + Parameters: + em : an instance of the Emitter class used for code generation + lsq_submodules : A collection of objects representing submodules whose VHDL entity + definitions are already generated. This parameter is used to + generate their port map instantiations. + path_rtl : Output directory for VHDL files. + + Output: + Appends the 'entity' and 'architecture' definitions + to the .vhd file at /.vhd. + Entity and architecture use the identifier: + + Example: + lsq_core.generate(lsq_submodules, path_rtl) + + """ + ###### LSQ Architecture ###### + ###### IOs ###### + + # group initialzation signals + group_init_valid_i = LogicArray( + em, "group_init_valid", "i", self.configs.numGroups + ) + group_init_ready_o = LogicArray( + em, "group_init_ready", "o", self.configs.numGroups + ) + + # Memory access ports, i.e., the connection "kernel -> LSQ" + # Load address channel (addr, valid, ready) from kernel, contains signals: + ldp_addr_i = LogicVecArray( + em, "ldp_addr", "i", self.configs.numLdPorts, self.configs.addrW + ) + ldp_addr_valid_i = LogicArray( + em, "ldp_addr_valid", "i", self.configs.numLdPorts + ) + ldp_addr_ready_o = LogicArray( + em, "ldp_addr_ready", "o", self.configs.numLdPorts + ) + + # Load data channel (data, valid, ready) to kernel + ldp_data_o = LogicVecArray( + em, "ldp_data", "o", self.configs.numLdPorts, self.configs.dataW + ) + ldp_data_valid_o = LogicArray( + em, "ldp_data_valid", "o", self.configs.numLdPorts + ) + ldp_data_ready_i = LogicArray( + em, "ldp_data_ready", "i", self.configs.numLdPorts + ) + + # Store address channel (addr, valid, ready) from kernel + stp_addr_i = LogicVecArray( + em, "stp_addr", "i", self.configs.numStPorts, self.configs.addrW + ) + stp_addr_valid_i = LogicArray( + em, "stp_addr_valid", "i", self.configs.numStPorts + ) + stp_addr_ready_o = LogicArray( + em, "stp_addr_ready", "o", self.configs.numStPorts + ) + + # Store data channel (data, valid, ready) from kernel + stp_data_i = LogicVecArray( + em, "stp_data", "i", self.configs.numStPorts, self.configs.dataW + ) + stp_data_valid_i = LogicArray( + em, "stp_data_valid", "i", self.configs.numStPorts + ) + stp_data_ready_o = LogicArray( + em, "stp_data_ready", "o", self.configs.numStPorts + ) + + if self.configs.stResp: + stp_exec_valid_o = LogicArray( + em, "stp_exec_valid", "o", self.configs.numStPorts + ) + stp_exec_ready_i = LogicArray( + em, "stp_exec_ready", "i", self.configs.numStPorts + ) + + # queue empty signal + empty_o = Logic(em, "empty", "o") + + # Memory interface: i.e., the connection LSQ -> AXI + # We assume that the memory interface has + # 1. A read request channel (rreq) and a read response channel (rresp). + # 2. A write request channel (wreq) and a write response channel (wresp). + rreq_valid_o = LogicArray(em, "rreq_valid", "o", self.configs.numLdMem) + rreq_ready_i = LogicArray(em, "rreq_ready", "i", self.configs.numLdMem) + rreq_id_o = LogicVecArray( + em, "rreq_id", "o", self.configs.numLdMem, self.configs.idW + ) + rreq_addr_o = LogicVecArray( + em, "rreq_addr", "o", self.configs.numLdMem, self.configs.addrW + ) + + rresp_valid_i = LogicArray(em, "rresp_valid", "i", self.configs.numLdMem) + rresp_ready_o = LogicArray(em, "rresp_ready", "o", self.configs.numLdMem) + rresp_id_i = LogicVecArray( + em, "rresp_id", "i", self.configs.numLdMem, self.configs.idW + ) + rresp_data_i = LogicVecArray( + em, "rresp_data", "i", self.configs.numLdMem, self.configs.dataW + ) + + wreq_valid_o = LogicArray(em, "wreq_valid", "o", self.configs.numStMem) + wreq_ready_i = LogicArray(em, "wreq_ready", "i", self.configs.numStMem) + wreq_id_o = LogicVecArray( + em, "wreq_id", "o", self.configs.numStMem, self.configs.idW + ) + wreq_addr_o = LogicVecArray( + em, "wreq_addr", "o", self.configs.numStMem, self.configs.addrW + ) + wreq_data_o = LogicVecArray( + em, "wreq_data", "o", self.configs.numStMem, self.configs.dataW + ) + + wresp_valid_i = LogicArray(em, "wresp_valid", "i", self.configs.numStMem) + wresp_ready_o = LogicArray(em, "wresp_ready", "o", self.configs.numStMem) + wresp_id_i = LogicVecArray( + em, "wresp_id", "i", self.configs.numStMem, self.configs.idW + ) + + # Pointer related signals + # For updating pointers + num_loads = LogicVec(em, "num_loads", "w", self.configs.ldqAddrW) + num_stores = LogicVec(em, "num_stores", "w", self.configs.stqAddrW) + stq_issue_en = Logic(em, "stq_issue_en", "w") + stq_resp_en = Logic(em, "stq_resp_en", "w") + # Generated by pointers + ldq_empty = Logic(em, "ldq_empty", "w") + stq_empty = Logic(em, "stq_empty", "w") + ldq_head_oh = LogicVec(em, "ldq_head_oh", "w", self.configs.numLdqEntries) + stq_head_oh = LogicVec(em, "stq_head_oh", "w", self.configs.numStqEntries) + #! If this is the lsq master, then we need the following logic + #! Define new interfaces needed by dynamatic + if self.configs.master: + memStart_ready = Logic(em, "memStart_ready", "o") + memStart_valid = Logic(em, "memStart_valid", "i") + ctrlEnd_ready = Logic(em, "ctrlEnd_ready", "o") + ctrlEnd_valid = Logic(em, "ctrlEnd_valid", "i") + memEnd_ready = Logic(em, "memEnd_ready", "i") + memEnd_valid = Logic(em, "memEnd_valid", "o") + + #! Add extra signals required + memStartReady = Logic(em, "memStartReady", "w", force_reg=True) + memEndValid = Logic(em, "memEndValid", "w", force_reg=True) + ctrlEndReady = Logic(em, "ctrlEndReady", "w", force_reg=True) + temp_gen_mem = Logic(em, "TEMP_GEN_MEM", "w") + + #! The memory completion signal cannot be set to 1 when any group is allocating: + no_curr_ga = ~reduce_bin(BinOp.OR, [Val(group_init_valid_i, i) for i in range(group_init_valid_i.length)]) + + #! Define the needed logic + em.add_comment( + "This signal indicates that all mem. ops are completed and func. can return." + ) + em.add_comment("LSQ can return iff all the following conditions are true:") + em.add_comment("1. No more upcoming BBs containing memory accesses.") + em.add_comment("2. Both store and load queues are empty.") + em.add_comment("3. No GA in the same cycle.") + em.add_assignment( + temp_gen_mem, ctrlEnd_valid & stq_empty & ldq_empty & no_curr_ga + ) + + em.add_comment("Define logic for the new interfaces needed by dynamatic") + vhdl_str = "" + # TODO: Add proper emitter functions in order to do this + vhdl_str += "\tprocess (clk) is\n\tbegin\n" + vhdl_str += "\t" * 2 + "if rising_edge(clk) then\n" + vhdl_str += "\t" * 3 + "if rst = '1' then\n" + vhdl_str += "\t" * 4 + "memStartReady <= '1';\n" + vhdl_str += "\t" * 4 + "memEndValid <= '0';\n" + vhdl_str += "\t" * 4 + "ctrlEndReady <= '0';\n" + vhdl_str += "\t" * 3 + "else\n" + vhdl_str += ( + "\t" * 4 + + "memStartReady <= (memEndValid and memEnd_ready_i) or ((not (memStart_valid_i and memStartReady)) and memStartReady);\n" + ) + vhdl_str += "\t" * 4 + "memEndValid <= TEMP_GEN_MEM or memEndValid;\n" + vhdl_str += ( + "\t" * 4 + + "ctrlEndReady <= (not (ctrlEnd_valid_i and ctrlEndReady)) and (TEMP_GEN_MEM or ctrlEndReady);\n" + ) + vhdl_str += "\t" * 3 + "end if;\n" + vhdl_str += "\t" * 2 + "end if;\n" + vhdl_str += "\tend process;\n\n" + + verilog_str = """ +always @(posedge clk) begin + if (rst) begin + memStartReady <= 1'b1; + memEndValid <= 1'b0; + ctrlEndReady <= 1'b0; + end + else begin + memStartReady <= (memEndValid && memEnd_ready_i) || + ((!(memStart_valid_i && memStartReady)) && memStartReady); + + memEndValid <= TEMP_GEN_MEM || memEndValid; + + ctrlEndReady <= (!(ctrlEnd_valid_i && ctrlEndReady)) && + (TEMP_GEN_MEM || ctrlEndReady); + end +end + """ + + em.add_custom_statement(CustomStatement(vhdl_str, verilog_str)) + + #! Assign signals for the newly added ports + em.add_comment("Update new memory interfaces") + em.add_assignment(memStart_ready, memStartReady) + em.add_assignment(ctrlEnd_ready, ctrlEndReady) + em.add_assignment(memEnd_valid, memEndValid) + + ###### Queue Registers ###### + # Load Queue Entries + ldq_alloc = LogicArray(em, "ldq_alloc", "r", self.configs.numLdqEntries) + ldq_issue = LogicArray(em, "ldq_issue", "r", self.configs.numLdqEntries) + if self.configs.ldpAddrW > 0: + ldq_port_idx = LogicVecArray( + em, + "ldq_port_idx", + "r", + self.configs.numLdqEntries, + self.configs.ldpAddrW, + ) + else: + ldq_port_idx = None + ldq_addr_valid = LogicArray( + em, "ldq_addr_valid", "r", self.configs.numLdqEntries + ) + ldq_addr = LogicVecArray( + em, "ldq_addr", "r", self.configs.numLdqEntries, self.configs.addrW + ) + ldq_data_valid = LogicArray( + em, "ldq_data_valid", "r", self.configs.numLdqEntries + ) + ldq_data = LogicVecArray( + em, "ldq_data", "r", self.configs.numLdqEntries, self.configs.dataW + ) + + # Store Queue Entries + stq_alloc = LogicArray(em, "stq_alloc", "r", self.configs.numStqEntries) + if self.configs.stResp: + stq_exec = LogicArray(em, "stq_exec", "r", self.configs.numStqEntries) + if self.configs.stpAddrW > 0: + stq_port_idx = LogicVecArray( + em, + "stq_port_idx", + "r", + self.configs.numStqEntries, + self.configs.stpAddrW, + ) + else: + stq_port_idx = None + stq_addr_valid = LogicArray( + em, "stq_addr_valid", "r", self.configs.numStqEntries + ) + stq_addr = LogicVecArray( + em, "stq_addr", "r", self.configs.numStqEntries, self.configs.addrW + ) + stq_data_valid = LogicArray( + em, "stq_data_valid", "r", self.configs.numStqEntries + ) + stq_data = LogicVecArray( + em, "stq_data", "r", self.configs.numStqEntries, self.configs.dataW + ) + + # Order for load-store + store_is_older = LogicVecArray( + em, + "store_is_older", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + # Pointers + ldq_tail = LogicVec(em, "ldq_tail", "r", self.configs.ldqAddrW) + ldq_head = LogicVec(em, "ldq_head", "r", self.configs.ldqAddrW) + + stq_tail = LogicVec(em, "stq_tail", "r", self.configs.stqAddrW) + stq_head = LogicVec(em, "stq_head", "r", self.configs.stqAddrW) + stq_issue = LogicVec(em, "stq_issue", "r", self.configs.stqAddrW) + stq_resp = LogicVec(em, "stq_resp", "r", self.configs.stqAddrW) + + # Entry related signals + # From port dispatchers + ldq_wen = LogicArray(em, "ldq_wen", "w", self.configs.numLdqEntries) + ldq_addr_wen = LogicArray(em, "ldq_addr_wen", "w", self.configs.numLdqEntries) + ldq_reset = LogicArray(em, "ldq_reset", "w", self.configs.numLdqEntries) + stq_wen = LogicArray(em, "stq_wen", "w", self.configs.numStqEntries) + stq_addr_wen = LogicArray(em, "stq_addr_wen", "w", self.configs.numStqEntries) + stq_data_wen = LogicArray(em, "stq_data_wen", "w", self.configs.numStqEntries) + stq_reset = LogicArray(em, "stq_reset", "w", self.configs.numStqEntries) + # From Read/Write Block + ldq_data_wen = LogicArray(em, "ldq_data_wen", "w", self.configs.numLdqEntries) + ldq_issue_set = LogicArray(em, "ldq_issue_set", "w", self.configs.numLdqEntries) + if self.configs.stResp: + stq_exec_set = LogicArray( + em, "stq_exec_set", "w", self.configs.numStqEntries + ) + # Form Group Allocator + ga_ls_order = LogicVecArray( + em, + "ga_ls_order", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + BitsToOH(em, ldq_head_oh, ldq_head) + BitsToOH(em, stq_head_oh, stq_head) + + # update queue entries + # load queue + if self.configs.pipe0 or self.configs.pipeComp: + ldq_wen_p0 = LogicArray(em, "ldq_wen_p0", "r", self.configs.numLdqEntries) + ldq_wen_p0.regInit() + if self.configs.pipe0 and self.configs.pipeComp: + ldq_wen_p1 = LogicArray( + em, "ldq_wen_p1", "r", self.configs.numLdqEntries + ) + ldq_wen_p1.regInit() + ldq_alloc_next = LogicArray( + em, "ldq_alloc_next", "w", self.configs.numLdqEntries + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment(ldq_alloc_next[i], ~ldq_reset[i] & ldq_alloc[i]) + em.add_assignment(ldq_alloc[i], ldq_wen[i] | ldq_alloc_next[i]) + if self.configs.pipe0 or self.configs.pipeComp: + em.add_assignment(ldq_wen_p0[i], ldq_wen[i]) + if self.configs.pipe0 and self.configs.pipeComp: + em.add_assignment(ldq_wen_p1[i], ldq_wen[i]) + em.add_assignment( + ldq_issue[i], ~ldq_wen_p1[i] & (ldq_issue_set[i] | ldq_issue[i]) + ) + else: + em.add_assignment( + ldq_issue[i], ~ldq_wen_p0[i] & (ldq_issue_set[i] | ldq_issue[i]) + ) + else: + em.add_assignment( + ldq_issue[i], ~ldq_wen[i] & (ldq_issue_set[i] | ldq_issue[i]) + ) + + em.add_assignment( + ldq_addr_valid[i], ~ldq_wen[i] & (ldq_addr_wen[i] | ldq_addr_valid[i]) + ) + em.add_assignment( + ldq_data_valid[i], ~ldq_wen[i] & (ldq_data_wen[i] | ldq_data_valid[i]) + ) + # store queue + stq_alloc_next = LogicArray( + em, "stq_alloc_next", "w", self.configs.numStqEntries + ) + for i in range(0, self.configs.numStqEntries): + em.add_assignment(stq_alloc_next[i], ~stq_reset[i] & stq_alloc[i]) + em.add_assignment(stq_alloc[i], stq_wen[i] | stq_alloc_next[i]) + if self.configs.stResp: + em.add_assignment( + stq_exec[i], ~stq_wen[i] & (stq_exec_set[i] | stq_exec[i]) + ) + em.add_assignment( + stq_addr_valid[i], ~stq_wen[i] & (stq_addr_wen[i] | stq_addr_valid[i]) + ) + em.add_assignment( + stq_data_valid[i], ~stq_wen[i] & (stq_data_wen[i] | stq_data_valid[i]) + ) + + # order matrix + # store_is_older(i,j) = (not stq_reset(j) and (stq_alloc(j) or ga_ls_order(i, j))) + # when ldq_wen(i) + # else not stq_reset(j) and store_is_older(i, j) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (store_is_older, i, j), + (~Val(stq_reset, j) & (Val(stq_alloc, j) | Val(ga_ls_order, i, j))) + .when(Val(ldq_wen, i)) + .else_(~Val(stq_reset, j) & Val(store_is_older, i, j)), + ) + + # pointers update + ldq_not_empty = Logic(em, "ldq_not_empty", "w") + stq_not_empty = Logic(em, "stq_not_empty", "w") + Reduce(em, ldq_not_empty, ldq_alloc, BinOp.OR) + em.add_assignment(ldq_empty, ~ldq_not_empty) + MuxLookUp(em, stq_not_empty, stq_alloc, stq_head) + em.add_assignment(stq_empty, ~stq_not_empty) + em.add_assignment(empty_o, ldq_empty & stq_empty) + + WrapAdd(em, ldq_tail, ldq_tail, num_loads, self.configs.numLdqEntries) + WrapAdd(em, stq_tail, stq_tail, num_stores, self.configs.numStqEntries) + WrapAddConst(em, stq_issue, stq_issue, 1, self.configs.numStqEntries) + WrapAddConst(em, stq_resp, stq_resp, 1, self.configs.numStqEntries) + + ldq_tail_oh = LogicVec(em, "ldq_tail_oh", "w", self.configs.numLdqEntries) + BitsToOH(em, ldq_tail_oh, ldq_tail) + ldq_head_next_oh = LogicVec( + em, "ldq_head_next_oh", "w", self.configs.numLdqEntries + ) + ldq_head_next = LogicVec(em, "ldq_head_next", "w", self.configs.ldqAddrW) + ldq_head_sel = Logic(em, "ldq_head_sel", "w") + if self.configs.headLag: + # Update the head pointer according to the valid signal of last cycle + CyclicPriorityMasking(em, ldq_head_next_oh, ldq_alloc, ldq_tail_oh) + Reduce(em, ldq_head_sel, ldq_alloc, BinOp.OR) + else: + CyclicPriorityMasking(em, ldq_head_next_oh, ldq_alloc_next, ldq_tail_oh) + Reduce(em, ldq_head_sel, ldq_alloc_next, BinOp.OR) + OHToBits(em, ldq_head_next, ldq_head_next_oh) + em.add_assignment(ldq_head, ldq_head_next.when(ldq_head_sel).else_(ldq_tail)) + + stq_tail_oh = LogicVec(em, "stq_tail_oh", "w", self.configs.numStqEntries) + BitsToOH(em, stq_tail_oh, stq_tail) + stq_head_next_oh = LogicVec( + em, "stq_head_next_oh", "w", self.configs.numStqEntries + ) + stq_head_next = LogicVec(em, "stq_head_next", "w", self.configs.stqAddrW) + stq_head_sel = Logic(em, "stq_head_sel", "w") + if self.configs.stResp: + if self.configs.headLag: + # Update the head pointer according to the valid signal of last cycle + CyclicPriorityMasking(em, stq_head_next_oh, stq_alloc, stq_tail_oh) + Reduce(em, stq_head_sel, stq_alloc, BinOp.OR) + else: + CyclicPriorityMasking(em, stq_head_next_oh, stq_alloc_next, stq_tail_oh) + Reduce(em, stq_head_sel, stq_alloc_next, BinOp.OR) + OHToBits(em, stq_head_next, stq_head_next_oh) + em.add_assignment( + stq_head, stq_head_next.when(stq_head_sel).else_(stq_tail) + ) + else: + WrapAddConst(em, stq_head_next, stq_head, 1, self.configs.numStqEntries) + em.add_assignment(stq_head_sel, wresp_valid_i[0]) + em.add_assignment( + stq_head, stq_head_next.when(stq_head_sel).else_(stq_head) + ) + + # Load Queue Entries + ldq_alloc.regInit(init=[0] * self.configs.numLdqEntries) + ldq_issue.regInit() + if self.configs.ldpAddrW > 0: + ldq_port_idx.regInit(ldq_wen) + ldq_addr_valid.regInit() + ldq_addr.regInit(ldq_addr_wen) + ldq_data_valid.regInit() + ldq_data.regInit(ldq_data_wen) + + # Store Queue Entries + stq_alloc.regInit(init=[0] * self.configs.numStqEntries) + if self.configs.stResp: + stq_exec.regInit() + if self.configs.stpAddrW > 0: + stq_port_idx.regInit(stq_wen) + stq_addr_valid.regInit() + stq_addr.regInit(stq_addr_wen) + stq_data_valid.regInit() + stq_data.regInit(stq_data_wen) + + # Order for load-store + store_is_older.regInit() + + # Pointers + ldq_tail.regInit(init=0) + ldq_head.regInit(init=0) + + stq_tail.regInit(init=0) + stq_head.regInit(init=0) + stq_issue.regInit(enable=stq_issue_en, init=0) + stq_resp.regInit(enable=stq_resp_en, init=0) + + ###### Entity Instantiation ###### + + # Group Allocator + lsq_submodules.group_allocator.instantiate( + em, + group_init_valid_i, + group_init_ready_o, + ldq_tail, + ldq_head, + ldq_empty, + stq_tail, + stq_head, + stq_empty, + ldq_wen, + num_loads, + ldq_port_idx, + stq_wen, + num_stores, + stq_port_idx, + ga_ls_order, + ) + + # When the condition "lsq_submodules.ptq_dispatcher_lda != None" is not true: + # The dispatcher module will be set to None when there are zero load ports. + # In this case, do not instantiate dispatching logic when there are zero load ports. + # - WARNING: This logic needs more testing + # - TODO: Also remove the load queue when there are zero load ports. + if lsq_submodules.ptq_dispatcher_lda != None: + # Load Address Port Dispatcher + lsq_submodules.ptq_dispatcher_lda.instantiate( + em, + ldp_addr_i, + ldp_addr_valid_i, + ldp_addr_ready_o, + ldq_alloc, + ldq_addr_valid, + ldq_port_idx, + ldq_addr, + ldq_addr_wen, + ldq_head_oh, + ) + + # When the condition "lsq_submodules.qtp_dispatcher_ldd != None" is not true: + # The dispatcher module will be set to None when there are zero load ports. + # In this case, do not instantiate dispatching logic when there are zero load ports. + # - WARNING: This logic needs more testing + # - TODO: Also remove the load queue when there are zero load ports. + if lsq_submodules.qtp_dispatcher_ldd != None: + # Load Data Port Dispatcher + lsq_submodules.qtp_dispatcher_ldd.instantiate( + em, + ldp_data_o, + ldp_data_valid_o, + ldp_data_ready_i, + ldq_alloc, + ldq_data_valid, + ldq_port_idx, + ldq_data, + ldq_reset, + ldq_head_oh, + ) + + # Store Address Port Dispatcher + lsq_submodules.ptq_dispatcher_sta.instantiate( + em, + stp_addr_i, + stp_addr_valid_i, + stp_addr_ready_o, + stq_alloc, + stq_addr_valid, + stq_port_idx, + stq_addr, + stq_addr_wen, + stq_head_oh, + ) + + # Store Data Port Dispatcher + lsq_submodules.ptq_dispatcher_std.instantiate( + em, + stp_data_i, + stp_data_valid_i, + stp_data_ready_o, + stq_alloc, + stq_data_valid, + stq_port_idx, + stq_data, + stq_data_wen, + stq_head_oh, + ) + + # Store Backward Port Dispatcher + if self.configs.stResp: + lsq_submodules.qtp_dispatcher_stb.instantiate( + em, + None, + stp_exec_valid_o, + stp_exec_ready_i, + stq_alloc, + stq_exec, + stq_port_idx, + None, + stq_reset, + stq_head_oh, + ) + + if self.configs.pipe0: + ###### Dependency Check ###### + load_idx_oh = LogicVecArray( + em, + "load_idx_oh", + "w", + self.configs.numLdMem, + self.configs.numLdqEntries, + ) + load_en = LogicArray(em, "load_en", "w", self.configs.numLdMem) + + # Multiple store channels not yet implemented + assert self.configs.numStMem == 1 + store_idx = LogicVec(em, "store_idx", "w", self.configs.stqAddrW) + store_en = Logic(em, "store_en", "w") + + bypass_idx_oh_p0 = LogicVecArray( + em, + "bypass_idx_oh_p0", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + bypass_idx_oh_p0.regInit() + bypass_en = LogicArray(em, "bypass_en", "w", self.configs.numLdqEntries) + + # Matrix Generation + ld_st_conflict = LogicVecArray( + em, + "ld_st_conflict", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + can_bypass = LogicVecArray( + em, + "can_bypass", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + can_bypass_p0 = LogicVecArray( + em, + "can_bypass_p0", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + can_bypass_p0.regInit(init=[0] * self.configs.numLdqEntries) + + if self.configs.pipeComp: + ldq_alloc_pcomp = LogicArray( + em, "ldq_alloc_pcomp", "r", self.configs.numLdqEntries + ) + ldq_addr_valid_pcomp = LogicArray( + em, "ldq_addr_valid_pcomp", "r", self.configs.numLdqEntries + ) + stq_alloc_pcomp = LogicArray( + em, "stq_alloc_pcomp", "r", self.configs.numStqEntries + ) + stq_addr_valid_pcomp = LogicArray( + em, "stq_addr_valid_pcomp", "r", self.configs.numStqEntries + ) + stq_data_valid_pcomp = LogicArray( + em, "stq_data_valid_pcomp", "r", self.configs.numStqEntries + ) + addr_valid_pcomp = LogicVecArray( + em, + "addr_valid_pcomp", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + addr_same_pcomp = LogicVecArray( + em, + "addr_same_pcomp", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + store_is_older_pcomp = LogicVecArray( + em, + "store_is_older_pcomp", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + ldq_alloc_pcomp.regInit(init=[0] * self.configs.numLdqEntries) + ldq_addr_valid_pcomp.regInit() + stq_alloc_pcomp.regInit(init=[0] * self.configs.numStqEntries) + stq_addr_valid_pcomp.regInit() + stq_data_valid_pcomp.regInit() + addr_same_pcomp.regInit() + store_is_older_pcomp.regInit() + + for i in range(0, self.configs.numLdqEntries): + em.add_assignment((ldq_alloc_pcomp, i), Val(ldq_alloc, i)) + em.add_assignment((ldq_addr_valid_pcomp, i), Val(ldq_addr_valid, i)) + for j in range(0, self.configs.numStqEntries): + em.add_assignment((stq_alloc_pcomp, j), Val(stq_alloc, j)) + em.add_assignment((stq_addr_valid_pcomp, j), Val(stq_addr_valid, j)) + em.add_assignment((stq_data_valid_pcomp, j), Val(stq_data_valid, j)) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (store_is_older_pcomp, i, j), Val(store_is_older, i, j) + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_valid_pcomp, i, j), + Val(ldq_addr_valid_pcomp, i) & Val(stq_addr_valid_pcomp, j), + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_same_pcomp, i, j), + Bit(1) + .when(Val(ldq_addr, i) == Val(stq_addr, j)) + .else_(Bit(0)), + ) + + # A load conflicts with a store when: + # 1. The store entry is valid, and + # 2. The store is older than the load, and + # 3. The address conflicts(same or invalid store address). + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (ld_st_conflict, i, j), + Val(stq_alloc_pcomp, j) + & Val(store_is_older_pcomp, i, j) + & ( + Val(addr_same_pcomp, i, j) + | ~Val(stq_addr_valid_pcomp, j) + ), + ) + + # A conflicting store entry can be bypassed to a load entry when: + # 1. The load entry is valid, and + # 2. The load entry is not issued yet, and + # 3. The address of the load-store pair are both valid and values the same. + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (can_bypass_p0, i, j), + Val(ldq_alloc_pcomp, i) + & Val(stq_data_valid_pcomp, j) + & Val(addr_same_pcomp, i, j) + & Val(addr_valid_pcomp, i, j), + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (can_bypass, i, j), + ~Val(ldq_issue, i) & Val(can_bypass_p0, i, j), + ) + + # Load + + load_conflict = LogicArray( + em, "load_conflict", "w", self.configs.numLdqEntries + ) + load_req_valid = LogicArray( + em, "load_req_valid", "w", self.configs.numLdqEntries + ) + can_load = LogicArray(em, "can_load", "w", self.configs.numLdqEntries) + can_load_p0 = LogicArray( + em, "can_load_p0", "r", self.configs.numLdqEntries + ) + can_load_p0.regInit(init=[0] * self.configs.numLdqEntries) + + # The load conflicts with any store + for i in range(0, self.configs.numLdqEntries): + Reduce(em, load_conflict[i], ld_st_conflict[i], BinOp.OR) + # The load is valid when the entry is valid and not yet issued, the load address should also be valid. + # We do not need to check ldq_data_valid, since unissued load request cannot have valid data. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + load_req_valid[i], ldq_alloc_pcomp[i] & ldq_addr_valid_pcomp[i] + ) + # Generate list for loads that does not face dependency issue + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load_p0[i], ~load_conflict[i] & load_req_valid[i] + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment(can_load[i], ~ldq_issue[i] & can_load_p0[i]) + + ldq_head_oh_p0 = LogicVec( + em, "ldq_head_oh_p0", "r", self.configs.numLdqEntries + ) + ldq_head_oh_p0.regInit() + em.add_assignment(ldq_head_oh_p0, ldq_head_oh) + + can_load_list = [] + can_load_list.append(can_load) + for w in range(0, self.configs.numLdMem): + CyclicPriorityMasking( + em, load_idx_oh[w], can_load_list[w], ldq_head_oh_p0 + ) + Reduce(em, load_en[w], can_load_list[w], BinOp.OR) + if w + 1 != self.configs.numLdMem: + load_idx_oh_LogicArray = LogicArray( + em, + f"load_idx_oh_Array_{w+1}", + "w", + self.configs.numLdqEntries, + ) + VecToArray(em, load_idx_oh_LogicArray, load_idx_oh[w]) + can_load_list.append( + LogicArray( + em, + f"can_load_list_{w+1}", + "w", + self.configs.numLdqEntries, + ) + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load_list[w + 1][i], + ~load_idx_oh_LogicArray[i] & can_load_list[w][i], + ) + + # Store + stq_issue_en_p0 = Logic(em, "stq_issue_en_p0", "r") + stq_issue_next = LogicVec( + em, "stq_issue_next", "w", self.configs.stqAddrW + ) + + store_conflict = Logic(em, "store_conflict", "w") + + can_store_curr = Logic(em, "can_store_curr", "w") + st_ld_conflict_curr = LogicVec( + em, "st_ld_conflict_curr", "w", self.configs.numLdqEntries + ) + store_valid_curr = Logic(em, "store_valid_curr", "w") + store_data_valid_curr = Logic(em, "store_data_valid_curr", "w") + store_addr_valid_curr = Logic(em, "store_addr_valid_curr", "w") + + can_store_next = Logic(em, "can_store_next", "w") + st_ld_conflict_next = LogicVec( + em, "st_ld_conflict_next", "w", self.configs.numLdqEntries + ) + store_valid_next = Logic(em, "store_valid_next", "w") + store_data_valid_next = Logic(em, "store_data_valid_next", "w") + store_addr_valid_next = Logic(em, "store_addr_valid_next", "w") + + can_store_p0 = Logic(em, "can_store_p0", "r") + st_ld_conflict_p0 = LogicVec( + em, "st_ld_conflict_p0", "r", self.configs.numLdqEntries + ) + + stq_issue_en_p0.regInit(init=0) + can_store_p0.regInit(init=0) + st_ld_conflict_p0.regInit() + + em.add_assignment(stq_issue_en_p0, stq_issue_en) + WrapAddConst( + em, stq_issue_next, stq_issue, 1, self.configs.numStqEntries + ) + + # A store conflicts with a load when: + # 1. The load entry is valid, and + # 2. The load is older than the store, and + # 3. The address conflicts(same or invalid store address). + # Index order are reversed for store matrix. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + (st_ld_conflict_curr, i), + Val(ldq_alloc_pcomp, i) + & ~Val(em.mux_index(store_is_older_pcomp[i], stq_issue)) + & ( + Val(em.mux_index(addr_same_pcomp[i], stq_issue)) + | ~Val(ldq_addr_valid_pcomp, i) + ), + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + (st_ld_conflict_next, i), + Val(ldq_alloc_pcomp, i) + & ~Val(em.mux_index(store_is_older_pcomp[i], stq_issue_next)) + & ( + Val(em.mux_index(addr_same_pcomp[i], stq_issue_next)) + | ~Val(ldq_addr_valid_pcomp, i) + ), + ) + # The store is valid whe the entry is valid and the data is also valid, + # the store address should also be valid + MuxLookUp(em, store_valid_curr, stq_alloc_pcomp, stq_issue) + MuxLookUp(em, store_data_valid_curr, stq_data_valid_pcomp, stq_issue) + MuxLookUp(em, store_addr_valid_curr, stq_addr_valid_pcomp, stq_issue) + em.add_assignment( + can_store_curr, + store_valid_curr & store_data_valid_curr & store_addr_valid_curr, + ) + MuxLookUp(em, store_valid_next, stq_alloc_pcomp, stq_issue_next) + MuxLookUp( + em, store_data_valid_next, stq_data_valid_pcomp, stq_issue_next + ) + MuxLookUp( + em, store_addr_valid_next, stq_addr_valid_pcomp, stq_issue_next + ) + em.add_assignment( + can_store_next, + store_valid_next & store_data_valid_next & store_addr_valid_next, + ) + # Multiplex from current and next + em.add_assignment( + st_ld_conflict_p0, + st_ld_conflict_next.when(stq_issue_en).else_(st_ld_conflict_curr), + ) + em.add_assignment( + can_store_p0, + can_store_next.when(stq_issue_en).else_(can_store_curr), + ) + # The store conflicts with any load + Reduce(em, store_conflict, st_ld_conflict_p0, BinOp.OR) + em.add_assignment(store_en, ~store_conflict & can_store_p0) + + em.add_assignment(store_idx, stq_issue) + + # Bypass + stq_last_oh = LogicVec( + em, "stq_last_oh", "w", self.configs.numStqEntries + ) + BitsToOHSub1(em, stq_last_oh, stq_tail) + for i in range(0, self.configs.numLdqEntries): + bypass_en_vec = LogicVec( + em, f"bypass_en_vec_{i}", "w", self.configs.numStqEntries + ) + # Search for the youngest store that is older than the load and conflicts + CyclicPriorityMasking( + em, bypass_idx_oh_p0[i], ld_st_conflict[i], stq_last_oh, True + ) + # Check if the youngest conflict store can bypass with the load + em.add_assignment( + bypass_en_vec, bypass_idx_oh_p0[i] & can_bypass[i] + ) + Reduce(em, bypass_en[i], bypass_en_vec, BinOp.OR) + else: + addr_valid = LogicVecArray( + em, + "addr_valid", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + addr_same = LogicVecArray( + em, + "addr_same", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_valid, i, j), + Val(ldq_addr_valid, i) & Val(stq_addr_valid, j), + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_same, i, j), + Bit(1) + .when(Val(ldq_addr, i) == Val(stq_addr, j)) + .else_(Bit(0)), + ) + + # A load conflicts with a store when: + # 1. The store entry is valid, and + # 2. The store is older than the load, and + # 3. The address conflicts(same or invalid store address). + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (ld_st_conflict, i, j), + Val(stq_alloc, j) + & Val(store_is_older, i, j) + & (Val(addr_same, i, j) | ~Val(stq_addr_valid, j)), + ) + + # A conflicting store entry can be bypassed to a load entry when: + # 1. The load entry is valid, and + # 2. The load entry is not issued yet, and + # 3. The address of the load-store pair are both valid and values the same. + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (can_bypass_p0, i, j), + Val(ldq_alloc, i) + & Val(stq_data_valid, j) + & Val(addr_same, i, j) + & Val(addr_valid, i, j), + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (can_bypass, i, j), + ~Val(ldq_issue, i) & Val(can_bypass_p0, i, j), + ) + + # Load + + load_conflict = LogicArray( + em, "load_conflict", "w", self.configs.numLdqEntries + ) + load_req_valid = LogicArray( + em, "load_req_valid", "w", self.configs.numLdqEntries + ) + can_load = LogicArray(em, "can_load", "w", self.configs.numLdqEntries) + can_load_p0 = LogicArray( + em, "can_load_p0", "r", self.configs.numLdqEntries + ) + can_load_p0.regInit(init=[0] * self.configs.numLdqEntries) + + # The load conflicts with any store + for i in range(0, self.configs.numLdqEntries): + Reduce(em, load_conflict[i], ld_st_conflict[i], BinOp.OR) + # The load is valid when the entry is valid and not yet issued, the load address should also be valid. + # We do not need to check ldq_data_valid, since unissued load request cannot have valid data. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + load_req_valid[i], ldq_alloc[i] & ldq_addr_valid[i] + ) + # Generate list for loads that does not face dependency issue + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load_p0[i], ~load_conflict[i] & load_req_valid[i] + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment(can_load[i], ~ldq_issue[i] & can_load_p0[i]) + ldq_head_oh_p0 = LogicVec( + em, "ldq_head_oh_p0", "r", self.configs.numLdqEntries + ) + ldq_head_oh_p0.regInit() + em.add_assignment(ldq_head_oh_p0, ldq_head_oh) + + can_load_list = [] + can_load_list.append(can_load) + for w in range(0, self.configs.numLdMem): + CyclicPriorityMasking( + em, load_idx_oh[w], can_load_list[w], ldq_head_oh_p0 + ) + Reduce(em, load_en[w], can_load_list[w], BinOp.OR) + if w + 1 != self.configs.numLdMem: + load_idx_oh_LogicArray = LogicArray( + em, + f"load_idx_oh_Array_{w+1}", + "w", + self.configs.numLdqEntries, + ) + arch += VecToArray(em, load_idx_oh_LogicArray, load_idx_oh[w]) + can_load_list.append( + LogicArray( + em, + f"can_load_list_{w+1}", + "w", + self.configs.numLdqEntries, + ) + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load_list[w + 1][i], + ~load_idx_oh_LogicArray[i] & can_load_list[w][i], + ) + + # Store + stq_issue_en_p0 = Logic(em, "stq_issue_en_p0", "r") + stq_issue_next = LogicVec( + em, "stq_issue_next", "w", self.configs.stqAddrW + ) + + store_conflict = Logic(em, "store_conflict", "w") + + can_store_curr = Logic(em, "can_store_curr", "w") + st_ld_conflict_curr = LogicVec( + em, "st_ld_conflict_curr", "w", self.configs.numLdqEntries + ) + store_valid_curr = Logic(em, "store_valid_curr", "w") + store_data_valid_curr = Logic(em, "store_data_valid_curr", "w") + store_addr_valid_curr = Logic(em, "store_addr_valid_curr", "w") + + can_store_next = Logic(em, "can_store_next", "w") + st_ld_conflict_next = LogicVec( + em, "st_ld_conflict_next", "w", self.configs.numLdqEntries + ) + store_valid_next = Logic(em, "store_valid_next", "w") + store_data_valid_next = Logic(em, "store_data_valid_next", "w") + store_addr_valid_next = Logic(em, "store_addr_valid_next", "w") + + can_store_p0 = Logic(em, "can_store_p0", "r") + st_ld_conflict_p0 = LogicVec( + em, "st_ld_conflict_p0", "r", self.configs.numLdqEntries + ) + + stq_issue_en_p0.regInit(init=0) + can_store_p0.regInit(init=0) + st_ld_conflict_p0.regInit() + + em.add_assignment(stq_issue_en_p0, stq_issue_en) + WrapAddConst( + em, stq_issue_next, stq_issue, 1, self.configs.numStqEntries + ) + + # A store conflicts with a load when: + # 1. The load entry is valid, and + # 2. The load is older than the store, and + # 3. The address conflicts(same or invalid store address). + # Index order are reversed for store matrix. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + (st_ld_conflict_curr, i), + Val(ldq_alloc, i) + & ~Val(em.mux_index(store_is_older[i], stq_issue)) + & ( + Val(em.mux_index(addr_same[i], stq_issue)) + | ~Val(ldq_addr_valid, i) + ), + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + (st_ld_conflict_next, i), + Val(ldq_alloc, i) + & ~Val(em.mux_index(store_is_older[i], stq_issue_next)) + & ( + Val(em.mux_index(addr_same[i], stq_issue_next)) + | ~Val(ldq_addr_valid, i) + ), + ) + # The store is valid whe the entry is valid and the data is also valid, + # the store address should also be valid + MuxLookUp(em, store_valid_curr, stq_alloc, stq_issue) + MuxLookUp(em, store_data_valid_curr, stq_data_valid, stq_issue) + MuxLookUp(em, store_addr_valid_curr, stq_addr_valid, stq_issue) + em.add_assignment( + can_store_curr, + store_valid_curr & store_data_valid_curr & store_addr_valid_curr, + ) + MuxLookUp(em, store_valid_next, stq_alloc, stq_issue_next) + MuxLookUp(em, store_data_valid_next, stq_data_valid, stq_issue_next) + MuxLookUp(em, store_addr_valid_next, stq_addr_valid, stq_issue_next) + em.add_assignment( + can_store_next, + store_valid_next & store_data_valid_next & store_addr_valid_next, + ) + # Multiplex from current and next + em.add_assignment( + st_ld_conflict_p0, + st_ld_conflict_next.when(stq_issue_en).else_(st_ld_conflict_curr), + ) + em.add_assignment( + can_store_p0, + can_store_next.when(stq_issue_en).else_(can_store_curr), + ) + # The store conflicts with any load + Reduce(em, store_conflict, st_ld_conflict_p0, BinOp.OR) + em.add_assignment(store_en, ~store_conflict & can_store_p0) + + em.add_assignment(store_idx, stq_issue) + + # Bypass + stq_last_oh = LogicVec( + em, "stq_last_oh", "w", self.configs.numStqEntries + ) + BitsToOHSub1(em, stq_last_oh, stq_tail) + for i in range(0, self.configs.numLdqEntries): + bypass_en_vec = LogicVec( + em, f"bypass_en_vec_{i}", "w", self.configs.numStqEntries + ) + # Search for the youngest store that is older than the load and conflicts + CyclicPriorityMasking( + em, bypass_idx_oh_p0[i], ld_st_conflict[i], stq_last_oh, True + ) + # Check if the youngest conflict store can bypass with the load + em.add_assignment( + bypass_en_vec, bypass_idx_oh_p0[i] & can_bypass[i] + ) + Reduce(em, bypass_en[i], bypass_en_vec, BinOp.OR) + else: + ###### Dependency Check ###### + + load_idx_oh = LogicVecArray( + em, + "load_idx_oh", + "w", + self.configs.numLdMem, + self.configs.numLdqEntries, + ) + load_en = LogicArray(em, "load_en", "w", self.configs.numLdMem) + + # Multiple store channels not yet implemented + assert self.configs.numStMem == 1 + store_idx = LogicVec(em, "store_idx", "w", self.configs.stqAddrW) + store_en = Logic(em, "store_en", "w") + + bypass_idx_oh = LogicVecArray( + em, + "bypass_idx_oh", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + bypass_en = LogicArray(em, "bypass_en", "w", self.configs.numLdqEntries) + + # Matrix Generation + ld_st_conflict = LogicVecArray( + em, + "ld_st_conflict", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + can_bypass = LogicVecArray( + em, + "can_bypass", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + if self.configs.pipeComp: + ldq_alloc_pcomp = LogicArray( + em, "ldq_alloc_pcomp", "r", self.configs.numLdqEntries + ) + ldq_addr_valid_pcomp = LogicArray( + em, "ldq_addr_valid_pcomp", "r", self.configs.numLdqEntries + ) + stq_alloc_pcomp = LogicArray( + em, "stq_alloc_pcomp", "r", self.configs.numStqEntries + ) + stq_addr_valid_pcomp = LogicArray( + em, "stq_addr_valid_pcomp", "r", self.configs.numStqEntries + ) + stq_data_valid_pcomp = LogicArray( + em, "stq_data_valid_pcomp", "r", self.configs.numStqEntries + ) + addr_valid_pcomp = LogicVecArray( + em, + "addr_valid_pcomp", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + addr_same_pcomp = LogicVecArray( + em, + "addr_same_pcomp", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + store_is_older_pcomp = LogicVecArray( + em, + "store_is_older_pcomp", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + ldq_alloc_pcomp.regInit(init=[0] * self.configs.numLdqEntries) + ldq_addr_valid_pcomp.regInit() + stq_alloc_pcomp.regInit(init=[0] * self.configs.numStqEntries) + stq_addr_valid_pcomp.regInit() + stq_data_valid_pcomp.regInit() + addr_same_pcomp.regInit() + store_is_older_pcomp.regInit() + + for i in range(0, self.configs.numLdqEntries): + em.add_assignment((ldq_alloc_pcomp, i), Val(ldq_alloc, i)) + em.add_assignment((ldq_addr_valid_pcomp, i), Val(ldq_addr_valid, i)) + for j in range(0, self.configs.numStqEntries): + em.add_assignment((stq_alloc_pcomp, j), Val(stq_alloc, j)) + em.add_assignment((stq_addr_valid_pcomp, j), Val(stq_addr_valid, j)) + em.add_assignment((stq_data_valid_pcomp, j), Val(stq_data_valid, j)) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (store_is_older_pcomp, i, j), Val(store_is_older, i, j) + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_valid_pcomp, i, j), + Val(ldq_addr_valid_pcomp, i) & Val(stq_addr_valid_pcomp, j), + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_same_pcomp, i, j), + Bit(1) + .when(Val(ldq_addr, i) == Val(stq_addr, j)) + .else_(Bit(0)), + ) + + # A load conflicts with a store when: + # 1. The store entry is valid, and + # 2. The store is older than the load, and + # 3. The address conflicts(same or invalid store address). + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (ld_st_conflict, i, j), + Val(stq_alloc_pcomp, j) + & Val(store_is_older_pcomp, i, j) + & ( + Val(addr_same_pcomp, i, j) + | ~Val(stq_addr_valid_pcomp, j) + ), + ) + + # A conflicting store entry can be bypassed to a load entry when: + # 1. The load entry is valid, and + # 2. The load entry is not issued yet, and + # 3. The address of the load-store pair are both valid and values the same. + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (can_bypass, i, j), + Val(ldq_alloc_pcomp, i) + & ~Val(ldq_issue, i) + & Val(stq_data_valid_pcomp, j) + & Val(addr_same_pcomp, i, j) + & Val(addr_valid_pcomp, i, j), + ) + + # Load + + load_conflict = LogicArray( + em, "load_conflict", "w", self.configs.numLdqEntries + ) + load_req_valid = LogicArray( + em, "load_req_valid", "w", self.configs.numLdqEntries + ) + can_load = LogicArray(em, "can_load", "w", self.configs.numLdqEntries) + + # The load conflicts with any store + for i in range(0, self.configs.numLdqEntries): + Reduce(em, load_conflict[i], ld_st_conflict[i], BinOp.OR) + # The load is valid when the entry is valid and not yet issued, the load address should also be valid. + # We do not need to check ldq_data_valid, since unissued load request cannot have valid data. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + load_req_valid[i], + ldq_alloc_pcomp[i] & ~ldq_issue[i] & ldq_addr_valid_pcomp[i], + ) + # Generate list for loads that does not face dependency issue + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load[i], ~load_conflict[i] & load_req_valid[i] + ) + + can_load_list = [] + can_load_list.append(can_load) + for w in range(0, self.configs.numLdMem): + CyclicPriorityMasking( + em, load_idx_oh[w], can_load_list[w], ldq_head_oh + ) + Reduce(em, load_en[w], can_load_list[w], BinOp.OR) + if w + 1 != self.configs.numLdMem: + load_idx_oh_LogicArray = LogicArray( + em, + f"load_idx_oh_Array_{w+1}", + "w", + self.configs.numLdqEntries, + ) + VecToArray(em, load_idx_oh_LogicArray, load_idx_oh[w]) + can_load_list.append( + LogicArray( + em, + f"can_load_list_{w+1}", + "w", + self.configs.numLdqEntries, + ) + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load_list[w + 1][i], + ~load_idx_oh_LogicArray[i] & can_load_list[w][i], + ) + + # Store + + st_ld_conflict = LogicVec( + em, "st_ld_conflict", "w", self.configs.numLdqEntries + ) + store_conflict = Logic(em, "store_conflict", "w") + store_valid = Logic(em, "store_valid", "w") + store_data_valid = Logic(em, "store_data_valid", "w") + store_addr_valid = Logic(em, "store_addr_valid", "w") + + # A store conflicts with a load when: + # 1. The load entry is valid, and + # 2. The load is older than the store, and + # 3. The address conflicts(same or invalid store address). + # Index order are reversed for store matrix. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + (st_ld_conflict, i), + Val(ldq_alloc_pcomp, i) + & ~Val(em.mux_index(store_is_older_pcomp[i], stq_issue)) + & ( + Val(em.mux_index(addr_same_pcomp[i], stq_issue)) + | ~Val(ldq_addr_valid_pcomp, i) + ), + ) + # The store conflicts with any load + Reduce(em, store_conflict, st_ld_conflict, BinOp.OR) + # The store is valid whe the entry is valid and the data is also valid, + # the store address should also be valid + MuxLookUp(em, store_valid, stq_alloc_pcomp, stq_issue) + MuxLookUp(em, store_data_valid, stq_data_valid_pcomp, stq_issue) + MuxLookUp(em, store_addr_valid, stq_addr_valid_pcomp, stq_issue) + em.add_assignment( + store_en, + ~store_conflict & store_valid & store_data_valid & store_addr_valid, + ) + em.add_assignment(store_idx, stq_issue) + + stq_last_oh = LogicVec( + em, "stq_last_oh", "w", self.configs.numStqEntries + ) + BitsToOHSub1(em, stq_last_oh, stq_tail) + for i in range(0, self.configs.numLdqEntries): + bypass_en_vec = LogicVec( + em, f"bypass_en_vec_{i}", "w", self.configs.numStqEntries + ) + # Search for the youngest store that is older than the load and conflicts + CyclicPriorityMasking( + em, bypass_idx_oh[i], ld_st_conflict[i], stq_last_oh, True + ) + # Check if the youngest conflict store can bypass with the load + em.add_assignment(bypass_en_vec, bypass_idx_oh[i] & can_bypass[i]) + Reduce(em, bypass_en[i], bypass_en_vec, BinOp.OR) + else: + addr_valid = LogicVecArray( + em, + "addr_valid", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + addr_same = LogicVecArray( + em, + "addr_same", + "w", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_valid, i, j), + Val(ldq_addr_valid, i) & Val(stq_addr_valid, j), + ) + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (addr_same, i, j), + Bit(1) + .when(Val(ldq_addr, i) == Val(stq_addr, j)) + .else_(Bit(0)), + ) + + # A load conflicts with a store when: + # 1. The store entry is valid, and + # 2. The store is older than the load, and + # 3. The address conflicts(same or invalid store address). + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (ld_st_conflict, i, j), + Val(stq_alloc, j) + & Val(store_is_older, i, j) + & (Val(addr_same, i, j) | ~Val(stq_addr_valid, j)), + ) + + # A conflicting store entry can be bypassed to a load entry when: + # 1. The load entry is valid, and + # 2. The load entry is not issued yet, and + # 3. The address of the load-store pair are both valid and values the same. + for i in range(0, self.configs.numLdqEntries): + for j in range(0, self.configs.numStqEntries): + em.add_assignment( + (can_bypass, i, j), + Val(ldq_alloc, i) + & ~Val(ldq_issue, i) + & Val(stq_data_valid, j) + & Val(addr_same, i, j) + & Val(addr_valid, i, j), + ) + + # Load + + load_conflict = LogicArray( + em, "load_conflict", "w", self.configs.numLdqEntries + ) + load_req_valid = LogicArray( + em, "load_req_valid", "w", self.configs.numLdqEntries + ) + can_load = LogicArray(em, "can_load", "w", self.configs.numLdqEntries) + + # The load conflicts with any store + for i in range(0, self.configs.numLdqEntries): + Reduce(em, load_conflict[i], ld_st_conflict[i], BinOp.OR) + # The load is valid when the entry is valid and not yet issued, the load address should also be valid. + # We do not need to check ldq_data_valid, since unissued load request cannot have valid data. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + load_req_valid[i], + ldq_alloc[i] & ~ldq_issue[i] & ldq_addr_valid[i], + ) + # Generate list for loads that does not face dependency issue + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load[i], ~load_conflict[i] & load_req_valid[i] + ) + + can_load_list = [] + can_load_list.append(can_load) + for w in range(0, self.configs.numLdMem): + CyclicPriorityMasking( + em, load_idx_oh[w], can_load_list[w], ldq_head_oh + ) + Reduce(em, load_en[w], can_load_list[w], BinOp.OR) + if w + 1 != self.configs.numLdMem: + load_idx_oh_LogicArray = LogicArray( + em, + f"load_idx_oh_Array_{w+1}", + "w", + self.configs.numLdqEntries, + ) + VecToArray(em, load_idx_oh_LogicArray, load_idx_oh[w]) + can_load_list.append( + LogicArray( + em, + f"can_load_list_{w+1}", + "w", + self.configs.numLdqEntries, + ) + ) + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + can_load_list[w + 1][i], + ~load_idx_oh_LogicArray[i] & can_load_list[w][i], + ) + # Store + + st_ld_conflict = LogicVec( + em, "st_ld_conflict", "w", self.configs.numLdqEntries + ) + store_conflict = Logic(em, "store_conflict", "w") + store_valid = Logic(em, "store_valid", "w") + store_data_valid = Logic(em, "store_data_valid", "w") + store_addr_valid = Logic(em, "store_addr_valid", "w") + + # A store conflicts with a load when: + # 1. The load entry is valid, and + # 2. The load is older than the store, and + # 3. The address conflicts(same or invalid store address). + # Index order are reversed for store matrix. + for i in range(0, self.configs.numLdqEntries): + em.add_assignment( + (st_ld_conflict, i), + Val(ldq_alloc, i) + & ~Val(em.mux_index(store_is_older[i], stq_issue)) + & ( + Val(em.mux_index(addr_same[i], stq_issue)) + | ~Val(ldq_addr_valid, i) + ), + ) + # The store conflicts with any load + Reduce(em, store_conflict, st_ld_conflict, BinOp.OR) + # The store is valid whe the entry is valid and the data is also valid, + # the store address should also be valid + MuxLookUp(em, store_valid, stq_alloc, stq_issue) + MuxLookUp(em, store_data_valid, stq_data_valid, stq_issue) + MuxLookUp(em, store_addr_valid, stq_addr_valid, stq_issue) + em.add_assignment( + store_en, + ~store_conflict & store_valid & store_data_valid & store_addr_valid, + ) + em.add_assignment(store_idx, stq_issue) + + stq_last_oh = LogicVec( + em, "stq_last_oh", "w", self.configs.numStqEntries + ) + BitsToOHSub1(em, stq_last_oh, stq_tail) + for i in range(0, self.configs.numLdqEntries): + bypass_en_vec = LogicVec( + em, f"bypass_en_vec_{i}", "w", self.configs.numStqEntries + ) + # Search for the youngest store that is older than the load and conflicts + CyclicPriorityMasking( + em, bypass_idx_oh[i], ld_st_conflict[i], stq_last_oh, True + ) + # Check if the youngest conflict store can bypass with the load + em.add_assignment(bypass_en_vec, bypass_idx_oh[i] & can_bypass[i]) + Reduce(em, bypass_en[i], bypass_en_vec, BinOp.OR) + + if self.configs.pipe1: + # Pipeline Stage 1 + load_idx_oh_p1 = LogicVecArray( + em, + "load_idx_oh_p1", + "r", + self.configs.numLdMem, + self.configs.numLdqEntries, + ) + load_en_p1 = LogicArray(em, "load_en_p1", "r", self.configs.numLdMem) + + load_hs = LogicArray(em, "load_hs", "w", self.configs.numLdMem) + load_p1_ready = LogicArray(em, "load_p1_ready", "w", self.configs.numLdMem) + + store_idx_p1 = LogicVec(em, "store_idx_p1", "r", self.configs.stqAddrW) + store_en_p1 = Logic(em, "store_en_p1", "r") + + store_hs = Logic(em, "store_hs", "w") + store_p1_ready = Logic(em, "store_p1_ready", "w") + + bypass_idx_oh_p1 = LogicVecArray( + em, + "bypass_idx_oh_p1", + "r", + self.configs.numLdqEntries, + self.configs.numStqEntries, + ) + bypass_en_p1 = LogicArray( + em, "bypass_en_p1", "r", self.configs.numLdqEntries + ) + + load_idx_oh_p1.regInit(enable=load_p1_ready) + load_en_p1.regInit(init=[0] * self.configs.numLdMem, enable=load_p1_ready) + + store_idx_p1.regInit(enable=store_p1_ready) + store_en_p1.regInit(init=0, enable=store_p1_ready) + + bypass_idx_oh_p1.regInit() + bypass_en_p1.regInit(init=[0] * self.configs.numLdqEntries) + + for w in range(0, self.configs.numLdMem): + em.add_assignment(load_hs[w], load_en_p1[w] & rreq_ready_i[w]) + em.add_assignment(load_p1_ready[w], load_hs[w] | ~load_en_p1[w]) + + for w in range(0, self.configs.numLdMem): + em.add_assignment(load_idx_oh_p1[w], load_idx_oh[w]) + em.add_assignment(load_en_p1[w], load_en[w]) + + em.add_assignment(store_hs, store_en_p1 & wreq_ready_i[0]) + em.add_assignment(store_p1_ready, store_hs | ~store_en_p1) + + em.add_assignment(store_idx_p1, store_idx) + em.add_assignment(store_en_p1, store_en) + + if self.configs.pipe0: + for i in range(0, self.configs.numLdqEntries): + em.add_assignment(bypass_idx_oh_p1[i], bypass_idx_oh_p0[i]) + else: + for i in range(0, self.configs.numLdqEntries): + em.add_assignment(bypass_idx_oh_p1[i], bypass_idx_oh[i]) + + for i in range(0, self.configs.numLdqEntries): + em.add_assignment(bypass_en_p1[i], bypass_en[i]) + + ###### Read/Write ###### + # Read Request + for w in range(0, self.configs.numLdMem): + em.add_assignment(rreq_valid_o[w], load_en_p1[w]) + OHToBits(em, rreq_id_o[w], load_idx_oh_p1[w]) + Mux1H(em, rreq_addr_o[w], ldq_addr, load_idx_oh_p1[w]) + + for i in range(0, self.configs.numLdqEntries): + ldq_issue_set_vec = LogicVec( + em, f"ldq_issue_set_vec_{i}", "w", self.configs.numLdMem + ) + for w in range(0, self.configs.numLdMem): + em.add_assignment( + (ldq_issue_set_vec, w), + (Val(load_idx_oh, w, i) & Val(load_p1_ready, w)) + | Val(bypass_en, i), + ) + Reduce(em, ldq_issue_set[i], ldq_issue_set_vec, BinOp.OR) + + # Write Request + em.add_assignment(wreq_valid_o[0], store_en_p1) + em.add_assignment(wreq_id_o[0], 0) + MuxLookUp(em, wreq_addr_o[0], stq_addr, store_idx_p1) + MuxLookUp(em, wreq_data_o[0], stq_data, store_idx_p1) + em.add_assignment(stq_issue_en, store_en & store_p1_ready) + + # Read Response and Bypass + for i in range(0, self.configs.numLdqEntries): + # check each read response channel for each load + read_idx_oh = LogicArray( + em, f"read_idx_oh_{i}", "w", self.configs.numLdMem + ) + read_valid = Logic(em, f"read_valid_{i}", "w") + read_data = LogicVec(em, f"read_data_{i}", "w", self.configs.dataW) + for w in range(0, self.configs.numLdMem): + em.add_assignment( + read_idx_oh[w], + rresp_valid_i[w].when( + (rresp_id_i[w] == Val(i, self.configs.idW)).else_(Bit(0)) + ), + ) + Mux1H(em, read_data, rresp_data_i, read_idx_oh) + Reduce(em, read_valid, read_idx_oh, BinOp.OR) + # multiplex from store queue data + bypass_data = LogicVec(em, f"bypass_data_{i}", "w", self.configs.dataW) + Mux1H(em, bypass_data, stq_data, bypass_idx_oh_p1[i]) + # multiplex from read and bypass data + em.add_assignment(ldq_data[i], read_data | bypass_data) + em.add_assignment(ldq_data_wen[i], bypass_en_p1[i] | read_valid) + for w in range(0, self.configs.numLdMem): + em.add_assignment(rresp_ready_o[w], Bit(1)) + + # Write Response + if self.configs.stResp: + for i in range(0, self.configs.numStqEntries): + em.add_assignment( + stq_exec_set[i], + wresp_valid_i[0] + .when((stq_resp == Val(i, self.configs.stqAddrW))) + .else_(Bit(0)), + ) + else: + for i in range(0, self.configs.numStqEntries): + em.add_assignment( + stq_reset[i], + wresp_valid_i[0] + .when((stq_resp == Val(i, self.configs.stqAddrW))) + .else_(Bit(0)), + ) + + em.add_assignment(stq_resp_en, wresp_valid_i[0]) + em.add_assignment(wresp_ready_o[0], Bit(1)) + else: + ###### Read/Write ###### + # Read Request + for w in range(0, self.configs.numLdMem): + em.add_assignment(rreq_valid_o[w], load_en[w]) + OHToBits(em, rreq_id_o[w], load_idx_oh[w]) + Mux1H(em, rreq_addr_o[w], ldq_addr, load_idx_oh[w]) + + for i in range(0, self.configs.numLdqEntries): + ldq_issue_set_vec = LogicVec( + em, f"ldq_issue_set_vec_{i}", "w", self.configs.numLdMem + ) + for w in range(0, self.configs.numLdMem): + em.add_assignment( + (ldq_issue_set_vec, w), + ( + Val(load_idx_oh, w, i) + & Val(rreq_ready_i, w) + & Val(load_en, w) + ) + | Val(bypass_en, i), + ) + Reduce(em, ldq_issue_set[i], ldq_issue_set_vec, BinOp.OR) + + # Write Request + em.add_assignment(wreq_valid_o[0], store_en) + em.add_assignment(wreq_id_o[0], Val(0)) + MuxLookUp(em, wreq_addr_o[0], stq_addr, store_idx) + MuxLookUp(em, wreq_data_o[0], stq_data, store_idx) + em.add_assignment(stq_issue_en, store_en & wreq_ready_i[0]) + + # Read Response and Bypass + for i in range(0, self.configs.numLdqEntries): + # check each read response channel for each load + read_idx_oh = LogicArray( + em, f"read_idx_oh_{i}", "w", self.configs.numLdMem + ) + read_valid = Logic(em, f"read_valid_{i}", "w") + read_data = LogicVec(em, f"read_data_{i}", "w", self.configs.dataW) + for w in range(0, self.configs.numLdMem): + em.add_assignment( + read_idx_oh[w], + rresp_valid_i[w] + .when(rresp_id_i[w] == Val(i, self.configs.idW)) + .else_(Bit(0)), + ) + Mux1H(em, read_data, rresp_data_i, read_idx_oh) + Reduce(em, read_valid, read_idx_oh, BinOp.OR) + # multiplex from store queue data + bypass_data = LogicVec(em, f"bypass_data_{i}", "w", self.configs.dataW) + if self.configs.pipe0: + Mux1H(em, bypass_data, stq_data, bypass_idx_oh_p0[i]) + else: + Mux1H(em, bypass_data, stq_data, bypass_idx_oh[i]) + # multiplex from read and bypass data + em.add_assignment(ldq_data[i], read_data | bypass_data) + em.add_assignment(ldq_data_wen[i], bypass_en[i] | read_valid) + for w in range(0, self.configs.numLdMem): + em.add_assignment(rresp_ready_o[w], Bit(1)) + + # Write Response + if self.configs.stResp: + for i in range(0, self.configs.numStqEntries): + em.add_assignment( + stq_exec_set[i], + wresp_valid_i[0] + .when(stq_resp == Val(i, self.configs.stqAddrW)) + .else_(Bit(0)), + ) + else: + for i in range(0, self.configs.numStqEntries): + em.add_assignment( + stq_reset[i], + wresp_valid_i[0] + .when(stq_resp == Val(i, self.configs.stqAddrW)) + .else_(Bit(0)), + ) + em.add_assignment(stq_resp_en, wresp_valid_i[0]) + em.add_assignment(wresp_ready_o[0], Bit(1)) + + # Write to the file + output_str = em.get_definition_str(self.module_name) + with open(f"{path_rtl}/{self.name}.{em.get_file_suffix()}", "a") as file: + file.write(output_str) + + def instantiate(self, **kwargs) -> str: + """ + *Instantiation of LSQ is in lsq-generator.py. + """ + pass diff --git a/tools/backend/lsq-generator-python/vhdl_gen/generators/lsq_submodule_wrapper.py b/tools/backend/lsq-generator-python/core_gen/generators/lsq_submodule_wrapper.py similarity index 90% rename from tools/backend/lsq-generator-python/vhdl_gen/generators/lsq_submodule_wrapper.py rename to tools/backend/lsq-generator-python/core_gen/generators/lsq_submodule_wrapper.py index 799a8f5fbb..b7628d69cf 100644 --- a/tools/backend/lsq-generator-python/vhdl_gen/generators/lsq_submodule_wrapper.py +++ b/tools/backend/lsq-generator-python/core_gen/generators/lsq_submodule_wrapper.py @@ -1,38 +1,38 @@ -import vhdl_gen.generators.group_allocator as group_allocator -import vhdl_gen.generators.dispatchers as dispatchers - - -class LSQ_Submodules: - """ - Save LSQ (Load-Store Queue) submodule instances. - - This class acts as a simple struct to group together all the generator objects - required to build a complete LSQ. An instance of this class is created by the - codegen.py script, and then passed to the LSQ generator. - - Attributes: - group_allocator (group_allocator.GroupAllocator): - The generator instance for the Group Allocator module. - ptq_dispatcher_lda (dispatchers.PortToQueueDispatcher): - The Port-to-Queue dispatcher for the Load Address (LDA) channel. - qtp_dispatcher_ldd (dispatchers.QueueToPortDispatcher): - The Queue-to-Port dispatcher for the Load Data (LDD) channel. - ptq_dispatcher_sta (dispatchers.PortToQueueDispatcher): - The Port-to-Queue dispatcher for the Store Address (STA) channel. - ptq_dispatcher_std (dispatchers.PortToQueueDispatcher): - The Port-to-Queue dispatcher for the Store Data (STD) channel. - qtp_dispatcher_stb (dispatchers.QueueToPortDispatcher): - The optional Queue-to-Port dispatcher for the Store Backward/Response - (STB) channel. This is only instantiated if store responses are - enabled in the configuration. - """ - - def __init__(self): - self.group_allocator: group_allocator.GroupAllocator = None - self.ptq_dispatcher_lda: dispatchers.PortToQueueDispatcher = None - self.qtp_dispatcher_ldd: dispatchers.QueueToPortDispatcher = None - self.ptq_dispatcher_sta: dispatchers.PortToQueueDispatcher = None - self.ptq_dispatcher_std: dispatchers.PortToQueueDispatcher = None - - # Optional (stResp = True) - self.qtp_dispatcher_stb: dispatchers.QueueToPortDispatcher = None +import core_gen.generators.group_allocator as group_allocator +import core_gen.generators.dispatchers as dispatchers + + +class LSQ_Submodules: + """ + Save LSQ (Load-Store Queue) submodule instances. + + This class acts as a simple struct to group together all the generator objects + required to build a complete LSQ. An instance of this class is created by the + codegen.py script, and then passed to the LSQ generator. + + Attributes: + group_allocator (group_allocator.GroupAllocator): + The generator instance for the Group Allocator module. + ptq_dispatcher_lda (dispatchers.PortToQueueDispatcher): + The Port-to-Queue dispatcher for the Load Address (LDA) channel. + qtp_dispatcher_ldd (dispatchers.QueueToPortDispatcher): + The Queue-to-Port dispatcher for the Load Data (LDD) channel. + ptq_dispatcher_sta (dispatchers.PortToQueueDispatcher): + The Port-to-Queue dispatcher for the Store Address (STA) channel. + ptq_dispatcher_std (dispatchers.PortToQueueDispatcher): + The Port-to-Queue dispatcher for the Store Data (STD) channel. + qtp_dispatcher_stb (dispatchers.QueueToPortDispatcher): + The optional Queue-to-Port dispatcher for the Store Backward/Response + (STB) channel. This is only instantiated if store responses are + enabled in the configuration. + """ + + def __init__(self): + self.group_allocator: group_allocator.GroupAllocator = None + self.ptq_dispatcher_lda: dispatchers.PortToQueueDispatcher = None + self.qtp_dispatcher_ldd: dispatchers.QueueToPortDispatcher = None + self.ptq_dispatcher_sta: dispatchers.PortToQueueDispatcher = None + self.ptq_dispatcher_std: dispatchers.PortToQueueDispatcher = None + + # Optional (stResp = True) + self.qtp_dispatcher_stb: dispatchers.QueueToPortDispatcher = None diff --git a/tools/backend/lsq-generator-python/core_gen/ir.py b/tools/backend/lsq-generator-python/core_gen/ir.py new file mode 100644 index 0000000000..4efb7d0074 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/ir.py @@ -0,0 +1,291 @@ +from enum import Enum + + +class Statement: + """ + Represents a statement base class. + + This class contains common operator overloads and helpers used across the + generator. + """ + + def __add__(self, other): + return Bin(self, BinOp.ADD, other) + + def __sub__(self, other): + return Bin(self, BinOp.SUB, other) + + def __and__(self, other): + return Bin(self, BinOp.AND, other) + + def __or__(self, other): + return Bin(self, BinOp.OR, other) + + def __xor__(self, other): + return Bin(self, BinOp.XOR, other) + + def __mul__(self, other): + return Bin(self, BinOp.MUL, other) + + def __invert__(self): + return Un(UnOp.NOT, self) + + def __ge__(self, other): + return Bin(self, BinOp.GE, other) + + def __le__(self, other): + return Bin(self, BinOp.LE, other) + + def __gt__(self, other): + return Bin(self, BinOp.GT, other) + + def __lt__(self, other): + return Bin(self, BinOp.LT, other) + + def __eq__(self, other): + return Bin(self, BinOp.EQ, other) + + def __ne__(self, other): + return Bin(self, BinOp.NEQ, other) + + def concat(self, other): + return Bin(self, BinOp.CONCAT, other) + + def to_str(self, em: "Emitter", meta: "Meta") -> str: + if self.get_precedence() <= meta.precedence: + return f"({self._to_str(em, meta)})" + else: + return self._to_str(em, meta) + + def when(self, condition): + self.condition = condition + return self + + def else_(self, statement): + if getattr(self, "condition", None) is None: + raise ValueError("else_ can only be called after when") + + return WhenElse(self, self.condition, statement) + + def _to_str(self, em: "Emitter", meta: "Meta") -> str: + raise NotImplementedError("Subclasses must implement _to_str method") + + def get_type(self) -> str: + return Type.LOGIC + + def get_precedence(self) -> int: + # TODO: Cleaner way to handle precedence + return 10000 + + +class Type(Enum): + LOGIC = "logic" + ARITH = "arith" + BOOL = "bool" + ANY = "any" + + +class Val(Statement): + """ + Represents a value, which can be either a variable or a constant + Can be initialized with a string, an int, a tuple of (var, index), or a tuple of (var, index1, index2) + """ + + def __init__(self, *var, size=None): + self.var = var + self.size = size + + def _to_str(self, em: "Emitter", meta: "Meta"): + if self.size is not None: + size = self.size + else: + size = meta.size + + arg = self.var[0] if len(self.var) == 1 else tuple(self.var) + if type(arg) == str: + str_ret = arg + elif type(arg) == int: + str_ret = em.int_to_str(arg, size, meta=meta) + elif type(arg) == tuple: + if type(arg[0]) == int: + str_ret = em.int_to_str(arg[0], arg[1], meta=meta) + elif len(arg) == 2: + str_ret = arg[0].getNameRead(arg[1]) + else: + str_ret = arg[0].getNameRead(arg[1], arg[2]) + else: + str_ret = arg.getNameRead() + + return str_ret + + def get_type(self) -> str: + return Type.ANY if type(self.var[0]) == int else Type.LOGIC + + +class BinOp(Enum): + """Represents a binary operator""" + + ADD = ("+", 4, Type.ARITH, Type.ARITH) + SUB = ("-", 4, Type.ARITH, Type.ARITH) + AND = ("and", 3, Type.LOGIC, Type.LOGIC) + OR = ("or", 3, Type.LOGIC, Type.LOGIC) + XOR = ("xor", 3, Type.LOGIC, Type.LOGIC) + CONCAT = ("&", 3, Type.LOGIC, Type.LOGIC) + MUL = ("*", 5, Type.ARITH, Type.ARITH) + GE = (">=", 2, Type.BOOL, Type.ARITH) + LE = ("<=", 2, Type.BOOL, Type.ARITH) + GT = (">", 2, Type.BOOL, Type.ARITH) + LT = ("<", 2, Type.BOOL, Type.ARITH) + EQ = ("=", 1, Type.BOOL, Type.ANY) + NEQ = ("!=", 1, Type.BOOL, Type.ANY) + + def get_precedence(self) -> int: + return self.value[1] + + def get_type(self) -> str: + return self.value[2] + + def get_param_type(self) -> str: + return self.value[3] + + +class Bin(Statement): + """ + Represents a binary statement + """ + + def __init__(self, left, op, right): + self.left = left + self.op = op + self.right = right + + def get_precedence(self) -> int: + return self.op.get_precedence() + + def get_type(self) -> str: + return self.op.get_type() + + def get_param_type(self) -> str: + return self.op.get_param_type() + + def _to_str(self, em: "Emitter", meta: "Meta") -> str: + return em.bin_to_str(self, meta) + + +class UnOp(Enum): + """Represents a unary operator""" + + NOT = ("not", 10, Type.LOGIC, Type.LOGIC) + + def get_precedence(self) -> int: + return self.value[1] + + def get_type(self) -> str: + return self.value[2] + + def get_param_type(self) -> str: + return self.value[3] + + +class Un(Statement): + """ + Represents a unary statement + """ + + def __init__(self, op: UnOp, val: Statement): + self.op = op + self.val = val + + def get_precedence(self) -> int: + return self.op.get_precedence() + + def get_type(self) -> str: + return self.op.get_type() + + def get_param_type(self) -> str: + return self.op.get_param_type() + + def _to_str(self, em: "Emitter", meta: "Meta") -> str: + return em.un_to_str(self, meta) + + +class Bit(Statement): + """ + Represents a bit statement + """ + + def __init__(self, value: int): + if value not in (0, 1): + raise ValueError("Bit value must be 0 or 1") + self.value = value + + def get_precedence(self) -> int: + return 11 + + def _to_str(self, em: "Emitter", meta: "Meta") -> str: + return em.get_bit_str(self) + + +class CustomStatement(Statement): + """ + Represents a custom string statement + """ + + def __init__(self, vhdl_str, verilog_str): + self.vhdl_str = vhdl_str + self.verilog_str = verilog_str + + def _to_str(self, em: "Emitter", meta: "Meta") -> str: + return em.print_custom_str(self) + + +class WhenElse(Statement): + """ + Represents a when-else statement + """ + + def __init__( + self, + true_statement: Statement, + condition: Statement, + false_statement: Statement, + ): + self.condition = condition + self.true_statement = true_statement + self.false_statement = false_statement + + def get_precedence(self) -> int: + return 0 + + def _to_str(self, em: "Emitter", meta: "Meta") -> str: + return em.when_else_to_str(self, meta) + + def get_type(self) -> str: + if ( + self.true_statement.get_type() != self.false_statement.get_type() + and self.true_statement.get_type() != Type.ANY + and self.false_statement.get_type() != Type.ANY + ): + raise ValueError( + f"true_statement and false_statement must have the same type, got {self.true_statement.get_type()} and {self.false_statement.get_type()}" + ) + return self.true_statement.get_type() + +def reduce_bin(op: BinOp, statements: list) -> Statement: + """ + Reduces a list of statements into a single statement using the given binary operator. + The statements are combined in a left-associative manner. + + Args: + statements (list): A list of Statement objects to be reduced. + op (BinOp): The binary operator to use for reduction. + + Returns: + Statement: A single Statement object resulting from the reduction. + """ + if len(statements) == 0: + raise ValueError("Cannot reduce an empty list of statements") + elif len(statements) == 1: + return statements[0] + else: + return Bin(statements[0], op, reduce_bin(op, statements[1:])) \ No newline at end of file diff --git a/tools/backend/lsq-generator-python/core_gen/operators/__init__.py b/tools/backend/lsq-generator-python/core_gen/operators/__init__.py new file mode 100644 index 0000000000..94563dec2e --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/operators/__init__.py @@ -0,0 +1,23 @@ +# core_gen/operators/__init__.py +from core_gen.operators.arithmetic import WrapAdd, WrapAddConst, WrapSub +from core_gen.operators.shifts import CyclicLeftShift +from core_gen.operators.reduction import Reduce +from core_gen.operators.mux import Mux1H, Mux1HROM, MuxLookUp +from core_gen.operators.masking import CyclicPriorityMasking +from core_gen.operators.conversions import VecToArray, BitsToOH, BitsToOHSub1, OHToBits + +__all__ = [ + "WrapAdd", + "WrapAddConst", + "WrapSub", + "CyclicLeftShift", + "Reduce", + "Mux1H", + "Mux1HROM", + "MuxLookUp", + "CyclicPriorityMasking", + "VecToArray", + "BitsToOH", + "BitsToOHSub1", + "OHToBits", +] diff --git a/tools/backend/lsq-generator-python/core_gen/operators/arithmetic.py b/tools/backend/lsq-generator-python/core_gen/operators/arithmetic.py new file mode 100644 index 0000000000..db7f1cfbe4 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/operators/arithmetic.py @@ -0,0 +1,82 @@ +from core_gen.emitters import Emitter +from core_gen.ir import Val, Bit +from core_gen.utils import isPow2 +from core_gen.signals import * + + +def WrapAdd(em: Emitter, out, in_a, in_b, max: int) -> str: + """ + if "max" is power of 2: + out = in_a + in_b + else: + "sum", "res" -> one extra bit to extend the bit-width + Concatenates '0' to each input to extend the bit-width + + sum = in_a + in_b + + if sum >= max: + out = sum - max + else + out = sum + """ + + em.add_comment("WrapAdd Begin") + em.add_comment(f"WrapAdd({out.name}, {in_a.name}, {in_b.name}, {max})") + if isPow2(max): + em.add_assignment(out, in_a + in_b) + else: + em.use_temp() + sum = LogicVec(em, em.get_temp("sum"), "w", out.size + 1) + res = LogicVec(em, em.get_temp("res"), "w", out.size + 1) + em.add_assignment(sum, Bit(0).concat(in_a) + Bit(0).concat(in_b)) + em.add_assignment(res, (sum - Val(max)).when(sum >= Val(max)).else_(sum)) + em.add_assignment(out, em.slice_var(res.getNameRead(), out_size - 1, 0)) + em.add_comment("WrapAdd End\n") + + +def WrapAddConst(em: Emitter, out, in_a, const: int, max: int) -> str: + """ + if "max" is power of 2: + out = in_a + const + else: + if in_a + const >= max: + out = in_a + const - max + else: + out = in_a + const + """ + + em.add_comment("WrapAdd Begin") + em.add_comment(f"WrapAdd({out.name}, {in_a.name}, {const}, {max})") + + if isPow2(max): + em.add_assignment(out, in_a + Val(const)) + else: + em.add_assignment( + out, + (in_a - Val(max - const)) + .when(in_a >= Val(max - const)) + .else_(in_a + Val(const)), + ) + em.add_comment("WrapAdd End") + + +def WrapSub(em: Emitter, out, in_a, in_b, max: int) -> str: + """ + if "max" is power of 2: + out = in_a - in_b + else: + if in_a >= in_b: + out = in_a - in_b + else: + out = (in_a + max) - in_b + """ + + em.add_comment("WrapSub Begin") + em.add_comment(f"WrapSub({out.name}, {in_a.name}, {in_b.name}, {max})") + if isPow2(max): + em.add_assignment(out, in_a - in_b) + else: + em.add_assignment( + out, (in_a - in_b).when(in_a >= in_b).else_(in_a + Val(max) - in_b) + ) + em.add_comment("WrapSub End") diff --git a/tools/backend/lsq-generator-python/core_gen/operators/conversions.py b/tools/backend/lsq-generator-python/core_gen/operators/conversions.py new file mode 100644 index 0000000000..51d78c4775 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/operators/conversions.py @@ -0,0 +1,85 @@ +from core_gen.signals import * +from core_gen.ir import Val, Bit, BinOp +from core_gen.emitters import Emitter +from core_gen.operators import Reduce + + +def VecToArray(em: Emitter, dout, din) -> str: + """ + Converts LogicVec to LogicArray + + Parameter: + dout (LogicArray) + din (LogicVec) + + Example: + din = "0101" + dout = ('0'; '1'; '0'; '1') + """ + size = din.size + assert dout.length == size + for i in range(0, size): + em.add_assignment((dout, i), Val(din, i)) + + +def BitsToOH(em: Emitter, dout, din) -> str: + """ + Convert a binary vector into its one-hot representation in VHDL. + + Example: + din = "01" + dout = "0010" + """ + em.add_comment("Bits To One-Hot Begin") + em.add_comment(f"BitsToOH({dout.name}, {din.name})") + for i in range(0, dout.size): + em.add_assignment( + (dout, i), Bit(1).when(din == Val(i, size=din.size)).else_(Bit(0)) + ) + em.add_comment("Bits To One-Hot End\n") + + +def BitsToOHSub1(em: Emitter, dout, din) -> str: + """ + Convert a binary vector into its one-hot representation in VHDL. + The result one-hot representation should be cyclic right shifted. + + Example: + din = "01" + dout = "0001" + """ + em.add_comment("Bits To One-Hot Begin") + em.add_comment(f"BitsToOHSub1({dout.name}, {din.name})") + for i in range(0, dout.size): + em.add_assignment( + (dout, i), + Bit(1).when(din == Val((i + 1) % dout.size, size=din.size)).else_(Bit(0)), + ) + em.add_comment("Bits To One-Hot End\n") + + +def OHToBits(em: Emitter, dout, din) -> str: + """ + Generate VHDL code to convert a one-hot vector into its binary index. + + Example: + din = "0010" + dout = "01" + """ + + em.add_comment("One-Hot To Bits Begin") + em.add_comment(f"OHToBits({dout.name}, {din.name})") + size = dout.size + size_in = din.size + em.use_temp() + for i in range(0, size): + temp_in = LogicArray(em, em.get_temp(f"in_{i}"), "w", size_in) + temp_out = Logic(em, em.get_temp(f"out_{i}"), "w") + for j in range(0, size_in): + if (j // (2**i)) % 2 == 1: + em.add_assignment((temp_in, j), Val(din, j)) + else: + em.add_assignment((temp_in, j), Bit(0)) + Reduce(em, temp_out, temp_in, BinOp.OR, False) + em.add_assignment((dout, i), temp_out) + em.add_comment("One-Hot To Bits End\n") diff --git a/tools/backend/lsq-generator-python/core_gen/operators/masking.py b/tools/backend/lsq-generator-python/core_gen/operators/masking.py new file mode 100644 index 0000000000..7235d13704 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/operators/masking.py @@ -0,0 +1,128 @@ +from core_gen.emitters import Emitter +from core_gen.ir import Val + + +def CyclicPriorityMasking(em: Emitter, dout, din, base, reverse=False) -> str: + """ + Parameters: + dout (LogicVecArray, LogicArray, LogicVec): + Destination to write the masked result. + One youngest or oldest bit set to '1' and the other to '0' per each Array + din (LogicVecArray, LogicArray, LogicVec): + Input data to be masked. + base (LogicVec): + Binary pivot index for the rotation mask. + reverse (bool, optional): + Choose direction of masking. + False -> Find the oldest (Searching direction: base to MSB -> LSB to base) + True -> Find the youngest (Searching direction: base to LSB -> MSB to base) + + Example: + 1. din1 = 010110 2. din2 = 100100 3. din3 = 000110 + base = 001000 base = 001000 base = 001000 + reverse = False reverse = True reverse = False + + dout1= 010000 dout2= 000100 dout3= 00001 + (base to MSB) (base to LSB) (base to MSB -> LSB to base) + + Behavior (with the Example 1): + double_in = 010110 010110 + base = 000000 001000 + double_in - base = 010110 001110 + ~(double_in - base) = 101001 110001 + double_out = double_in & ~(double_in - base) + = 000000 010000 + dout = 000000 | 010000 + = 010000 + + Example (LogicVecArray din): + 1. din = 010 + 000 + 100 + 010 + base = 001 + reverse = False + + priority masking -> (0th col) [0010] with base = 1 -> 0010 + priority masking -> (1st col) [1001] with base = 1 -> 0001 + priority masking -> (2nd col) [0000] with base = 1 -> 0000 + + -> dout = 000 + 000 + 100 + 010 + """ + + em.add_comment("Priority Masking Begin") + em.add_comment(f"CyclicPriorityMask({dout.name}, {din.name}, {base.name})") + em.use_temp() + from core_gen.signals import LogicVecArray, LogicVec, LogicArray + + if isinstance(din, LogicVecArray): + assert reverse == False + for i in range(0, din.size): + size = din.length + double_in = LogicVec(em, em.get_temp(f"double_in_{i}"), "w", size * 2) + for j in range(0, size): + em.add_assignment((double_in, j), Val(din, j, i)) + em.add_assignment((double_in, j + size), Val(din, j, i)) + double_out = LogicVec(em, em.get_temp(f"double_out_{i}"), "w", size * 2) + # TODO: Double check whether the brackets are correct + em.add_assignment( + double_out, double_in & ~(double_in - (Val(0, size).concat(base))) + ) + for j in range(0, size): + em.add_assignment( + (dout, j, i), Val(double_out, j) | Val(double_out, j + size) + ) + else: + if reverse: + if isinstance(din, LogicArray): + size = din.length + else: + size = din.size + double_in = LogicVec(em, em.get_temp("double_in"), "w", size * 2) + for i in range(0, size): + em.add_assignment((double_in, i), Val(din, size - 1 - i)) + em.add_assignment((double_in, i + size), Val(din, size - 1 - i)) + base_rev = LogicVec(em, em.get_temp("base_rev"), "w", size) + for i in range(0, size): + em.add_assignment((base_rev, i), Val(base, size - 1 - i)) + double_out = LogicVec(em, em.get_temp("double_out"), "w", size * 2) + em.add_assignment( + double_out, double_in & ~(double_in - (Val(0, size).concat(base_rev))) + ) + for i in range(0, size): + em.add_assignment( + (dout, size - 1 - i), Val(double_out, i) | Val(double_out, i + size) + ) + else: + if isinstance(din, LogicArray): + size = din.length + double_in = LogicVec(em, em.get_temp("double_in"), "w", size * 2) + for i in range(0, size): + em.add_assignment((double_in, i), Val(din, i)) + em.add_assignment((double_in, i + size), Val(din, i)) + else: + size = din.size + double_in = LogicVec(em, em.get_temp("double_in"), "w", size * 2) + em.add_assignment(double_in, din & din) + double_out = LogicVec(em, em.get_temp("double_out"), "w", size * 2) + em.add_assignment( + double_out, double_in & ~(double_in - (Val(0, size).concat(base))) + ) + if isinstance(dout, LogicVec): + # TODO: Have indexing function + em.add_assignment( + dout, + Val(em.slice_var(f"{double_out.getNameRead()}", size - 1, 0)) + | Val( + em.slice_var(f"{double_out.getNameRead()}", 2 * size - 1, size) + ), + ) + else: + for i in range(0, size): + em.add_assignment( + (dout, i), Val(double_out, i) | Val(double_out, i + size) + ) + em.add_comment("Priority Masking End\n") diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/mux.py b/tools/backend/lsq-generator-python/core_gen/operators/mux.py similarity index 52% rename from tools/backend/lsq-generator-python/vhdl_gen/operators/mux.py rename to tools/backend/lsq-generator-python/core_gen/operators/mux.py index ad899bc8ee..f46954d870 100644 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/mux.py +++ b/tools/backend/lsq-generator-python/core_gen/operators/mux.py @@ -1,220 +1,224 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * -from vhdl_gen.operators import * - - -# ===----------------------------------------------------------------------===# -# Multiplexer -# ===----------------------------------------------------------------------===# -# Mux1H : One-hot select elements of `din` using `sel` -# Mux1HROM : Special multiplexer for the Group Allocator ROM. -# MuxIndex : Generate a VHDL array-index expression for selecting an element. -# MuxLookUp: Generate a conditional "when/else" lookup multiplexer in VHDL. - -def Mux1H(ctx: VHDLContext, dout, din, sel, j=None) -> str: - """ - Generate a one-hot multiplexer: for each element of "din", - write that bit/vector into a temporary and then OR-reduce into "dout". - - Parameters: - dout (LogicVec or Logic): - Destination for the multiplexed data, chosen by "sel". - din (LogicVecArray or LogicArray or LogicVec): - Source data. - - If LogicVecArray: 2D array of vectors. - - If LogicArray: 1D array of bits. - - If LogicVec: single vector. - sel (LogicVec or LogicArray or LogicVecArray): - One-hot select signals. - j (int, optional): - When "sel" is LogicVecArray, select the j-th "sel" signal. - - Returns: - str: A VHDL code snippet for multiplexing. - - Example: - type(din) = LogicVecArray: - din = ("0010"; "1100"), sel = "10" - -> dout = "0010" - - type(din) = LogicArray: - din = ("0"; "1"; "1"), sel = "010" - -> selects the middle bit: dout = '1' - - type(din) = LogicVec: - din = "01101", sel = "00100" - -> selects the third bit: dout = '1' - """ - - str_ret = ctx.get_current_indent() + '-- Mux1H Begin\n' - str_ret += ctx.get_current_indent() + f'-- Mux1H({dout.name}, {din.name}, {sel.name})\n' - ctx.use_temp() - - # din is always LogicVecArray - if (type(din) == LogicVecArray): - length = din.length - size = din.size - mux = LogicVecArray(ctx, ctx.get_temp('mux'), 'w', length, din.size) - elif (type(din) == LogicArray): - length = din.length - size = None - mux = LogicArray(ctx, ctx.get_temp('mux'), 'w', length) - else: - length = din.size - size = None - mux = LogicArray(ctx, ctx.get_temp('mux'), 'w', length) - - str_zero = Zero(size) - if (j == None): - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{mux.getNameWrite(i)} <= {din.getNameRead(i)} ' +\ - f'when {sel.getNameRead(i)} = \'1\' else {str_zero};\n' - else: - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{mux.getNameWrite(i)} <= {din.getNameRead(i)} ' +\ - f'when {sel.getNameRead(i, j)} = \'1\' else {str_zero};\n' - - str_ret += Reduce(ctx, dout, mux, 'or', False) - str_ret += ctx.get_current_indent() + '-- Mux1H End\n\n' - return str_ret - - -def Mux1HROM(ctx: VHDLContext, dout, din, sel, func=IntToBits) -> str: - """ - Generate a one-hot ROM multiplexer for LSQ port index allocation, - Load-Store Order Matrix construction, and tracking load/store numbers. - - Parameters: - dout (LogicVecArray or LogicVec): - If LogicVecArray: an NxM array; each row i will be computed independently. - - ldq_port_idx_rom - - stq_port_idx_rom - - ga_ls_order_rom - If LogicVec: a single M-bit vector; results from all groups are OR-reduced. - - num_loads - - num_stores - - din (list or list of lists): - ROM contents. (configs.gaLdPortIdx, configs.gaStPortIdx, configs.gaLdOrder - configs.gaNumLoads, configs. gaNumStores) - - sel (LogicArray): - Indicates groups to be allocated. (group_init_hs) - - func (callable, optional): - Conversion function from integer to LogicVec (default: IntToBits). - Either IntToBits() or MaskLess() - - Behavior: - - type(dout) == LogicVec: - 1. Build a temporary vector "mux" of width M. - 2. For each group j, if sel[j] = '1', assign mux[j] <= func(din[j]); - else mux[j] <= Zero. - 3. OR-reduce "mux" into the single "dout". - - - type(dout) == LogicVecArray: - For each row i in dout: - Repeat 1, 2, and 3. - - Example: - Assume numBB = 3 - - - type(dout) == LogicVec: - dout = num_loads - din = configs.gaNumLoads = [3,1,2] - sel = "010" - -> dout = "01" (1 load) - - This means that the currently allocated BB is BB1 (among BB0, BB1, and BB2) - It has 1 load. that "dout" indicates 1. - """ - - str_ret = ctx.get_current_indent() + '-- Mux1H For Rom Begin\n' - str_ret += ctx.get_current_indent() + f'-- Mux1H({dout.name}, {sel.name})\n' - ctx.use_temp() - mlen = sel.length - size = dout.size - str_zero = Zero(size) - if (type(dout) == LogicVecArray): - length = dout.length - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'-- Loop {i}\n' - mux = LogicVecArray(ctx, ctx.get_temp(f'mux_{i}'), 'w', mlen, size) - for j in range(0, mlen): - str_value = func(GetValue(din[j], i), size) - if (str_value == str_zero): - str_ret += ctx.get_current_indent() + f'{mux.getNameWrite(j)} <= {str_zero};\n' - else: - str_ret += ctx.get_current_indent() + f'{mux.getNameWrite(j)} <= {str_value} ' + \ - f'when {sel.getNameRead(j)} else {str_zero};\n' - str_ret += Reduce(ctx, dout[i], mux, 'or', False) - else: # type(dout) == LogicVec - mux = LogicVecArray(ctx, ctx.get_temp(f'mux'), 'w', mlen, size) - for j in range(0, mlen): - str_value = func(din[j], size) - if (str_value == str_zero): - str_ret += ctx.get_current_indent() + f'{mux.getNameWrite(j)} <= {str_zero};\n' - else: - str_ret += ctx.get_current_indent() + f'{mux.getNameWrite(j)} <= {str_value} ' + \ - f'when {sel.getNameRead(j)} else {str_zero};\n' - str_ret += Reduce(ctx, dout, mux, 'or', False) - str_ret += ctx.get_current_indent() + '-- Mux1H For Rom End\n\n' - return str_ret - - -def MuxIndex(din, sel) -> str: - """ - Generate a VHDL array-index expression for selecting an element - """ - return f'{din.getNameRead()}(to_integer(unsigned({sel.getNameRead()})))' - - -def MuxLookUp(ctx: VHDLContext, dout, din, sel) -> str: - """ - Generate a conditional "when/else" lookup multiplexer in VHDL. - - Parameters: - dout (Logic or LogicVec): - Destination signal to receive the selected value. - din (LogicArray or LogicVecArray): - Array of input signals to choose from. - sel (LogicVec): - Binary select vector; compared against each index using IntToBits. - - Example: - dout <= - din_0 when (sel = "0000") else - din_1 when (sel = "0001") else - din_2 when (sel = "0010") else - din_3 when (sel = "0011") else - din_4 when (sel = "0100") else - din_5 when (sel = "0101") else - din_6 when (sel = "0110") else - din_7 when (sel = "0111") else - din_8 when (sel = "1000") else - din_9 when (sel = "1001") else - '0'; - - Depending on the value of "sel", "dout" is driven by the - corresponding element of "din" or defaults to '0'. - - """ - - str_ret = ctx.get_current_indent() + '-- MuxLookUp Begin\n' - str_ret += ctx.get_current_indent() + f'-- MuxLookUp({dout.name}, {din.name}, {sel.name})\n' - - length = din.length - size = sel.size - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite()} <= \n' - - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{din.getNameRead(i)} ' +\ - f'when ({sel.getNameRead()} = {IntToBits(i, size)}) else\n' - if (type(dout) == LogicVec): - str_ret += ctx.get_current_indent() + f'{Zero(dout.size)};\n' - else: - str_ret += ctx.get_current_indent() + f'\'0\';\n' - - str_ret += ctx.get_current_indent() + '-- MuxLookUp End\n\n' - return str_ret +from core_gen.emitters import Emitter +from core_gen.utils import GetValue +from core_gen.signals import LogicVecArray, LogicArray, Logic, LogicVec +from core_gen.operators import * +from core_gen.ir import Val, BinOp, WhenElse, Bit + + +# ===----------------------------------------------------------------------===# +# Multiplexer +# ===----------------------------------------------------------------------===# +# Mux1H : One-hot select elements of `din` using `sel` +# Mux1HROM : Special multiplexer for the Group Allocator ROM. +# MuxLookUp: Generate a conditional "when/else" lookup multiplexer in VHDL. + + +def Mux1H(em: Emitter, dout, din, sel, j=None) -> str: + """ + Generate a one-hot multiplexer: for each element of "din", + write that bit/vector into a temporary and then OR-reduce into "dout". + + Parameters: + dout (LogicVec or Logic): + Destination for the multiplexed data, chosen by "sel". + din (LogicVecArray or LogicArray or LogicVec): + Source data. + - If LogicVecArray: 2D array of vectors. + - If LogicArray: 1D array of bits. + - If LogicVec: single vector. + sel (LogicVec or LogicArray or LogicVecArray): + One-hot select signals. + j (int, optional): + When "sel" is LogicVecArray, select the j-th "sel" signal. + + Returns: + str: A VHDL code snippet for multiplexing. + + Example: + type(din) = LogicVecArray: + din = ("0010"; "1100"), sel = "10" + -> dout = "0010" + + type(din) = LogicArray: + din = ("0"; "1"; "1"), sel = "010" + -> selects the middle bit: dout = '1' + + type(din) = LogicVec: + din = "01101", sel = "00100" + -> selects the third bit: dout = '1' + """ + + em.add_comment("Mux1H Begin") + em.add_comment(f"Mux1H({dout.name}, {din.name}, {sel.name})") + em.use_temp() + + # din is always LogicVecArray + if isinstance(din, LogicVecArray): + length = din.length + size = din.size + mux = LogicVecArray(em, em.get_temp("mux"), "w", length, din.size) + elif isinstance(din, LogicArray): + length = din.length + size = None + mux = LogicArray(em, em.get_temp("mux"), "w", length) + else: + length = din.size + size = None + mux = LogicArray(em, em.get_temp("mux"), "w", length) + + str_zero = em.int_to_str(0, size) + if j == None: + for i in range(0, length): + em.add_assignment( + (mux, i), Val(din, i).when(Val(sel, i) == Bit(1)).else_(Val(str_zero)) + ) + else: + for i in range(0, length): + em.add_assignment( + (mux, i), + Val(din, i).when(Val(sel, i, j) == Bit(1)).else_(Val(str_zero)), + ) + + Reduce(em, dout, mux, BinOp.OR, False) + em.add_comment("Mux1H End\n") + + +def Mux1HROM(em: Emitter, dout, din, sel, func=None) -> str: + """ + Generate a one-hot ROM multiplexer for LSQ port index allocation, + Load-Store Order Matrix construction, and tracking load/store numbers. + + Parameters: + dout (LogicVecArray or LogicVec): + If LogicVecArray: an NxM array; each row i will be computed independently. + - ldq_port_idx_rom + - stq_port_idx_rom + - ga_ls_order_rom + If LogicVec: a single M-bit vector; results from all groups are OR-reduced. + - num_loads + - num_stores + + din (list or list of lists): + ROM contents. (configs.gaLdPortIdx, configs.gaStPortIdx, configs.gaLdOrder + configs.gaNumLoads, configs. gaNumStores) + + sel (LogicArray): + Indicates groups to be allocated. (group_init_hs) + + func (callable, optional): + Conversion function from integer to LogicVec (default: em.int_to_bits). + Either em.int_to_bits or MaskLess() + + Behavior: + - type(dout) == LogicVec: + 1. Build a temporary vector "mux" of width M. + 2. For each group j, if sel[j] = '1', assign mux[j] <= func(din[j]); + else mux[j] <= Zero. + 3. OR-reduce "mux" into the single "dout". + + - type(dout) == LogicVecArray: + For each row i in dout: + Repeat 1, 2, and 3. + + Example: + Assume numBB = 3 + + - type(dout) == LogicVec: + dout = num_loads + din = configs.gaNumLoads = [3,1,2] + sel = "010" + -> dout = "01" (1 load) + + This means that the currently allocated BB is BB1 (among BB0, BB1, and BB2) + It has 1 load. that "dout" indicates 1. + """ + if func is None: + func = em.int_to_str + + em.add_comment("Mux1H For Rom Begin") + em.add_comment(f"Mux1H({dout.name}, {sel.name})") + em.use_temp() + mlen = sel.length + size = dout.size + str_zero = em.int_to_str(0, size) + + if isinstance(dout, LogicVecArray): + length = dout.length + for i in range(0, length): + em.add_comment(f"Loop {i}") + mux = LogicVecArray(em, em.get_temp(f"mux_{i}"), "w", mlen, size) + for j in range(0, mlen): + str_value = func(GetValue(din[j], i), size) + if str_value == str_zero: + em.add_assignment((mux, j), Val(str_zero)) + else: + em.add_assignment( + (mux, j), Val(str_value).when(Val(sel, j)).else_(Val(str_zero)) + ) + Reduce(em, dout[i], mux, BinOp.OR, False) + else: # type(dout) == LogicVec + mux = LogicVecArray(em, em.get_temp(f"mux"), "w", mlen, size) + for j in range(0, mlen): + str_value = func(din[j], size) + if str_value == str_zero: + em.add_assignment((mux, j), Val(str_zero)) + else: + em.add_assignment( + (mux, j), Val(str_value).when(Val(sel, j)).else_(Val(str_zero)) + ) + Reduce(em, dout, mux, BinOp.OR, False) + em.add_comment("Mux1H For Rom End\n") + + +# TODO: Properly test this +def MuxLookUp(em: Emitter, dout, din, sel) -> str: + """ + Generate a conditional "when/else" lookup multiplexer in VHDL. + + Parameters: + dout (Logic or LogicVec): + Destination signal to receive the selected value. + din (LogicArray or LogicVecArray): + Array of input signals to choose from. + sel (LogicVec): + Binary select vector; compared against each index using IntToBits. + + Example: + dout <= + din_0 when (sel = "0000") else + din_1 when (sel = "0001") else + din_2 when (sel = "0010") else + din_3 when (sel = "0011") else + din_4 when (sel = "0100") else + din_5 when (sel = "0101") else + din_6 when (sel = "0110") else + din_7 when (sel = "0111") else + din_8 when (sel = "1000") else + din_9 when (sel = "1001") else + '0'; + + Depending on the value of "sel", "dout" is driven by the + corresponding element of "din" or defaults to '0'. + + """ + + em.add_comment("MuxLookUp Begin") + em.add_comment(f"MuxLookUp({dout.name}, {din.name}, {sel.name})") + + length = din.length + size = sel.size + + if type(dout) == LogicVec: + last = Val(0) + else: + last = Bit(0) + + whenelses = [ + WhenElse(Val(din, i), sel == Val(i, size), None) for i in range(0, length) + ] + whenelses.append(last) + for i, op in enumerate(whenelses[:-1]): + op.false_statement = whenelses[i + 1] + + em.add_assignment(dout, whenelses[0]) + em.add_comment("MuxLookUp End\n") diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/reduction.py b/tools/backend/lsq-generator-python/core_gen/operators/reduction.py similarity index 55% rename from tools/backend/lsq-generator-python/vhdl_gen/operators/reduction.py rename to tools/backend/lsq-generator-python/core_gen/operators/reduction.py index 50322b818b..af8f716110 100644 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/reduction.py +++ b/tools/backend/lsq-generator-python/core_gen/operators/reduction.py @@ -1,187 +1,175 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * -from vhdl_gen.operators import * - - -# ===----------------------------------------------------------------------===# -# Reduction -# ===----------------------------------------------------------------------===# -# The following functions implement cyclic left shifts: -# ReduceLogicVec() : Recursively reduce a single vector. -# ReduceLogicArray() : Recursively reduce an array of single-bit elements. -# ReduceLogicVecArray() : Recursively reduce an array of vectors. -# -> These are called only internally by Reduce(). -# -# Reduce(): -# Detects the type of `din` and dispatches to the appropriate implementation. - -def ReduceLogicVec(ctx: VHDLContext, dout, din, operator, length) -> str: - """ - Recursively reduce the vector "din" by "operator". - - Parameters: - dout (Logic) : Destination std_logic to hold the reduced result. - din (LogicVec): Source vector to be reduced. - operator (str) : 'and', 'or', ... - length (int) : Current recursion length; - set to "2**(log2Ceil(din.size) - 1)" when called initially. - - The "length" parameter is used internally to control recursion depth and - should always start at "2**(log2Ceil(din.size) - 1)". - - Returns: - str_ret (str): A VHDL code snippet implementing the LogicVec reduction. - - Usage: - (Called only internally by Reduce) - ReduceLogicVec(dout, din, operator, 2**(log2Ceil(din.size) - 1)) - - When this method is called, "length" is always "2**(log2Ceil(din.size) - 1)". - "length" is just for an recursive action. - - - Example: - 1. din = "01110010", operator = 'and' -> dout = '0' - 2. din = "01100111", operator = 'or' -> dout = '1' - 3. din = "abcdefghijklmnop" - dout = "a" operator "b" operator "c" operator "d" operator "e" operator "f" - operator "g" operator "h" operator "i" operator "j" operator "k" - operator "l" operator "m" operator "n" operator "o" operator "p" - """ - - str_ret = '' - if (length == 1): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite()} <= ' + \ - f'{din.getNameRead(0)} {operator} {din.getNameRead(1)};\n' - else: - ctx.use_temp() - res = LogicVec(ctx, ctx.get_temp('res'), 'w', length) - for i in range(0, din.size - length): - str_ret += ctx.get_current_indent() + f'{res.getNameWrite(i)} <= ' + \ - f'{din.getNameRead(i)} {operator} {din.getNameRead(i+length)};\n' - for i in range(din.size - length, length): - str_ret += ctx.get_current_indent() + f'{res.getNameWrite(i)} <= ' + \ - f'{din.getNameRead(i)};\n' - str_ret += ctx.get_current_indent() + '-- Layer End\n' - str_ret += ReduceLogicVec(ctx, dout, res, operator, length//2) - return str_ret - - -def ReduceLogicArray(ctx: VHDLContext, dout, din, operator, length) -> str: - """ - Recursively perform reduction of LogicArray "din" by "operator". - - Identical in behavior to ReduceLogicVec, but operates on multiple VHDL single-bit std_logic - instead of std_logic_vector. - """ - - str_ret = '' - if (length == 1): - str_ret += Op(ctx, dout, din[0], operator, din[1]) - else: - ctx.use_temp() - res = LogicArray(ctx, ctx.get_temp('res'), 'w', length) - for i in range(0, din.length - length): - str_ret += Op(ctx, res[i], din[i], operator, din[i+length]) - for i in range(din.length - length, length): - str_ret += Op(ctx, res[i], din[i]) - str_ret += ctx.get_current_indent() + '-- Layer End\n' - str_ret += ReduceLogicArray(ctx, dout, res, operator, length//2) - return str_ret - - -def ReduceLogicVecArray(ctx: VHDLContext, dout, din, operator, length) -> str: - """ - Recursively perform reduction of the LogicVecArray "din" by "operator". - - Parameters: - dout (LogicVec) : Destination std_logic_vector to hold the reduced result. - din (LogicVecArray): Source LogicVecArray to be reduced. - operator (str) : 'and', 'or', ... - length (int) : Current recursion length; - set to "2**(log2Ceil(din.size) - 1)" when called initially. - - The "length" parameter is used internally to control recursion depth and - should always start at "2**(log2Ceil(din.size) - 1)". - - Returns: - str_ret (str): A VHDL code snippet implementing the LogicVecArray reduction. - - Usage: - (Called only internally by Reduce) - ReduceLogicVecArray(dout, din, operator, 2**(log2Ceil(din.size) - 1)) - - When this method is called, "length" is always "2**(log2Ceil(din.size) - 1)". - "length" is just for an recursive action. - - Example: - din = (LogicVecArray x with length of 8, each Vec size 16) where - x[0] = "a1 a2 a3 ... a16" - x[1] = "b1 b2 b3 ... b16" - ... - x[7] = "p1 p2 p3 ... p16" - - dout = x[0] operator x[1] operator ... operator x[7] - - If operator = '&', - dout = {a1 & b1 & ... & p1, a2 & b2 & ... & p2, ..., a16 & b16 & ... & p16} - - Therefore, dout is LogicVec. - """ - str_ret = '' - if (length == 1): - str_ret += Op(ctx, dout, din[0], operator, din[1]) - else: - ctx.use_temp() - res = LogicVecArray(ctx, ctx.get_temp('res'), 'w', length, dout.size) - for i in range(0, din.length - length): - str_ret += Op(ctx, res[i], din[i], operator, din[i+length]) - for i in range(din.length - length, length): - str_ret += Op(ctx, res[i], din[i]) - str_ret += ctx.get_current_indent() + '-- Layer End\n' - str_ret += ReduceLogicVecArray(ctx, dout, res, operator, length//2) - return str_ret - - -def Reduce(ctx: VHDLContext, dout, din, operator, comment: bool = True) -> str: - """ - Execute reduction based on the type of "din" - - This function wraps the three implementations: - - ReduceLogicVec : when "din" is LogicVec - - ReduceLogicArray : when "din" is LogicArray - - ReduceLogicVecArray : when "din" is LogicVecArray - - Parameters: - dout : Destination signal to receive the reduced data. - din : Source data to be reduced. - operator: types of operator for the reduction - comment : Turn on/off adding VHDL comment lines. - - Returns: - str_ret : A VHDL code snippet (with indentation) implementing the reduction. - """ - - str_ret = '' - if (comment): - str_ret += ctx.get_current_indent() + '-- Reduction Begin\n' - str_ret += ctx.get_current_indent() + f'-- Reduce({dout.name}, {din.name}, {operator})\n' - if (type(din) == LogicVec): - if (din.size == 1): - str_ret += Op(ctx, dout, (din, 0)) - else: - length = 2**(log2Ceil(din.size) - 1) - str_ret += ReduceLogicVec(ctx, dout, din, operator, length) - else: - if (din.length == 1): - str_ret += Op(ctx, dout, din[0]) - else: - length = 2**(log2Ceil(din.length) - 1) - if (type(din) == LogicArray): - str_ret += ReduceLogicArray(ctx, dout, din, operator, length) - else: - str_ret += ReduceLogicVecArray(ctx, dout, din, operator, length) - if (comment): - str_ret += ctx.get_current_indent() + '-- Reduction End\n\n' - return str_ret +from core_gen.emitters import Emitter +from core_gen.utils import log2Ceil +from core_gen.operators import * +from core_gen.ir import Val, Bin + + +# ===----------------------------------------------------------------------===# +# Reduction +# ===----------------------------------------------------------------------===# +# The following functions implement cyclic left shifts: +# ReduceLogicVec() : Recursively reduce a single vector. +# ReduceLogicArray() : Recursively reduce an array of single-bit elements. +# ReduceLogicVecArray() : Recursively reduce an array of vectors. +# -> These are called only internally by Reduce(). +# +# Reduce(): +# Detects the type of `din` and dispatches to the appropriate implementation. + + +def ReduceLogicVec(em: Emitter, dout, din, operator, length) -> str: + """ + Recursively reduce the vector "din" by "operator" and add this to "em". + + Parameters: + dout (Logic) : Destination std_logic to hold the reduced result. + din (LogicVec): Source vector to be reduced. + operator (str) : 'and', 'or', ... + length (int) : Current recursion length; + set to "2**(log2Ceil(din.size) - 1)" when called initially. + + The "length" parameter is used internally to control recursion depth and + should always start at "2**(log2Ceil(din.size) - 1)". + + Usage: + (Called only internally by Reduce) + ReduceLogicVec(dout, din, operator, 2**(log2Ceil(din.size) - 1)) + + When this method is called, "length" is always "2**(log2Ceil(din.size) - 1)". + "length" is just for an recursive action. + + + Example: + 1. din = "01110010", operator = 'and' -> dout = '0' + 2. din = "01100111", operator = 'or' -> dout = '1' + 3. din = "abcdefghijklmnop" + dout = "a" operator "b" operator "c" operator "d" operator "e" operator "f" + operator "g" operator "h" operator "i" operator "j" operator "k" + operator "l" operator "m" operator "n" operator "o" operator "p" + """ + from core_gen.signals import LogicVec + + if length == 1: + em.add_assignment(dout, Bin(Val(din, 0), operator, Val(din, 1))) + else: + em.use_temp() + res = LogicVec(em, em.get_temp("res"), "w", length) + for i in range(0, din.size - length): + em.add_assignment( + (res, i), Bin(Val(din, i), operator, Val(din, i + length)) + ) + for i in range(din.size - length, length): + em.add_assignment((res, i), Val(din, i)) + em.add_comment("Layer End") + ReduceLogicVec(em, dout, res, operator, length // 2) + + +def ReduceLogicArray(em: Emitter, dout, din, operator, length) -> str: + """ + Recursively perform reduction of LogicArray "din" by "operator". + + Identical in behavior to ReduceLogicVec, but operates on multiple VHDL single-bit std_logic + instead of std_logic_vector. + """ + from core_gen.signals import LogicArray + + if length == 1: + em.add_assignment(dout, Bin(din[0], operator, din[1])) + else: + em.use_temp() + res = LogicArray(em, em.get_temp("res"), "w", length) + for i in range(0, din.length - length): + em.add_assignment(res[i], Bin(din[i], operator, din[i + length])) + for i in range(din.length - length, length): + em.add_assignment(res[i], din[i]) + em.add_comment("Layer End") + ReduceLogicArray(em, dout, res, operator, length // 2) + + +def ReduceLogicVecArray(em: Emitter, dout, din, operator, length) -> str: + """ + Recursively perform reduction of the LogicVecArray "din" by "operator" and add this to "em". + + Parameters: + dout (LogicVec) : Destination std_logic_vector to hold the reduced result. + din (LogicVecArray): Source LogicVecArray to be reduced. + operator (str) : 'and', 'or', ... + length (int) : Current recursion length; + set to "2**(log2Ceil(din.size) - 1)" when called initially. + + The "length" parameter is used internally to control recursion depth and + should always start at "2**(log2Ceil(din.size) - 1)". + + Usage: + (Called only internally by Reduce) + ReduceLogicVecArray(dout, din, operator, 2**(log2Ceil(din.size) - 1)) + + When this method is called, "length" is always "2**(log2Ceil(din.size) - 1)". + "length" is just for an recursive action. + + Example: + din = (LogicVecArray x with length of 8, each Vec size 16) where + x[0] = "a1 a2 a3 ... a16" + x[1] = "b1 b2 b3 ... b16" + ... + x[7] = "p1 p2 p3 ... p16" + + dout = x[0] operator x[1] operator ... operator x[7] + + If operator = '&', + dout = {a1 & b1 & ... & p1, a2 & b2 & ... & p2, ..., a16 & b16 & ... & p16} + + Therefore, dout is LogicVec. + """ + from core_gen.signals import LogicVecArray, LogicArray + + if length == 1: + em.add_assignment(dout, Bin(din[0], operator, din[1])) + else: + em.use_temp() + res = LogicVecArray(em, em.get_temp("res"), "w", length, dout.size) + for i in range(0, din.length - length): + em.add_assignment(res[i], Bin(din[i], operator, din[i + length])) + for i in range(din.length - length, length): + em.add_assignment(res[i], din[i]) + em.add_comment("Layer End") + ReduceLogicVecArray(em, dout, res, operator, length // 2) + + +def Reduce(em: Emitter, dout, din, operator, comment: bool = True) -> str: + """ + Execute reduction based on the type of "din" and add this to "em". + + This function wraps the three implementations: + - ReduceLogicVec : when "din" is LogicVec + - ReduceLogicArray : when "din" is LogicArray + - ReduceLogicVecArray : when "din" is LogicVecArray + + Parameters: + dout : Destination signal to receive the reduced data. + din : Source data to be reduced. + operator: types of operator for the reduction + comment : Turn on/off adding VHDL comment lines. + """ + from core_gen.signals import LogicVec, LogicArray, LogicVecArray + + if comment: + em.add_comment("Reduction Begin") + em.add_comment(f"Reduce({dout.name}, {din.name}, {em.get_binop_str(operator)})") + if type(din) == LogicVec: + if din.size == 1: + em.add_assignment(dout, Val(din, 0)) + else: + length = 2 ** (log2Ceil(din.size) - 1) + ReduceLogicVec(em, dout, din, operator, length) + else: + if din.length == 1: + em.add_assignment(dout, Val(din[0])) + else: + length = 2 ** (log2Ceil(din.length) - 1) + if type(din) == LogicArray: + ReduceLogicArray(em, dout, din, operator, length) + else: + ReduceLogicVecArray(em, dout, din, operator, length) + if comment: + em.add_comment("Reduction End\n") diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/shifts.py b/tools/backend/lsq-generator-python/core_gen/operators/shifts.py similarity index 51% rename from tools/backend/lsq-generator-python/vhdl_gen/operators/shifts.py rename to tools/backend/lsq-generator-python/core_gen/operators/shifts.py index 41f1af34e5..e5612ce1a2 100644 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/shifts.py +++ b/tools/backend/lsq-generator-python/core_gen/operators/shifts.py @@ -1,153 +1,172 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * - - -# ===----------------------------------------------------------------------===# -# Cyclic Left Shift -# ===----------------------------------------------------------------------===# -# The following functions implement cyclic left shifts: -# RotateLogicVec() : Recursively shift a single vector. -# RotateLogicArray() : Recursively shift an array of single-bit elements. -# RotateLogicVecArray() : Recursively shift an array of vectors. -# -> These are called only internally by CyclicLeftShift(). -# -# CyclicLeftShift(): -# Detects the type of `din` and dispatches to the appropriate implementation. - -def RotateLogicVec(ctx: VHDLContext, dout, din, distance, layer) -> str: - """ - Recursively perform a cyclic left shift of the vector "din" by the amount - specified in "distance". - - Parameters: - dout (LogicVec): Destination vector to hold the shifted result. - din (LogicVec): Source vector to be shifted. - distance (LogicVec): Binary vector representing the shift amount. - layer (int) : Current recursion layer; set to "distance.size-1" when called initially. - - The "layer" parameter is used internally to control recursion depth and - should always start at "distance.size - 1". - - Returns: - str_ret (str): A VHDL code snippet implementing the cyclic left shift. - - Usage: - (Called only internally by CyclicLeftShift) - RotateLogicVec(dout, din, distance, distance.size - 1) - - When this method is called, "layer" is always "distance.size - 1". - "layer" is just for an recursive action. - - - Example: - Input: din = "01110010", distance = 3 - Output: dout = "10010011" - """ - - str_ret = '' - length = din.size - if (layer == 0): - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(i)} <= {din.getNameRead((i-2**layer) % length)} ' + \ - f'when {distance.getNameRead(layer)} else {din.getNameRead(i)};\n' - else: - ctx.use_temp() - res = LogicVec(ctx, ctx.get_temp('res'), 'w', length) - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{res.getNameWrite(i)} <= {din.getNameRead((i-2**layer) % length)} ' + \ - f'when {distance.getNameRead(layer)} else {din.getNameRead(i)};\n' - str_ret += ctx.get_current_indent() + '-- Layer End\n' - str_ret += RotateLogicVec(ctx, dout, res, distance, layer-1) - return str_ret - - -def RotateLogicArray(ctx: VHDLContext, dout, din, distance, layer) -> str: - """ - Recursively perform a cyclic left shift of LogicArray "din" by the amount - specified in "distance". - - Identical in behavior to RotateLogicVec, but operates on multiple VHDL single-bit std_logic - instead of std_logic_vector. - - """ - - str_ret = '' - length = din.length - if (layer == 0): - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(i)} <= {din.getNameRead((i-2**layer) % length)} ' + \ - f'when {distance.getNameRead(layer)} else {din.getNameRead(i)};\n' - else: - ctx.use_temp() - res = LogicArray(ctx, ctx.get_temp('res'), 'w', length) - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{res.getNameWrite(i)} <= {din.getNameRead((i-2**layer) % length)} ' + \ - f'when {distance.getNameRead(layer)} else {din.getNameRead(i)};\n' - str_ret += ctx.get_current_indent() + '-- Layer End\n' - str_ret += RotateLogicArray(ctx, dout, res, distance, layer-1) - return str_ret - - -def RotateLogicVecArray(ctx: VHDLContext, dout, din, distance, layer) -> str: - """ - Recursively perform a cyclic left shift of the LogicVecArray "din" by the amount - specified in "distance". - - Identical in behavior to RotateLogicVec, but operates on multiple VHDL vectors std_logic_vector. - For every LogicVec in LogicVecArray, cyclic left shift by "distance". - - Example: - din = "11001001 - 11100011" - distance = 2 - - -> dout = "00100111 (Cyclic Left Shift of each vector by 2) - 10001111" - """ - - str_ret = '' - length = din.length - if (layer == 0): - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(i)} <= {din.getNameRead((i-2**layer) % length)} ' + \ - f'when {distance.getNameRead(layer)} else {din.getNameRead(i)};\n' - else: - ctx.use_temp() - res = LogicVecArray(ctx, ctx.get_temp('res'), 'w', length, dout.size) - for i in range(0, length): - str_ret += ctx.get_current_indent() + f'{res.getNameWrite(i)} <= {din.getNameRead((i-2**layer) % length)} ' + \ - f'when {distance.getNameRead(layer)} else {din.getNameRead(i)};\n' - str_ret += ctx.get_current_indent() + '-- Layer End\n' - str_ret += RotateLogicVecArray(ctx, dout, res, distance, layer-1) - return str_ret - - -def CyclicLeftShift(ctx: VHDLContext, dout, din, distance) -> str: - """ - Execute a cyclic left shift operation based on the type of "din" - - This function wraps the three implementations: - - RotateLogicVec : when "din" is LogicVec - - RotateLogicArray : when "din" is LogicArray - - RotateLogicVecArray : when "din" is LogicVecArray - - Parameters: - dout : Destination signal to receive the shifted data. - din : Source data to be shifted. - distance: Binary vector specifying how many positions to shift. - - Returns: - str_ret : A VHDL code snippet (with indentation) implementing the cyclic left shift. - """ - - str_ret = ctx.get_current_indent() + '-- Shifter Begin\n' - str_ret += ctx.get_current_indent() + f'-- CyclicLeftShift({dout.name}, {din.name}, {distance.name})\n' - if (type(din) == LogicArray): - str_ret += RotateLogicArray(ctx, dout, din, distance, distance.size-1) - elif (type(din) == LogicVecArray): - str_ret += RotateLogicVecArray(ctx, dout, din, distance, distance.size-1) - else: - str_ret += RotateLogicVec(ctx, dout, din, distance, distance.size-1) - str_ret += ctx.get_current_indent() + '-- Shifter End\n\n' - return str_ret +from core_gen.emitters import Emitter +from core_gen.signals import * +from core_gen.ir import Val + + +# ===----------------------------------------------------------------------===# +# Cyclic Left Shift +# ===----------------------------------------------------------------------===# +# The following functions implement cyclic left shifts: +# RotateLogicVec() : Recursively shift a single vector. +# RotateLogicArray() : Recursively shift an array of single-bit elements. +# RotateLogicVecArray() : Recursively shift an array of vectors. +# -> These are called only internally by CyclicLeftShift(). +# +# CyclicLeftShift(): +# Detects the type of `din` and dispatches to the appropriate implementation. + + +def RotateLogicVec(em: Emitter, dout, din, distance, layer) -> str: + """ + Recursively perform a cyclic left shift of the vector "din" by the amount + specified in "distance". + + Parameters: + dout (LogicVec): Destination vector to hold the shifted result. + din (LogicVec): Source vector to be shifted. + distance (LogicVec): Binary vector representing the shift amount. + layer (int) : Current recursion layer; set to "distance.size-1" when called initially. + + The "layer" parameter is used internally to control recursion depth and + should always start at "distance.size - 1". + + Returns: + str_ret (str): A VHDL code snippet implementing the cyclic left shift. + + Usage: + (Called only internally by CyclicLeftShift) + RotateLogicVec(dout, din, distance, distance.size - 1) + + When this method is called, "layer" is always "distance.size - 1". + "layer" is just for an recursive action. + + + Example: + Input: din = "01110010", distance = 3 + Output: dout = "10010011" + """ + + length = din.size + if layer == 0: + for i in range(0, length): + em.add_assignment( + (dout, i), + Val(din, (i - 2**layer) % length) + .when(Val(distance, layer)) + .else_(Val(din, i)), + ) + else: + em.use_temp() + res = LogicVec(em, em.get_temp("res"), "w", length) + for i in range(0, length): + em.add_assignment( + (res, i), + Val(din, (i - 2**layer) % length) + .when(Val(distance, layer)) + .else_(Val(din, i)), + ) + + em.add_comment("Layer End") + RotateLogicVec(em, dout, res, distance, layer - 1) + + +def RotateLogicArray(em: Emitter, dout, din, distance, layer) -> str: + """ + Recursively perform a cyclic left shift of LogicArray "din" by the amount + specified in "distance". + + Identical in behavior to RotateLogicVec, but operates on multiple VHDL single-bit std_logic + instead of std_logic_vector. + + """ + + length = din.length + if layer == 0: + for i in range(0, length): + em.add_assignment( + (dout, i), + Val(din, (i - 2**layer) % length) + .when(Val(distance, layer)) + .else_(Val(din, i)), + ) + else: + em.use_temp() + res = LogicArray(em, em.get_temp("res"), "w", length) + for i in range(0, length): + em.add_assignment( + (res, i), + Val(din, (i - 2**layer) % length) + .when(Val(distance, layer)) + .else_(Val(din, i)), + ) + em.add_comment("Layer End") + RotateLogicArray(em, dout, res, distance, layer - 1) + + +def RotateLogicVecArray(em: Emitter, dout, din, distance, layer) -> str: + """ + Recursively perform a cyclic left shift of the LogicVecArray "din" by the amount + specified in "distance". + + Identical in behavior to RotateLogicVec, but operates on multiple VHDL vectors std_logic_vector. + For every LogicVec in LogicVecArray, cyclic left shift by "distance". + + Example: + din = "11001001 + 11100011" + distance = 2 + + -> dout = "00100111 (Cyclic Left Shift of each vector by 2) + 10001111" + """ + + length = din.length + if layer == 0: + for i in range(0, length): + em.add_assignment( + (dout, i), + Val(din, (i - 2**layer) % length) + .when(Val(distance, layer)) + .else_(Val(din, i)), + ) + else: + em.use_temp() + res = LogicVecArray(em, em.get_temp("res"), "w", length, dout.size) + for i in range(0, length): + em.add_assignment( + (res, i), + Val(din, (i - 2**layer) % length) + .when(Val(distance, layer)) + .else_(Val(din, i)), + ) + em.add_comment("Layer End") + RotateLogicVecArray(em, dout, res, distance, layer - 1) + + +def CyclicLeftShift(em: Emitter, dout, din, distance) -> str: + """ + Execute a cyclic left shift operation based on the type of "din" + + This function wraps the three implementations: + - RotateLogicVec : when "din" is LogicVec + - RotateLogicArray : when "din" is LogicArray + - RotateLogicVecArray : when "din" is LogicVecArray + + Parameters: + dout : Destination signal to receive the shifted data. + din : Source data to be shifted. + distance: Binary vector specifying how many positions to shift. + + Returns: + str_ret : A VHDL code snippet (with indentation) implementing the cyclic left shift. + """ + + em.add_comment("Shifter Begin") + em.add_comment(f"CyclicLeftShift({dout.name}, {din.name}, {distance.name})") + if type(din) == LogicArray: + RotateLogicArray(em, dout, din, distance, distance.size - 1) + elif type(din) == LogicVecArray: + RotateLogicVecArray(em, dout, din, distance, distance.size - 1) + else: + RotateLogicVec(em, dout, din, distance, distance.size - 1) + em.add_comment("Shifter End\n") diff --git a/tools/backend/lsq-generator-python/core_gen/signals.py b/tools/backend/lsq-generator-python/core_gen/signals.py new file mode 100644 index 0000000000..ebdabe087a --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/signals.py @@ -0,0 +1,369 @@ +# ===----------------------------------------------------------------------===# +# VHDL Signal Definition +# ===----------------------------------------------------------------------===# +# This section defined Python classes that generate VHDL signal declarations. +# +# - class Logic : (std_logic) one‑bit signal wire / port / register +# - class LogicVec : (std_logic_vector) Multi-bit signal. +# - class LogicArray : (Multiple std_logic) Array of individual std_logic signals. +# - class LogicVecArray : (Multiple std_logic_vector) Array of std_logic_vector signals. + +# +# std_logic bit +# + +from core_gen.ir import Statement + + +class Logic(Statement): + """ + A one-bit logic signal. + + Logic class encapsulates wires, ports, and registers in the code generator, + handling name with '_i', '_o', '_q', '_d' suffixes. + + Attributes: + em (Emitter): Emitter for code generation. + name (str): The base name of the signal. + type (str): + 'i' input port (_i: in std_logic) + 'o' output port (_o: out std_logic) + 'w' internal wire (signal : std_logic) + 'r' register (_q) for the registered value + (_d) for the next-cycled value + dyn_comp (bool): Flag indicating whether to use dynamatic compatible naming + force_reg (bool): Flag indicating whether to force the signal to be a register, only relevant for Verilog generation + + Methods: + getNameRead(): Returns the name we should use when reading the signal. (e.g. _q for a register type) + getNameWrite(): Returns the name to write to. (e.g. _d for a register type) + signalInit(): Appends the VHDL signal/port declaration. + regInit(): Appends the VHDL register initialization block. + """ + + # Signal name + name = "" + # Signal type, 'i' for input, 'o' for output, 'w' for wire, 'r' for register + type = "" + + def __init__( + self, + em: Statement, + name: str, + type: str = "w", + init: bool = True, + dyn_comp=False, + force_reg=False, + ) -> None: + """ + init: If True, immediately generates the corresponding std_logic in VHDL. + True when we instantiate Logic. + False when we instantiate LogicVec, LogicArray, and LogicVecArray. + """ + # Type should be one of the four types. + assert type in ("i", "o", "w", "r") + self.em = em + self.name = name + self.type = type + self.dyn_comp = dyn_comp + self.force_reg = force_reg + if init: + self.signalInit() + + def __repr__(self) -> str: + """ + Print Logic with useful information. + """ + # Signal type + type = "" + if self.type == "w": + type = "wire" + elif self.type == "i": + type = "input" + elif self.type == "o": + type = "output" + elif self.type == "r": + type = "reg" + return ( + f"name: {self.get_base_name()}\n" + + f"type: {type}\n" + + f"size: single bit\n" + ) + + def getNameRead(self, sufix="") -> str: + """ + Returns the name we should use when reading the signal. + + Example (Pseudo-code) + If you want to do "Logic a = Logic b + Logic c" + -> getNameWrite(a) = getNameRead(b) + getNameRead(c) + """ + if self.type == "w": + return self.get_base_name(sufix) + elif self.type == "r": + return self.get_base_name(sufix) + "_q" + elif self.type == "i": + return self.get_base_name(sufix) + ("_i" if not self.dyn_comp else "") + elif self.type == "o": + raise TypeError( + f'Cannot read from the output signal "{self.get_base_name(sufix)}"!' + ) + + def _to_str(self, em: Statement, size) -> str: + return self.getNameRead() + + def getNameWrite(self, sufix="") -> str: + """ + Returns the name to write to. + + Example in the getNameRead() method. + """ + if self.type == "w": + return self.get_base_name(sufix) + elif self.type == "r": + return self.get_base_name(sufix) + "_d" + elif self.type == "i": + raise TypeError( + f'Cannot write to the input signal "{self.get_base_name(sufix)}"!' + ) + elif self.type == "o": + return self.get_base_name(sufix) + ("_o" if not self.dyn_comp else "") + + def signalInit(self, sufix="") -> None: + self.em.logic_signal_init(self, sufix) + + def regInit(self, enable=None, init=None) -> None: + self.em.logic_reg_init(self, enable, init) + + def get_base_name(self, sufix="") -> str: + if not self.dyn_comp or sufix == "": + return self.name + sufix + + name_list = self.name.split("_") + name_list = name_list[:-1] + [sufix, name_list[-1]] + name = "_".join(name_list) + return name.replace("__", "_") + + +# +# std_logic_vec +# + + +class LogicVec(Logic): + """ + Like 'class Logic', but for M-bit vectors. + + Inherits all methods and suffix rules of Logic in default. + Additionally, it has additional features. + + Attributes: + size (int): bit-width of vector (M) + + Methods: + Indexable reads/writes of LogicVec components + Access a certain i-th bit of LogicVec via getNameRead(i), getNameWrite(i) + + LogicVec (size=3) : "101" + LogicArray (length=3): [1, + 0, + 1] + LogicVecArray (size=3, length=2): [101, + 010] + """ + + # Signal name + name = "" + # Signal type, 'i' for input, 'o' for output, 'w' for wire, 'r' for register + type = "" + size = 1 + + def __init__( + self, + em: Statement, + name: str, + type: str = "w", + size: int = 1, + init: bool = True, + dyn_comp=False, + force_reg=False, + ) -> None: + Logic.__init__(self, em, name, type, False, dyn_comp, force_reg) + assert size > 0 + self.size = size + if init: + self.signalInit() + + def __repr__(self) -> str: + # Signal type + type = "" + if self.type == "w": + type = "wire" + elif self.type == "i": + type = "input" + elif self.type == "o": + type = "output" + elif self.type == "r": + type = "reg" + return ( + f"name: {self.get_base_name()}\n" + + f"type: {type}\n" + + f"size: {self.size}\n" + ) + + def getNameRead(self, i=None, sufix="") -> str: + if i == None: + return Logic.getNameRead(self, sufix) + else: + assert i < self.size + return self.em.index_var(Logic.getNameRead(self, sufix), i) + + def getNameWrite(self, i=None, sufix="") -> str: + if i == None: + return Logic.getNameWrite(self, sufix) + else: + assert i < self.size + return self.em.index_var(Logic.getNameWrite(self, sufix), i) + + def signalInit(self, sufix=""): + self.em.logicvec_signal_init(self, sufix) + + def regInit(self, enable=None, init=None) -> None: + self.em.logicvec_reg_init(self, enable, init) + + +# +# An array of std_logic +# + + +class LogicArray(Logic): + """ + Represents a N-length array of one-bit VHDL std_logic. + Generates total of N one-bit std_logic. + + Each element (total N) is generated as a separate Logic(name + f'_{i}', type) + For example, + signal _0 : std_logic; + signal _1 : std_logic; + ... + signal _{N-1} : std_logic; + + Attributes: + length (int): number of elements in the array. + + Methods: + Indexable reads/writes of LogicArray components + Access a certain i-th element of LogicArray via getNameRead(i), getNameWrite(i) + """ + + length = 1 + + def __init__( + self, + em: Statement, + name: str, + type: str = "w", + length: int = 1, + dyn_comp=False, + force_reg=False, + ): + self.length = length + Logic.__init__(self, em, name, type, False, dyn_comp, force_reg) + self.signalInit() + + def __repr__(self) -> str: + return Logic.__repr__(self) + f"array length: {self.length}" + + def getNameRead(self, i) -> str: + assert i in range(0, self.length) + return Logic.getNameRead(self, f"_{i}") + + def getNameWrite(self, i) -> str: + assert i in range(0, self.length) + return Logic.getNameWrite(self, f"_{i}") + + def signalInit(self) -> None: + for i in range(0, self.length): + Logic.signalInit(self, f"_{i}") + + def __getitem__(self, i) -> Logic: + assert i in range(0, self.length) + return Logic( + self.em, self.get_base_name(f"_{i}"), self.type, False, self.dyn_comp + ) + + def regInit(self, enable=None, init=None) -> None: + self.em.logicarray_reg_init(self, enable, init) + + +# +# An array of std_logic vector +# + + +class LogicVecArray(LogicVec): + """ + Represents a N-length array of M-bit VHDL std_logic_vec. + Generates total of N M-bit std_logic_vec. + + Each element (total N) is generated as a separate LogicVec + For example, + signal _0 : std_logic_vector(M-1 downto 0); + signal _1 : std_logic_vector(M-1 downto 0); + … + signal _{N-1} : std_logic_vector(M-1 downto 0); + + Attributes: + length (int): number of entries (N). + size (int): bit-width of each vector (M). + + Methods: + Indexable reads/writes of LogicVecArray components + Access a certain i-th LogicVec of LogicVecArray via getNameRead(i), getNameWrite(i) + """ + + length = 1 + + def __init__( + self, + em: Statement, + name: str, + type: str = "w", + length: int = 1, + size: int = 1, + dyn_comp=False, + force_reg=False, + ): + self.length = length + LogicVec.__init__(self, em, name, type, size, False, dyn_comp, force_reg) + self.signalInit() + + def __repr__(self) -> str: + return LogicVec.__repr__(self) + f"array length: {self.length}" + + def getNameRead(self, i, j=None) -> str: + assert i in range(0, self.length) + return LogicVec.getNameRead(self, j, f"_{i}") + + def getNameWrite(self, i, j=None) -> str: + assert i in range(0, self.length) + return LogicVec.getNameWrite(self, j, f"_{i}") + + def signalInit(self) -> None: + for i in range(0, self.length): + LogicVec.signalInit(self, f"_{i}") + + def __getitem__(self, i) -> LogicVec: + assert i in range(0, self.length) + return LogicVec( + self.em, + self.get_base_name(f"_{i}"), + self.type, + self.size, + False, + self.dyn_comp, + ) + + def regInit(self, enable=None, init=None) -> None: + self.em.logicvecarray_reg_init(self, enable, init) diff --git a/tools/backend/lsq-generator-python/core_gen/utils.py b/tools/backend/lsq-generator-python/core_gen/utils.py new file mode 100644 index 0000000000..82520955b5 --- /dev/null +++ b/tools/backend/lsq-generator-python/core_gen/utils.py @@ -0,0 +1,27 @@ +# +# Dynamatic is under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# + +import re +import math + +# ===----------------------------------------------------------------------===# +# Helper Function +# ===----------------------------------------------------------------------===# + + +def GetValue(row, i) -> int: + if len(row) > i: + return row[i] + else: + return 0 + + +def isPow2(value: int) -> bool: + return (value & (value - 1) == 0) and value != 0 + + +def log2Ceil(value: int) -> int: + return math.ceil(math.log2(value)) diff --git a/tools/backend/lsq-generator-python/lsq-generator.py b/tools/backend/lsq-generator-python/lsq-generator.py index 750c5694b9..89dbd6b1d7 100644 --- a/tools/backend/lsq-generator-python/lsq-generator.py +++ b/tools/backend/lsq-generator-python/lsq-generator.py @@ -12,7 +12,11 @@ import os import sys -from vhdl_gen import * +from core_gen.signals import Logic, LogicVec, LogicArray, LogicVecArray +from core_gen.configs import Configs, GetConfigs +from core_gen.codegen import codeGen +from core_gen.emitters import Emitter, VHDLEmitter, VerilogEmitter +from core_gen.ir import Val, CustomStatement, Bit # ===----------------------------------------------------------------------===# # Parser Definition @@ -20,11 +24,11 @@ parser = argparse.ArgumentParser( description="Please specify the output path and lsq config file" ) -parser.add_argument("--output-dir", "-o", - dest="output_path", default=".", type=str) +parser.add_argument("--output-dir", "-o", dest="output_path", default=".", type=str) parser.add_argument( "--config-file", "-c", required=True, dest="config_files", default="", type=str ) +parser.add_argument("--hdl", "-l", dest="hdl", default="vhdl", type=str) # Build the target args = parser.parse_args() @@ -127,974 +131,671 @@ def __init__(self, path_rtl: str, suffix: str, configs: Configs): self.module_suffix = suffix self.lsq_config = configs - # Define information needed for VHDL file generation - # This part is inherited from the design of the original lsq_generator - self.library_header = ( - "library IEEE;\nuse IEEE.std_logic_1164.all;\nuse IEEE.numeric_std.all;\n\n" - ) - self.tab_level = 1 - self.temp_count = 0 - self.signal_init_str = "" - self.port_init_str = ( - "\tport(\n\t\treset : in std_logic;\n\t\tclock : in std_logic" - ) - self.reg_init_str = "\tprocess (clock, reset) is\n" + "\tbegin\n" - # Define the final output string self.lsq_wrapper_str = "\n\n" - def genWrapper(self): + def genWrapper(self, em: Emitter): """This function generates the desired wrapper for the LSQ""" - # PART 1: Add library information to the VHDL module - self.lsq_wrapper_str += self.library_header - - # PART 2: Define the entity - self.lsq_wrapper_str += f"entity {self.lsq_name} is\n" - - # PART 3: Add the module port definition - self.lsq_wrapper_str += self.port_init_str + em.clock_name = "clock" + em.reset_name = "reset" ## # Define all the IOs, details can be found in the table above # ! Now for storeData and loadData related IO, we assume there's only one channel, thus we don't use the *Array class - # io_storeData: output - io_storeData = VHDLLogicVecType("io_storeData", "o", self.lsq_config.dataW) - - self.lsq_wrapper_str += io_storeData.signalInit() - - # io_storeAddr: output - io_storeAddr = VHDLLogicVecType("io_storeAddr", "o", self.lsq_config.addrW) - - self.lsq_wrapper_str += io_storeAddr.signalInit() - - # io_storeEn: output - io_storeEn = VHDLLogicType("io_storeEn", "o") - - self.lsq_wrapper_str += io_storeEn.signalInit() - - # io_loadData: input - io_loadData = VHDLLogicVecType("io_loadData", "i", self.lsq_config.dataW) - - self.lsq_wrapper_str += io_loadData.signalInit() - - # io_loadAddr: output - io_loadAddr = VHDLLogicVecType("io_loadAddr", "o", self.lsq_config.addrW) - - self.lsq_wrapper_str += io_loadAddr.signalInit() - - # io_loadEn: output - io_loadEn = VHDLLogicType("io_loadEn", "o") - - self.lsq_wrapper_str += io_loadEn.signalInit() - - # io_ctrl_*_ready: output - io_ctrl_ready = VHDLLogicTypeArray( - "io_ctrl_ready", "o", self.lsq_config.numGroups - ) - self.lsq_wrapper_str += io_ctrl_ready.signalInit() - - # io_ctrl_*_valid: input - io_ctrl_valid = VHDLLogicTypeArray( - "io_ctrl_valid", "i", self.lsq_config.numGroups - ) - self.lsq_wrapper_str += io_ctrl_valid.signalInit() - - # io_ldAddr_*_ready: output - io_ldAddr_ready = VHDLLogicTypeArray( - "io_ldAddr_ready", "o", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldAddr_ready.signalInit() - - # io_ldAddr_*_valid: input - io_ldAddr_valid = VHDLLogicTypeArray( - "io_ldAddr_valid", "i", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldAddr_valid.signalInit() - - # io_ldAddr_*_bits: input - io_ldAddr_bits = VHDLLogicVecTypeArray( - "io_ldAddr_bits", "i", self.lsq_config.numLdPorts, self.lsq_config.addrW - ) - self.lsq_wrapper_str += io_ldAddr_bits.signalInit() - - # io_ldData_*_ready: input - io_ldData_ready = VHDLLogicTypeArray( - "io_ldData_ready", "i", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldData_ready.signalInit() - - # io_ldData_*_valid: output - io_ldData_valid = VHDLLogicTypeArray( - "io_ldData_valid", "o", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldData_valid.signalInit() - - # io_ldData_*_bits: output - io_ldData_bits = VHDLLogicVecTypeArray( - "io_ldData_bits", "o", self.lsq_config.numLdPorts, self.lsq_config.dataW - ) - self.lsq_wrapper_str += io_ldData_bits.signalInit() - - # io_stAddr_ready: output - io_stAddr_ready = VHDLLogicTypeArray( - "io_stAddr_ready", "o", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stAddr_ready.signalInit() - - # io_stAddr_valid: input - io_stAddr_valid = VHDLLogicTypeArray( - "io_stAddr_valid", "i", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stAddr_valid.signalInit() - - # io_stAddr_bits: input - io_stAddr_bits = VHDLLogicVecTypeArray( - "io_stAddr_bits", "i", self.lsq_config.numStPorts, self.lsq_config.addrW - ) - self.lsq_wrapper_str += io_stAddr_bits.signalInit() - - # io_stData_ready: output - io_stData_ready = VHDLLogicTypeArray( - "io_stData_ready", "o", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stData_ready.signalInit() - - # io_stData_valid: input - io_stData_valid = VHDLLogicTypeArray( - "io_stData_valid", "i", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stData_valid.signalInit() - - # io_stData_bits: input - io_stData_bits = VHDLLogicVecTypeArray( - "io_stData_bits", "i", self.lsq_config.numStPorts, self.lsq_config.dataW - ) - self.lsq_wrapper_str += io_stData_bits.signalInit() - - # io_memStart_ready: output - io_memStart_ready = VHDLLogicType("io_memStart_ready", "o") - self.lsq_wrapper_str += io_memStart_ready.signalInit() - - # io_memStart_valid: input - io_memStart_valid = VHDLLogicType("io_memStart_valid", "i") - self.lsq_wrapper_str += io_memStart_valid.signalInit() - - # io_ctrlEnd_ready: output - io_ctrlEnd_ready = VHDLLogicType("io_ctrlEnd_ready", "o") - self.lsq_wrapper_str += io_ctrlEnd_ready.signalInit() - - # io_ctrlEnd_valid: input - io_ctrlEnd_valid = VHDLLogicType("io_ctrlEnd_valid", "i") - self.lsq_wrapper_str += io_ctrlEnd_valid.signalInit() - - # io_memEnd_ready: input - io_memEnd_ready = VHDLLogicType("io_memEnd_ready", "i") - self.lsq_wrapper_str += io_memEnd_ready.signalInit() - - # io_memEnd_valid: output - io_memEnd_valid = VHDLLogicType("io_memEnd_valid", "o") - self.lsq_wrapper_str += io_memEnd_valid.signalInit() - - ## - # IO Definition finished - ## - self.lsq_wrapper_str += "\n\t);" - self.lsq_wrapper_str += "\nend entity;\n\n" + io_storeData = LogicVec( + em, "io_storeData", "o", self.lsq_config.dataW, dyn_comp=True + ) + io_storeAddr = LogicVec( + em, "io_storeAddr", "o", self.lsq_config.addrW, dyn_comp=True + ) + io_storeEn = Logic(em, "io_storeEn", "o", dyn_comp=True) + io_loadData = LogicVec( + em, "io_loadData", "i", self.lsq_config.dataW, dyn_comp=True + ) + io_loadAddr = LogicVec( + em, "io_loadAddr", "o", self.lsq_config.addrW, dyn_comp=True + ) + io_loadEn = Logic(em, "io_loadEn", "o", dyn_comp=True) + io_ctrl_ready = LogicArray( + em, "io_ctrl_ready", "o", self.lsq_config.numGroups, dyn_comp=True + ) + io_ctrl_valid = LogicArray( + em, "io_ctrl_valid", "i", self.lsq_config.numGroups, dyn_comp=True + ) + io_ldAddr_ready = LogicArray( + em, "io_ldAddr_ready", "o", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldAddr_valid = LogicArray( + em, "io_ldAddr_valid", "i", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldAddr_bits = LogicVecArray( + em, + "io_ldAddr_bits", + "i", + self.lsq_config.numLdPorts, + self.lsq_config.addrW, + dyn_comp=True, + ) + io_ldData_ready = LogicArray( + em, "io_ldData_ready", "i", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldData_valid = LogicArray( + em, "io_ldData_valid", "o", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldData_bits = LogicVecArray( + em, + "io_ldData_bits", + "o", + self.lsq_config.numLdPorts, + self.lsq_config.dataW, + dyn_comp=True, + ) + io_stAddr_ready = LogicArray( + em, "io_stAddr_ready", "o", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stAddr_valid = LogicArray( + em, "io_stAddr_valid", "i", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stAddr_bits = LogicVecArray( + em, + "io_stAddr_bits", + "i", + self.lsq_config.numStPorts, + self.lsq_config.addrW, + dyn_comp=True, + ) + io_stData_ready = LogicArray( + em, "io_stData_ready", "o", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stData_valid = LogicArray( + em, "io_stData_valid", "i", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stData_bits = LogicVecArray( + em, + "io_stData_bits", + "i", + self.lsq_config.numStPorts, + self.lsq_config.dataW, + dyn_comp=True, + ) + io_memStart_ready = Logic(em, "io_memStart_ready", "o", dyn_comp=True) + io_memStart_valid = Logic(em, "io_memStart_valid", "i", dyn_comp=True) + io_ctrlEnd_ready = Logic(em, "io_ctrlEnd_ready", "o", dyn_comp=True) + io_ctrlEnd_valid = Logic(em, "io_ctrlEnd_valid", "i", dyn_comp=True) + io_memEnd_ready = Logic(em, "io_memEnd_ready", "i", dyn_comp=True) + io_memEnd_valid = Logic(em, "io_memEnd_valid", "o", dyn_comp=True) ## # Architecture definition start ## - self.lsq_wrapper_str += f"architecture arch of {self.lsq_name} is\n" # Define internal signals - rreq_ready = VHDLLogicTypeArray( - "rreq_ready", "w", self.lsq_config.numLdMem) - self.lsq_wrapper_str += rreq_ready.signalInit() - - rresp_valid = VHDLLogicTypeArray( - "rresp_valid", "w", self.lsq_config.numLdMem) - self.lsq_wrapper_str += rresp_valid.signalInit() - - rresp_id = VHDLLogicVecTypeArray( - "rresp_id", "w", self.lsq_config.numLdMem, self.lsq_config.idW + rreq_ready = LogicArray( + em, + "rreq_ready", + "w", + self.lsq_config.numLdMem, + dyn_comp=True, + force_reg=True, + ) + rresp_valid = LogicArray( + em, + "rresp_valid", + "w", + self.lsq_config.numLdMem, + dyn_comp=True, + force_reg=True, + ) + rresp_id = LogicVecArray( + em, + "rresp_id", + "w", + self.lsq_config.numLdMem, + self.lsq_config.idW, + dyn_comp=True, + force_reg=True, + ) + wreq_ready = LogicArray( + em, + "wreq_ready", + "w", + self.lsq_config.numStMem, + dyn_comp=True, + force_reg=True, + ) + wresp_valid = LogicArray( + em, + "wresp_valid", + "w", + self.lsq_config.numStMem, + dyn_comp=True, + force_reg=True, + ) + wresp_id = LogicVecArray( + em, + "wresp_id", + "w", + self.lsq_config.numStMem, + self.lsq_config.idW, + dyn_comp=True, + force_reg=True, + ) + rreq_id = LogicVecArray( + em, + "rreq_id", + "w", + self.lsq_config.numLdMem, + self.lsq_config.idW, + dyn_comp=True, + ) + + wreq_id = LogicVecArray( + em, + "wreq_id", + "w", + self.lsq_config.numStMem, + self.lsq_config.idW, + dyn_comp=True, ) - self.lsq_wrapper_str += rresp_id.signalInit() - - wreq_ready = VHDLLogicTypeArray( - "wreq_ready", "w", self.lsq_config.numStMem) - self.lsq_wrapper_str += wreq_ready.signalInit() - - wresp_valid = VHDLLogicTypeArray( - "wresp_valid", "w", self.lsq_config.numStMem) - self.lsq_wrapper_str += wresp_valid.signalInit() - - wresp_id = VHDLLogicVecTypeArray( - "wresp_id", "w", self.lsq_config.numStMem, self.lsq_config.idW - ) - self.lsq_wrapper_str += wresp_id.signalInit() - - rreq_id = VHDLLogicVecTypeArray( - "rreq_id", "w", self.lsq_config.numLdMem, self.lsq_config.idW - ) - self.lsq_wrapper_str += rreq_id.signalInit() - - wreq_id = VHDLLogicVecTypeArray( - "wreq_id", "w", self.lsq_config.numStMem, self.lsq_config.idW - ) - self.lsq_wrapper_str += wreq_id.signalInit() - - # Begin actual arch logic definition - self.lsq_wrapper_str += "begin\n" # Define the process to update # rreq_ready, rresp_valid - self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" - self.lsq_wrapper_str += ( - "\t-- Process for rreq_ready, rresp_valid and rresp_id\n" + em.add_comment( + "--------------------------------------------------------------------------" ) - self.lsq_wrapper_str += self.reg_init_str - self.lsq_wrapper_str += "\t" * \ - (self.tab_level + 1) + "if reset = '1' then\n" + em.add_comment("Process for rreq_ready, rresp_valid and rresp_id") + + em.add_statement(em.get_reg_init_str()) + em.increase_indent() + em.add_custom_statement( + CustomStatement("if reset = '1' then", "if (reset) begin") + ) + em.increase_indent() for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += OpTab(rreq_ready[i], (self.tab_level + 2), "'0'") - self.lsq_wrapper_str += OpTab(rresp_valid[i], - (self.tab_level + 2), "'0'") - self.lsq_wrapper_str += OpTab( - rresp_id[i], (self.tab_level + 2), "(", "others", "=>", "'0'", ")" - ) + em.add_assignment(rreq_ready[i], Bit(0), in_process=True) + em.add_assignment(rresp_valid[i], Bit(0), in_process=True) + em.add_assignment(rresp_id[i], Val(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 1) + "elsif rising_edge(clock) then\n" + em.decrease_indent() + em.add_custom_statement( + CustomStatement("elsif rising_edge(clock) then", "end\nelse begin") ) + em.increase_indent() for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += OpTab(rreq_ready[i], (self.tab_level + 2), "'1'") + em.add_assignment(rreq_ready[i], Bit(1), in_process=True) - self.lsq_wrapper_str += ( - "\n" - + "\t" * (self.tab_level + 2) - + "if " - + io_loadEn.getNameWrite() - + " = '1' then\n" + em.add_custom_statement( + CustomStatement( + f"if {io_loadEn.getNameWrite()} = '1' then", + f"if ({io_loadEn.getNameWrite()}) begin", + ) ) + em.increase_indent() for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += OpTab(rresp_valid[i], - (self.tab_level + 3), "'1'") - self.lsq_wrapper_str += OpTab(rresp_id[i], - (self.tab_level + 3), rreq_id[i]) + em.add_assignment(rresp_valid[i], Bit(1), in_process=True) + em.add_assignment(rresp_id[i], rreq_id[i], in_process=True) - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + "else\n" + em.decrease_indent() + em.add_custom_statement(CustomStatement("else", "end\nelse begin")) + em.increase_indent() for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += OpTab(rresp_valid[i], - (self.tab_level + 3), "'0'") + em.add_assignment(rresp_valid[i], Bit(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + "end if;\n" - + "\t" * 2 - + "end if;\n" - + "\tend process;\n" - ) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end process;", "end")) - self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" + em.add_comment( + "--------------------------------------------------------------------------" + ) # Define the process to update # wreq_ready, wresp_valid, wresp_id - self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" - self.lsq_wrapper_str += ( - "\t-- Process for wreq_ready, wresp_valid and wresp_id\n" + em.add_comment( + "--------------------------------------------------------------------------" ) - self.lsq_wrapper_str += self.reg_init_str - self.lsq_wrapper_str += "\t" * \ - (self.tab_level + 1) + "if reset = '1' then\n" + em.add_comment("Process for wreq_ready, wresp_valid and wresp_id") + + em.add_statement(em.get_reg_init_str()) + em.increase_indent() + em.add_custom_statement( + CustomStatement("if reset = '1' then", "if (reset) begin") + ) + em.increase_indent() for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wreq_ready[i], (self.tab_level + 2), "'0'") - self.lsq_wrapper_str += OpTab(wresp_valid[i], - (self.tab_level + 2), "'0'") - self.lsq_wrapper_str += OpTab( - wresp_id[i], (self.tab_level + 2), "(", "others", "=>", "'0'", ")" - ) + em.add_assignment(wreq_ready[i], Bit(0), in_process=True) + em.add_assignment(wresp_valid[i], Bit(0), in_process=True) + em.add_assignment(wresp_id[i], Val(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 1) + "elsif rising_edge(clock) then\n" + em.decrease_indent() + em.add_custom_statement( + CustomStatement("elsif rising_edge(clock) then", "end\nelse begin") ) + em.increase_indent() for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wreq_ready[i], (self.tab_level + 2), "'1'") + em.add_assignment(wreq_ready[i], Bit(1), in_process=True) - self.lsq_wrapper_str += ( - "\n" - + "\t" * (self.tab_level + 2) - + "if " - + io_storeEn.getNameWrite() - + " = '1' then\n" + em.add_custom_statement( + CustomStatement( + f"if {io_storeEn.getNameWrite()} = '1' then", + f"if ({io_storeEn.getNameWrite()}) begin", + ) ) + em.increase_indent() for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wresp_valid[i], - (self.tab_level + 3), "'1'") - self.lsq_wrapper_str += OpTab(wresp_id[i], - (self.tab_level + 3), rreq_id[i]) + em.add_assignment(wresp_valid[i], Bit(1), in_process=True) + em.add_assignment(wresp_id[i], rreq_id[i], in_process=True) - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + "else\n" + em.decrease_indent() + em.add_custom_statement(CustomStatement("else", "end\nelse begin")) + em.increase_indent() for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wresp_valid[i], - (self.tab_level + 3), "'0'") + em.add_assignment(wresp_valid[i], Bit(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + "end if;\n" - + "\t" * 2 - + "end if;\n" - + "\tend process;\n" - ) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end process;", "end")) self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" ### # Instantiate the LSQ_core module ### - self.lsq_wrapper_str += "\t-- Instantiate the core LSQ logic\n" - self.lsq_wrapper_str += ( - "\t" * (self.tab_level) - + f"{self.lsq_name}_core : entity work.{self.lsq_name}_core\n" - ) - self.lsq_wrapper_str += "\t" * (self.tab_level + 1) + f"port map(\n" - - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + f"rst => reset,\n" - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + f"clk => clock,\n" + em.add_comment("Instantiate the core LSQ logic") + em.start_instantiation(self.lsq_name + "_core") - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_data_0_o => {io_storeData.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_addr_0_o => {io_storeAddr.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_valid_0_o => {io_storeEn.getNameWrite()},\n" - ) + em.add_map("rst", "reset") + em.add_map("clk", "clock") - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_data_0_i => {io_loadData.getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_addr_0_o => {io_loadAddr.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_valid_0_o => {io_loadEn.getNameWrite()},\n" - ) + em.add_map("empty_o") + em.add_map("wreq_data_0_o", io_storeData.getNameWrite()) + em.add_map("wreq_addr_0_o", io_storeAddr.getNameWrite()) + em.add_map("wreq_valid_0_o", io_storeEn.getNameWrite()) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"memStart_ready_o => {io_memStart_ready.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"memStart_valid_i => {io_memStart_valid.getNameRead()},\n" - ) + em.add_map("rresp_data_0_i", io_loadData.getNameRead()) + em.add_map("rreq_addr_0_o", io_loadAddr.getNameWrite()) + em.add_map("rreq_valid_0_o", io_loadEn.getNameWrite()) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ctrlEnd_ready_o => {io_ctrlEnd_ready.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ctrlEnd_valid_i => {io_ctrlEnd_valid.getNameRead()},\n" - ) + em.add_map("memStart_ready_o", io_memStart_ready.getNameWrite()) + em.add_map("memStart_valid_i", io_memStart_valid.getNameRead()) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"memEnd_ready_i => {io_memEnd_ready.getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"memEnd_valid_o => {io_memEnd_valid.getNameWrite()},\n" - ) + em.add_map("ctrlEnd_ready_o", io_ctrlEnd_ready.getNameWrite()) + em.add_map("ctrlEnd_valid_i", io_ctrlEnd_valid.getNameRead()) + em.add_map("memEnd_ready_i", io_memEnd_ready.getNameRead()) + em.add_map("memEnd_valid_o", io_memEnd_valid.getNameWrite()) for i in range(self.lsq_config.numGroups): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"group_init_ready_{i}_o => {io_ctrl_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"group_init_valid_{i}_i => {io_ctrl_valid[i].getNameRead()},\n" - ) - + em.add_map(f"group_init_ready_{i}_o", io_ctrl_ready[i].getNameWrite()) + em.add_map(f"group_init_valid_{i}_i", io_ctrl_valid[i].getNameRead()) for i in range(self.lsq_config.numLdPorts): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_addr_ready_{i}_o => {io_ldAddr_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_addr_valid_{i}_i => {io_ldAddr_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_addr_{i}_i => {io_ldAddr_bits[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_data_ready_{i}_i => {io_ldData_ready[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_data_valid_{i}_o => {io_ldData_valid[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_data_{i}_o => {io_ldData_bits[i].getNameWrite()},\n" - ) + em.add_map(f"ldp_addr_ready_{i}_o", io_ldAddr_ready[i].getNameWrite()) + em.add_map(f"ldp_addr_valid_{i}_i", io_ldAddr_valid[i].getNameRead()) + em.add_map(f"ldp_addr_{i}_i", io_ldAddr_bits[i].getNameRead()) + em.add_map(f"ldp_data_ready_{i}_i", io_ldData_ready[i].getNameRead()) + em.add_map(f"ldp_data_valid_{i}_o", io_ldData_valid[i].getNameWrite()) + em.add_map(f"ldp_data_{i}_o", io_ldData_bits[i].getNameWrite()) for i in range(self.lsq_config.numStPorts): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_addr_ready_{i}_o => {io_stAddr_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_addr_valid_{i}_i => {io_stAddr_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_addr_{i}_i => {io_stAddr_bits[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_data_ready_{i}_o => {io_stData_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_data_valid_{i}_i => {io_stData_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_data_{i}_i => {io_stData_bits[i].getNameRead()},\n" - ) + em.add_map(f"stp_addr_ready_{i}_o", io_stAddr_ready[i].getNameWrite()) + em.add_map(f"stp_addr_valid_{i}_i", io_stAddr_valid[i].getNameRead()) + em.add_map(f"stp_addr_{i}_i", io_stAddr_bits[i].getNameRead()) + em.add_map(f"stp_data_ready_{i}_o", io_stData_ready[i].getNameWrite()) + em.add_map(f"stp_data_valid_{i}_i", io_stData_valid[i].getNameRead()) + em.add_map(f"stp_data_{i}_i", io_stData_bits[i].getNameRead()) # Define all AXI ports, we assume there is only 1 channel for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_ready_{i}_i => {rreq_ready[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_valid_{i}_i => {rresp_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_id_{i}_i => {rresp_id[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_id_0_o => {rreq_id[i].getNameWrite()},\n" - ) + em.add_map(f"rreq_ready_{i}_i", rreq_ready[i].getNameRead()) + em.add_map(f"rresp_valid_{i}_i", rresp_valid[i].getNameRead()) + em.add_map(f"rresp_ready_{i}_o") + em.add_map(f"rresp_id_{i}_i", rresp_id[i].getNameRead()) + em.add_map(f"rreq_id_0_o", rreq_id[i].getNameWrite()) for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_ready_{i}_i => {wreq_ready[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wresp_valid_{i}_i => {wresp_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wresp_id_{i}_i => {wresp_id[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_id_{i}_o => {wreq_id[i].getNameWrite()}\n" - ) + em.add_map(f"wreq_ready_{i}_i", wreq_ready[i].getNameRead()) + em.add_map(f"wresp_valid_{i}_i", wresp_valid[i].getNameRead()) + em.add_map(f"wresp_ready_{i}_o") + em.add_map(f"wresp_id_{i}_i", wresp_id[i].getNameRead()) + em.add_map(f"wreq_id_{i}_o", wreq_id[i].getNameWrite()) - self.lsq_wrapper_str += "\t" * (self.tab_level + 1) + ");\n" - - # End module definition - self.lsq_wrapper_str += "end architecture;\n" + em.complete_instantiation() # Write to the file - with open(f"{self.output_folder}/{self.lsq_name}.vhd", "w") as file: - file.write(self.lsq_wrapper_str) + output_str = em.get_definition_str(self.lsq_name) + with open( + f"{self.output_folder}/{self.lsq_name}.{em.get_file_suffix()}", "w" + ) as file: + file.write(output_str) return self.lsq_wrapper_str - def genWrapperSlave(self): + def genWrapperSlave(self, em: Emitter): """This function generates the desired wrapper for the LSQ""" - # PART 1: Add library information to the VHDL module - self.lsq_wrapper_str += self.library_header - - # PART 2: Define the entity - self.lsq_wrapper_str += f"entity {self.lsq_name} is\n" - - # PART 3: Add the module port definition - self.lsq_wrapper_str += self.port_init_str - ## # Define all the IOs - - # io_stDataToMC_bits: output - io_storeData = VHDLLogicVecType( - "io_stDataToMC_bits", "o", self.lsq_config.dataW - ) - self.lsq_wrapper_str += io_storeData.signalInit() - - # io_stAddrToMC_bits: output - io_storeAddr = VHDLLogicVecType( - "io_stAddrToMC_bits", "o", self.lsq_config.addrW - ) - self.lsq_wrapper_str += io_storeAddr.signalInit() - - # io_ldDataFromMC_bits: input - io_loadData = VHDLLogicVecType( - "io_ldDataFromMC_bits", "i", self.lsq_config.dataW - ) - self.lsq_wrapper_str += io_loadData.signalInit() - - # io_ldAddrToMC_bits: output - io_loadAddr = VHDLLogicVecType( - "io_ldAddrToMC_bits", "o", self.lsq_config.addrW) - self.lsq_wrapper_str += io_loadAddr.signalInit() - - # io_ctrl_*_ready: output - io_ctrl_ready = VHDLLogicTypeArray( - "io_ctrl_ready", "o", self.lsq_config.numGroups - ) - self.lsq_wrapper_str += io_ctrl_ready.signalInit() - - # io_ctrl_*_valid: input - io_ctrl_valid = VHDLLogicTypeArray( - "io_ctrl_valid", "i", self.lsq_config.numGroups - ) - self.lsq_wrapper_str += io_ctrl_valid.signalInit() - - # io_ldAddr_*_ready: output - io_ldAddr_ready = VHDLLogicTypeArray( - "io_ldAddr_ready", "o", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldAddr_ready.signalInit() - - # io_ldAddr_*_valid: input - io_ldAddr_valid = VHDLLogicTypeArray( - "io_ldAddr_valid", "i", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldAddr_valid.signalInit() - - # io_ldAddr_*_bits: input - io_ldAddr_bits = VHDLLogicVecTypeArray( - "io_ldAddr_bits", "i", self.lsq_config.numLdPorts, self.lsq_config.addrW - ) - self.lsq_wrapper_str += io_ldAddr_bits.signalInit() - - # io_ldData_*_ready: input - io_ldData_ready = VHDLLogicTypeArray( - "io_ldData_ready", "i", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldData_ready.signalInit() - - # io_ldData_*_valid: output - io_ldData_valid = VHDLLogicTypeArray( - "io_ldData_valid", "o", self.lsq_config.numLdPorts - ) - self.lsq_wrapper_str += io_ldData_valid.signalInit() - - # io_ldData_*_bits: output - io_ldData_bits = VHDLLogicVecTypeArray( - "io_ldData_bits", "o", self.lsq_config.numLdPorts, self.lsq_config.dataW - ) - self.lsq_wrapper_str += io_ldData_bits.signalInit() - - # io_stAddr_ready: output - io_stAddr_ready = VHDLLogicTypeArray( - "io_stAddr_ready", "o", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stAddr_ready.signalInit() - - # io_stAddr_valid: input - io_stAddr_valid = VHDLLogicTypeArray( - "io_stAddr_valid", "i", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stAddr_valid.signalInit() - - # io_stAddr_bits: input - io_stAddr_bits = VHDLLogicVecTypeArray( - "io_stAddr_bits", "i", self.lsq_config.numStPorts, self.lsq_config.addrW - ) - self.lsq_wrapper_str += io_stAddr_bits.signalInit() - - # io_stData_ready: output - io_stData_ready = VHDLLogicTypeArray( - "io_stData_ready", "o", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stData_ready.signalInit() - - # io_stData_valid: input - io_stData_valid = VHDLLogicTypeArray( - "io_stData_valid", "i", self.lsq_config.numStPorts - ) - self.lsq_wrapper_str += io_stData_valid.signalInit() - - # io_stData_bits: input - io_stData_bits = VHDLLogicVecTypeArray( - "io_stData_bits", "i", self.lsq_config.numStPorts, self.lsq_config.dataW - ) - self.lsq_wrapper_str += io_stData_bits.signalInit() - - # io_ldAddrToMC_ready: input - io_ldAddrToMC_ready = VHDLLogicType("io_ldAddrToMC_ready", "i") - self.lsq_wrapper_str += io_ldAddrToMC_ready.signalInit() - - # io_ldAddrToMC_valid - io_ldAddrToMC_valid = VHDLLogicType("io_ldAddrToMC_valid", "o") - self.lsq_wrapper_str += io_ldAddrToMC_valid.signalInit() - - # io_ldDataFromMC_ready - io_ldDataFromMC_ready = VHDLLogicType("io_ldDataFromMC_ready", "o") - self.lsq_wrapper_str += io_ldDataFromMC_ready.signalInit() - - # io_ldDataFromMC_valid - io_ldDataFromMC_valid = VHDLLogicType("io_ldDataFromMC_valid", "i") - self.lsq_wrapper_str += io_ldDataFromMC_valid.signalInit() - - # io_stAddrToMC_ready - io_stAddrToMC_ready = VHDLLogicType("io_stAddrToMC_ready", "i") - self.lsq_wrapper_str += io_stAddrToMC_ready.signalInit() - - # io_stAddrToMC_valid - io_stAddrToMC_valid = VHDLLogicType("io_stAddrToMC_valid", "o") - self.lsq_wrapper_str += io_stAddrToMC_valid.signalInit() - - # io_stDataToMC_ready - io_stDataToMC_ready = VHDLLogicType("io_stDataToMC_ready", "i") - self.lsq_wrapper_str += io_stDataToMC_ready.signalInit() - - # io_stDataToMC_valid - io_stDataToMC_valid = VHDLLogicType("io_stDataToMC_valid", "o") - self.lsq_wrapper_str += io_stDataToMC_valid.signalInit() + em.clock_name = "clock" + em.reset_name = "reset" + + io_storeData = LogicVec( + em, "io_stDataToMC_bits", "o", self.lsq_config.dataW, dyn_comp=True + ) + io_storeAddr = LogicVec( + em, "io_stAddrToMC_bits", "o", self.lsq_config.addrW, dyn_comp=True + ) + io_loadData = LogicVec( + em, "io_ldDataFromMC_bits", "i", self.lsq_config.dataW, dyn_comp=True + ) + io_loadAddr = LogicVec( + em, "io_ldAddrToMC_bits", "o", self.lsq_config.addrW, dyn_comp=True + ) + io_ctrl_ready = LogicArray( + em, "io_ctrl_ready", "o", self.lsq_config.numGroups, dyn_comp=True + ) + io_ctrl_valid = LogicArray( + em, "io_ctrl_valid", "i", self.lsq_config.numGroups, dyn_comp=True + ) + io_ldAddr_ready = LogicArray( + em, "io_ldAddr_ready", "o", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldAddr_valid = LogicArray( + em, "io_ldAddr_valid", "i", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldAddr_bits = LogicVecArray( + em, + "io_ldAddr_bits", + "i", + self.lsq_config.numLdPorts, + self.lsq_config.addrW, + dyn_comp=True, + ) + io_ldData_ready = LogicArray( + em, "io_ldData_ready", "i", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldData_valid = LogicArray( + em, "io_ldData_valid", "o", self.lsq_config.numLdPorts, dyn_comp=True + ) + io_ldData_bits = LogicVecArray( + em, + "io_ldData_bits", + "o", + self.lsq_config.numLdPorts, + self.lsq_config.dataW, + dyn_comp=True, + ) + io_stAddr_ready = LogicArray( + em, "io_stAddr_ready", "o", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stAddr_valid = LogicArray( + em, "io_stAddr_valid", "i", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stAddr_bits = LogicVecArray( + em, + "io_stAddr_bits", + "i", + self.lsq_config.numStPorts, + self.lsq_config.addrW, + dyn_comp=True, + ) + io_stData_ready = LogicArray( + em, "io_stData_ready", "o", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stData_valid = LogicArray( + em, "io_stData_valid", "i", self.lsq_config.numStPorts, dyn_comp=True + ) + io_stData_bits = LogicVecArray( + em, + "io_stData_bits", + "i", + self.lsq_config.numStPorts, + self.lsq_config.dataW, + dyn_comp=True, + ) + io_ldAddrToMC_ready = Logic(em, "io_ldAddrToMC_ready", "i", dyn_comp=True) + io_ldAddrToMC_valid = Logic(em, "io_ldAddrToMC_valid", "o", dyn_comp=True) + io_ldDataFromMC_ready = Logic(em, "io_ldDataFromMC_ready", "o", dyn_comp=True) + io_ldDataFromMC_valid = Logic(em, "io_ldDataFromMC_valid", "i", dyn_comp=True) + io_stAddrToMC_ready = Logic(em, "io_stAddrToMC_ready", "i", dyn_comp=True) + io_stAddrToMC_valid = Logic(em, "io_stAddrToMC_valid", "o", dyn_comp=True) + io_stDataToMC_ready = Logic(em, "io_stDataToMC_ready", "i", dyn_comp=True) + io_stDataToMC_valid = Logic(em, "io_stDataToMC_valid", "o", dyn_comp=True) ## # IO Definition finished ## - self.lsq_wrapper_str += "\n\t);" - self.lsq_wrapper_str += "\nend entity;\n\n" ## # Architecture definition start ## - self.lsq_wrapper_str += f"architecture arch of {self.lsq_name} is\n" # Define internal signals - io_loadEn = VHDLLogicType("io_loadEn", "w") - self.lsq_wrapper_str += io_loadEn.signalInit() - - io_storeEn = VHDLLogicType("io_storeEn", "w") - self.lsq_wrapper_str += io_storeEn.signalInit() - - rresp_id = VHDLLogicVecTypeArray( - "rresp_id", "w", self.lsq_config.numLdMem, self.lsq_config.idW + io_loadEn = Logic(em, "io_loadEn", "w", dyn_comp=True) + io_storeEn = Logic(em, "io_storeEn", "w", dyn_comp=True) + + rresp_id = LogicVecArray( + em, + "rresp_id", + "w", + self.lsq_config.numLdMem, + self.lsq_config.idW, + dyn_comp=True, + force_reg=True, + ) + + wreq_ready = LogicArray( + em, + "wreq_ready", + "w", + self.lsq_config.numStMem, + dyn_comp=True, + ) + wresp_valid = LogicArray( + em, + "wresp_valid", + "w", + self.lsq_config.numStMem, + dyn_comp=True, + force_reg=True, + ) + wresp_id = LogicVecArray( + em, + "wresp_id", + "w", + self.lsq_config.numStMem, + self.lsq_config.idW, + dyn_comp=True, + force_reg=True, + ) + + rreq_id = LogicVecArray( + em, + "rreq_id", + "w", + self.lsq_config.numLdMem, + self.lsq_config.idW, + dyn_comp=True, + ) + wreq_id = LogicVecArray( + em, + "wreq_id", + "w", + self.lsq_config.numStMem, + self.lsq_config.idW, + dyn_comp=True, ) - self.lsq_wrapper_str += rresp_id.signalInit() - - wreq_ready = VHDLLogicTypeArray( - "wreq_ready", "w", self.lsq_config.numStMem) - self.lsq_wrapper_str += wreq_ready.signalInit() - - wresp_valid = VHDLLogicTypeArray( - "wresp_valid", "w", self.lsq_config.numStMem) - self.lsq_wrapper_str += wresp_valid.signalInit() - - wresp_id = VHDLLogicVecTypeArray( - "wresp_id", "w", self.lsq_config.numStMem, self.lsq_config.idW - ) - self.lsq_wrapper_str += wresp_id.signalInit() - - rreq_id = VHDLLogicVecTypeArray( - "rreq_id", "w", self.lsq_config.numLdMem, self.lsq_config.idW - ) - self.lsq_wrapper_str += rreq_id.signalInit() - - wreq_id = VHDLLogicVecTypeArray( - "wreq_id", "w", self.lsq_config.numStMem, self.lsq_config.idW - ) - self.lsq_wrapper_str += wreq_id.signalInit() - - # Begin actual arch logic definition - self.lsq_wrapper_str += "begin\n" # Define the process to update # rresp_id - self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" - self.lsq_wrapper_str += "\t-- Process for rresp_id\n" - self.lsq_wrapper_str += self.reg_init_str - self.lsq_wrapper_str += "\t" * \ - (self.tab_level + 1) + "if reset = '1' then\n" + em.add_comment( + "----------------------------------------------------------------------------\n" + ) + em.add_comment("Process for rresp_id\n") + em.add_statement(em.get_reg_init_str()) + em.add_custom_statement( + CustomStatement("if reset = '1' then", "if (reset) begin") + ) + em.increase_indent() for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += OpTab( - rresp_id[i], (self.tab_level + 2), "(", "others", "=>", "'0'", ")" - ) + em.add_assignment(rresp_id[i], Val(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 1) + "elsif rising_edge(clock) then\n" + em.decrease_indent() + em.add_custom_statement( + CustomStatement("elsif rising_edge(clock) then", "end\nelse begin") ) - self.lsq_wrapper_str += ( - "\n" - + "\t" * (self.tab_level + 2) - + "if " - + io_loadEn.getNameWrite() - + " = '1' then\n" + em.increase_indent() + + em.add_custom_statement( + CustomStatement( + f"if {io_loadEn.getNameWrite()} = '1' then", + f"if ({io_loadEn.getNameWrite()}) begin", + ) ) for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += OpTab(rresp_id[i], - (self.tab_level + 3), rreq_id[i]) + em.add_assignment(rresp_id[i], rreq_id[i], in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + "end if;\n" - + "\t" * 2 - + "end if;\n" - + "\tend process;\n" - ) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end process;", "end")) - self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" + em.add_comment( + "--------------------------------------------------------------------------\n" + ) # Define the process to update # wresp_valid, wresp_id - self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" - self.lsq_wrapper_str += ( - "\t-- Process for wreq_ready, wresp_valid and wresp_id\n" + em.add_comment( + "----------------------------------------------------------------------------\n" ) - self.lsq_wrapper_str += self.reg_init_str - self.lsq_wrapper_str += "\t" * \ - (self.tab_level + 1) + "if reset = '1' then\n" + em.add_comment("Process for wreq_ready, wresp_valid and wresp_id\n") + em.add_statement(em.get_reg_init_str()) + em.add_custom_statement( + CustomStatement("if reset = '1' then", "if (reset) begin") + ) + em.increase_indent() for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wresp_valid[i], - (self.tab_level + 2), "'0'") - self.lsq_wrapper_str += OpTab( - wresp_id[i], (self.tab_level + 2), "(", "others", "=>", "'0'", ")" - ) + em.add_assignment(wresp_valid[i], Bit(0), in_process=True) + em.add_assignment(wresp_id[i], Val(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 1) + "elsif rising_edge(clock) then\n" + em.add_custom_statement( + CustomStatement("elsif rising_edge(clock) then", "end\nelse begin") ) - self.lsq_wrapper_str += ( - "\n" - + "\t" * (self.tab_level + 2) - + "if " - + io_storeEn.getNameWrite() - + " = '1' then\n" + em.add_custom_statement( + CustomStatement( + f"if {io_storeEn.getNameWrite()} = '1' then", + f"if ({io_storeEn.getNameWrite()}) begin", + ) ) for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wresp_valid[i], - (self.tab_level + 3), "'1'") - self.lsq_wrapper_str += OpTab(wresp_id[i], - (self.tab_level + 3), rreq_id[i]) + em.add_assignment(wresp_valid[i], Bit(1), in_process=True) + em.add_assignment(wresp_id[i], rreq_id[i], in_process=True) - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + "else\n" + em.add_custom_statement(CustomStatement("else", "end\nelse begin")) for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += OpTab(wresp_valid[i], - (self.tab_level + 3), "'0'") + em.add_assignment(wresp_valid[i], Bit(0), in_process=True) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + "end if;\n" - + "\t" * 2 - + "end if;\n" - + "\tend process;\n" - ) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end if;", "end")) + em.decrease_indent() + em.add_custom_statement(CustomStatement("end process;", "end")) self.lsq_wrapper_str += "\t----------------------------------------------------------------------------\n" ### # Signal Assignment ### - self.lsq_wrapper_str += "\t-- Signal Assignment\n" - self.lsq_wrapper_str += OpTab(io_ldAddrToMC_valid, - self.tab_level, io_loadEn) - self.lsq_wrapper_str += OpTab(io_stAddrToMC_valid, - self.tab_level, io_storeEn) - self.lsq_wrapper_str += OpTab(io_stDataToMC_valid, - self.tab_level, io_storeEn) - self.lsq_wrapper_str += OpTab( - wreq_ready[0], - self.tab_level, - io_stAddrToMC_ready, - "and", - io_stDataToMC_ready, - ) + em.add_comment("Signal Assignment") + em.add_assignment(io_ldAddrToMC_valid, io_loadEn) + em.add_assignment(io_stAddrToMC_valid, io_storeEn) + em.add_assignment(io_stDataToMC_valid, io_storeEn) + em.add_assignment(wreq_ready[0], io_stAddrToMC_ready & io_stDataToMC_ready) ### # Instantiate the LSQ_core module ### - self.lsq_wrapper_str += "\t-- Instantiate the core LSQ logic\n" - self.lsq_wrapper_str += ( - "\t" * (self.tab_level) - + f"{self.lsq_name}_core : entity work.{self.lsq_name}_core\n" - ) - self.lsq_wrapper_str += "\t" * (self.tab_level + 1) + f"port map(\n" + em.add_comment("Instantiate the core LSQ logic\n") + em.start_instantiation(self.lsq_name + "_core") - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + f"rst => reset,\n" - self.lsq_wrapper_str += "\t" * (self.tab_level + 2) + f"clk => clock,\n" + em.add_map("rst", "reset") + em.add_map("clk", "clock") - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_data_0_o => {io_storeData.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_addr_0_o => {io_storeAddr.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_valid_0_o => {io_storeEn.getNameWrite()},\n" - ) + em.add_map("wreq_data_0_o", io_storeData.getNameWrite()) + em.add_map("wreq_addr_0_o", io_storeAddr.getNameWrite()) + em.add_map("wreq_valid_0_o", io_storeEn.getNameWrite()) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_data_0_i => {io_loadData.getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_addr_0_o => {io_loadAddr.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_valid_0_o => {io_loadEn.getNameWrite()},\n" - ) + em.add_map("rresp_data_0_i", io_loadData.getNameRead()) + em.add_map("rreq_addr_0_o", io_loadAddr.getNameWrite()) + em.add_map("rreq_valid_0_o", io_loadEn.getNameWrite()) for i in range(self.lsq_config.numGroups): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"group_init_ready_{i}_o => {io_ctrl_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"group_init_valid_{i}_i => {io_ctrl_valid[i].getNameRead()},\n" - ) + em.add_map(f"group_init_ready_{i}_o", io_ctrl_ready[i].getNameWrite()) + em.add_map(f"group_init_valid_{i}_i", io_ctrl_valid[i].getNameRead()) for i in range(self.lsq_config.numLdPorts): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_addr_ready_{i}_o => {io_ldAddr_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_addr_valid_{i}_i => {io_ldAddr_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_addr_{i}_i => {io_ldAddr_bits[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_data_ready_{i}_i => {io_ldData_ready[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_data_valid_{i}_o => {io_ldData_valid[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"ldp_data_{i}_o => {io_ldData_bits[i].getNameWrite()},\n" - ) + em.add_map(f"ldp_addr_ready_{i}_o", io_ldAddr_ready[i].getNameWrite()) + em.add_map(f"ldp_addr_valid_{i}_i", io_ldAddr_valid[i].getNameRead()) + em.add_map(f"ldp_addr_{i}_i", io_ldAddr_bits[i].getNameRead()) + em.add_map(f"ldp_data_ready_{i}_i", io_ldData_ready[i].getNameRead()) + em.add_map(f"ldp_data_valid_{i}_o", io_ldData_valid[i].getNameWrite()) + em.add_map(f"ldp_data_{i}_o", io_ldData_bits[i].getNameWrite()) for i in range(self.lsq_config.numStPorts): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_addr_ready_{i}_o => {io_stAddr_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_addr_valid_{i}_i => {io_stAddr_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_addr_{i}_i => {io_stAddr_bits[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_data_ready_{i}_o => {io_stData_ready[i].getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_data_valid_{i}_i => {io_stData_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"stp_data_{i}_i => {io_stData_bits[i].getNameRead()},\n" - ) + em.add_map(f"stp_addr_ready_{i}_o", io_stAddr_ready[i].getNameWrite()) + em.add_map(f"stp_addr_valid_{i}_i", io_stAddr_valid[i].getNameRead()) + em.add_map(f"stp_addr_{i}_i", io_stAddr_bits[i].getNameRead()) + em.add_map(f"stp_data_ready_{i}_o", io_stData_ready[i].getNameWrite()) + em.add_map(f"stp_data_valid_{i}_i", io_stData_valid[i].getNameRead()) + em.add_map(f"stp_data_{i}_i", io_stData_bits[i].getNameRead()) # Define all AXI ports, we assume there is only 1 channel for i in range(self.lsq_config.numLdMem): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_ready_{i}_i => {io_ldAddrToMC_ready.getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_valid_{i}_i => {io_ldDataFromMC_valid.getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_ready_{i}_o => {io_ldDataFromMC_ready.getNameWrite()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rresp_id_{i}_i => {rresp_id[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"rreq_id_0_o => {rreq_id[i].getNameWrite()},\n" - ) + em.add_map(f"rreq_ready_{i}_i", io_ldAddrToMC_ready.getNameRead()) + em.add_map(f"rresp_valid_{i}_i", io_ldDataFromMC_valid.getNameRead()) + em.add_map(f"rresp_ready_{i}_o", io_ldDataFromMC_ready.getNameWrite()) + em.add_map(f"rresp_id_{i}_i", rresp_id[i].getNameRead()) + em.add_map(f"rreq_id_0_o", rreq_id[i].getNameWrite()) for i in range(self.lsq_config.numStMem): - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_ready_{i}_i => {wreq_ready[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wresp_valid_{i}_i => {wresp_valid[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wresp_id_{i}_i => {wresp_id[i].getNameRead()},\n" - ) - self.lsq_wrapper_str += ( - "\t" * (self.tab_level + 2) - + f"wreq_id_{i}_o => {wreq_id[i].getNameWrite()}\n" - ) - - self.lsq_wrapper_str += "\t" * (self.tab_level + 1) + ");\n" + em.add_map(f"wreq_ready_{i}_i", wreq_ready[i].getNameRead()) + em.add_map(f"wresp_valid_{i}_i", wresp_valid[i].getNameRead()) + em.add_map(f"wresp_id_{i}_i", wresp_id[i].getNameRead()) + em.add_map(f"wreq_id_{i}_o", wreq_id[i].getNameWrite()) - # End module definition - self.lsq_wrapper_str += "end architecture;\n" + em.complete_instantiation() # Write to the file - with open(f"{self.output_folder}/{self.lsq_name}.vhd", "w") as file: - file.write(self.lsq_wrapper_str) + output_str = em.get_definition_str(self.lsq_name) + with open( + f"{self.output_folder}/{self.lsq_name}.{em.get_file_suffix()}", "w" + ) as file: + file.write(output_str) - return self.lsq_wrapper_str + return output_str # ===----------------------------------------------------------------------===# @@ -1111,20 +812,27 @@ def main(): if not os.path.exists(args.output_path): os.makedirs(args.output_path) + if args.hdl == "vhdl": + emitter = VHDLEmitter() + elif args.hdl == "verilog": + emitter = VerilogEmitter() + else: + raise ValueError("Unsupported language specified. Use 'vhdl' or 'verilog'.") + # Parse the config file lsqConfig = GetConfigs(args.config_files) # STEP 1: Generate the desired core lsq logic - codeGen(args.output_path, lsqConfig) + codeGen(emitter.new(), args.output_path, lsqConfig) # STEP 2: Generate the wrapper to be connected with circuits generated by Dynamatic lsq_wrapper_module = LSQWrapper(args.output_path, "_wrapper", lsqConfig) # Step 3: Generate the corresponding wrapper based on the config.master if lsqConfig.master: - lsq_wrapper_module.genWrapper() + lsq_wrapper_module.genWrapper(emitter) else: - lsq_wrapper_module.genWrapperSlave() + lsq_wrapper_module.genWrapperSlave(emitter) if __name__ == "__main__": diff --git a/tools/backend/lsq-generator-python/vhdl_gen/__init__.py b/tools/backend/lsq-generator-python/vhdl_gen/__init__.py deleted file mode 100644 index 6ba16ad936..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# vhdl_gen/__init__.py -from vhdl_gen.utils import ( - VHDLLogicType, VHDLLogicVecType, VHDLLogicTypeArray, VHDLLogicVecTypeArray, - OpTab, -) -from vhdl_gen.configs import GetConfigs, Configs -from vhdl_gen.codegen import codeGen - - -# from vhdlgen import * -__all__ = [ - # utils - "VHDLLogicType", "VHDLLogicVecType", "VHDLLogicTypeArray", "VHDLLogicVecTypeArray", - "OpTab", - # configs - "GetConfigs", "Configs", - # codegen - "codeGen", -] diff --git a/tools/backend/lsq-generator-python/vhdl_gen/cli.py b/tools/backend/lsq-generator-python/vhdl_gen/cli.py deleted file mode 100644 index 354f13ea29..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/cli.py +++ /dev/null @@ -1,23 +0,0 @@ -import argparse -import vhdl_gen.configs as configs -import vhdl_gen.codegen as code_gen - - -def parse_args(): - # Parse the arguments - parser = argparse.ArgumentParser() - parser.add_argument('--target-dir', '-t', dest='target_dir', default='./', type=str) - parser.add_argument('--spec-file', '-s', required=True, dest='spec_file', default='', type=str) - args = parser.parse_args() - - return args - - -def main(): - args = parse_args() - lsqConfig = configs.GetConfigs(args.spec_file) - code_gen.codeGen(args.target_dir, lsqConfig) - - -if __name__ == "__main__": - main() diff --git a/tools/backend/lsq-generator-python/vhdl_gen/codegen.py b/tools/backend/lsq-generator-python/vhdl_gen/codegen.py deleted file mode 100644 index 827ab5f907..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/codegen.py +++ /dev/null @@ -1,64 +0,0 @@ -from vhdl_gen.context import VHDLContext -import vhdl_gen.generators.group_allocator as group_allocator -import vhdl_gen.generators.dispatchers as dispatchers -import vhdl_gen.generators.lsq as lsq - -import vhdl_gen.generators.lsq_submodule_wrapper as lsq_submodule_wrapper - - -def codeGen(path_rtl, configs): - ctx = VHDLContext() - - # Initialize a wrapper object to hold all submodule generator instances. - lsq_submodules = lsq_submodule_wrapper.LSQ_Submodules() - - name = configs.name + '_core' - # empty the file - file = open(f'{path_rtl}/{name}.vhd', 'w').close() - - # Group Allocator - ga = group_allocator.GroupAllocator( - name=name, suffix='_ga', configs=configs) - ga.generate(path_rtl) - lsq_submodules.group_allocator = ga - - # When the condition "if configs.numLdPorts > 0:" is not true: - # Do not generating dispatching modules when there are zero load ports. - # - # - WARNING: This logic needs more testing - # - TODO: Also remove the load queue when there are zero load ports. - if configs.numLdPorts > 0: - # Load Address Port Dispatcher - ptq_dispatcher_lda = dispatchers.PortToQueueDispatcher( - name, '_lda', configs.numLdPorts, configs.numLdqEntries, configs.addrW, configs.ldpAddrW) - ptq_dispatcher_lda.generate(path_rtl) - lsq_submodules.ptq_dispatcher_lda = ptq_dispatcher_lda - - # Load Data Port Dispatcher - qtp_dispatcher_ldd = dispatchers.QueueToPortDispatcher( - name, '_ldd', configs.numLdPorts, configs.numLdqEntries, configs.dataW, configs.ldpAddrW) - qtp_dispatcher_ldd.generate(path_rtl) - lsq_submodules.qtp_dispatcher_ldd = qtp_dispatcher_ldd - - # Store Address Port Dispatcher - ptq_dispatcher_sta = dispatchers.PortToQueueDispatcher( - name, '_sta', configs.numStPorts, configs.numStqEntries, configs.addrW, configs.stpAddrW) - ptq_dispatcher_sta.generate(path_rtl) - lsq_submodules.ptq_dispatcher_sta = ptq_dispatcher_sta - - # Store Data Port Dispatcher - ptq_dispatcher_std = dispatchers.PortToQueueDispatcher( - name, '_std', configs.numStPorts, configs.numStqEntries, configs.dataW, configs.stpAddrW) - ptq_dispatcher_std.generate(path_rtl) - lsq_submodules.ptq_dispatcher_std = ptq_dispatcher_std - - # Store Backward Port Dispatcher - if configs.stResp: - qtp_dispatcher_stb = dispatchers.QueueToPortDispatcher( - name, '_stb', configs.numStPorts, configs.numStqEntries, 0, configs.stpAddrW) - qtp_dispatcher_stb.generate(path_rtl) - lsq_submodules.qtp_dispatcher_stb = qtp_dispatcher_stb - - # Change the name of the following module to lsq_core - lsq_core = lsq.LSQ(name, '', configs) - lsq_core.generate(lsq_submodules, path_rtl) diff --git a/tools/backend/lsq-generator-python/vhdl_gen/configs.py b/tools/backend/lsq-generator-python/vhdl_gen/configs.py deleted file mode 100644 index a83ae2de0d..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/configs.py +++ /dev/null @@ -1,122 +0,0 @@ -# -# Dynamatic is under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# - -import math -import json -import sys - -# read and parse the json file -# example json format in README - - -def GetConfigs(config_json_path: str): - with open(config_json_path, 'r') as file: - configString = file.read() - configs = json.loads(configString) - return Configs(configs) - - -class Configs: - """ - Configuration object for LSQ code generation. - - This class is instantiated using 'GetConfigs(path_to_json_file)', which loads a JSON file - and overwrites all the values below using user-defined parameters. - - The values shown below are NOT the actual values used during generation; - they are one of possible default configurations. - """ - - name: str = 'test' # Name prefix used for generated VHDL files - dataW: int = 16 # Data width (Number of bits for load/store data) - addrW: int = 13 # Address width (Number of bits for memory address) - idW: int = 2 # ID width (Number of bits for ID in the memory interface) - numLdqEntries: int = 3 # Load queue size (Number of entries in the load queue) - numStqEntries: int = 10 # Store queue size (Number of entries in the store queue) - numLdPorts: int = 3 # Number of load access ports - numStPorts: int = 3 # Number of store access ports - numGroups: int = 2 # Number of total Basic Blocks (BBs) - numLdMem: int = 1 # Number of load channels at memory interface (Fixed to 1) - numStMem: int = 1 # Number of store channels at memory interface (Fixed to 1) - - gaNumLoads: list = [2, 1] # Number of loads in each BB - gaNumStores: list = [2, 1] # Number of stores in each BB - gaLdOrder: list = [[2, 2], # The order matrix for each group - [0]] # Outer list (Row): Index for each BB - # Inner list (Column): List of store counts ahead of each load - # In this example -> BB0=[st0,st1,ld0,ld1], BB1=[ld2,st2] - gaLdPortIdx: list = [[0, 1], # The related access port index for each load in BB - [2]] - gaStPortIdx: list = [[0, 1], # The related access port index for each store in BB - [2]] - ldqAddrW: int = 2 # Load queue address width - stqAddrW: int = 4 # Store queue address width - ldpAddrW: int = 2 # Load port address width - stpAddrW: int = 2 # Store port address width - - pipe0: bool = False # Enable pipeline register 0 - pipe1: bool = False # Enable pipeline register 1 - pipeComp: bool = False # Enable pipeline register pipeComp - headLag: bool = False # Whether the head pointer of the load queue is updated - # one cycle later than the valid bits of entries - stResp: bool = False # Whether store response channel in store access port is enabled - gaMulti: bool = False # Whether multiple groups are allowed to request an allocation at the same cycle - bypass: bool = True # Whether bypassing (store-to-load forwarding) is enabled - - def __init__(self, config: dict) -> None: - self.name = config["name"] - self.dataW = config["dataWidth"] - self.addrW = config["addrWidth"] - self.idW = config["indexWidth"] - self.numLdqEntries = config["fifoDepth_L"] - self.numStqEntries = config["fifoDepth_S"] - self.numLdPorts = config["numLoadPorts"] - self.numStPorts = config["numStorePorts"] - self.numGroups = config["numBBs"] - self.numLdMem = config["numLdChannels"] - self.numStMem = config["numStChannels"] - self.master = bool(config["master"]) - - self.stResp = bool(config["stResp"]) - self.gaMulti = bool(config["groupMulti"]) - self.bypass = True - - self.gaNumLoads = config["numLoads"] - self.gaNumStores = config["numStores"] - self.gaLdOrder = config["ldOrder"] - self.gaLdPortIdx = config["ldPortIdx"] - self.gaStPortIdx = config["stPortIdx"] - - self.ldqAddrW = math.ceil(math.log2(self.numLdqEntries)) - self.stqAddrW = math.ceil(math.log2(self.numStqEntries)) - - # Original empty assignment assumes the size of load or store queue to be always a multiple of 2 - if (self.numLdqEntries & (self.numLdqEntries % 2 == 0)): - self.emptyLdAddrW = math.ceil(math.log2(self.numLdqEntries+1)) - else: - self.emptyLdAddrW = math.ceil(math.log2(self.numLdqEntries)) + 1 - - if (self.numStqEntries & (self.numStqEntries % 2 == 0)): - self.emptyStAddrW = math.ceil(math.log2(self.numStqEntries+1)) - else: - self.emptyStAddrW = math.ceil(math.log2(self.numStqEntries)) + 1 - # Check the number of ports, if num*Ports == 0, set it to 1 - self.ldpAddrW = math.ceil(math.log2(self.numLdPorts if self.numLdPorts > 0 else 1)) - self.stpAddrW = math.ceil(math.log2(self.numStPorts if self.numStPorts > 0 else 1)) - - self.pipe0 = bool(config["pipe0En"]) - self.pipe1 = bool(config["pipe1En"]) - self.pipeComp = bool(config["pipeCompEn"]) - self.headLag = bool(config["headLagEn"]) - - assert (self.idW >= self.ldqAddrW) - - # list size checking - assert (len(self.gaNumLoads) == self.numGroups) - assert (len(self.gaNumStores) == self.numGroups) - assert (len(self.gaLdOrder) == self.numGroups) - assert (len(self.gaLdPortIdx) == self.numGroups) - assert (len(self.gaStPortIdx) == self.numGroups) diff --git a/tools/backend/lsq-generator-python/vhdl_gen/context.py b/tools/backend/lsq-generator-python/vhdl_gen/context.py deleted file mode 100644 index b3bacd6fbe..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/context.py +++ /dev/null @@ -1,47 +0,0 @@ -# ===----------------------------------------------------------------------===# -# Global Parameter Initialization -# ===----------------------------------------------------------------------===# -class VHDLContext: - """ - A context object to replace global variables for VHDL code generation. - Holds indentation level, temporary name counter, and initialization strings. - """ - - def __init__(self): - # Indentation level for generated code - self.tabLevel = 0 - - # Counter for generating unique temporary names - self.tempCount = 0 - - # Accumulated initialization code sections - self.signalInitString = '' - self.portInitString = '' - self.regInitString = '' - - # Default library imports for VHDL - self.library = 'library IEEE;\nuse IEEE.std_logic_1164.all;\nuse IEEE.numeric_std.all;\n\n' - - def get_current_indent(self) -> str: - return '\t' * self.tabLevel - - def increase_indent(self): - self.tabLevel += 1 - - def decrease_indent(self): - self.tabLevel = max(0, self.tabLevel - 1) - - def get_temp(self, name: str) -> str: - return f'TEMP_{self.tempCount}_{name}' - - def use_temp(self): - self.tempCount += 1 - - def add_signal_str(self, code: str): - self.signalInitString += code - - def add_port_str(self, code: str): - self.portInitString += code - - def add_reg_str(self, code: str): - self.regInitString += code diff --git a/tools/backend/lsq-generator-python/vhdl_gen/generators/__init__.py b/tools/backend/lsq-generator-python/vhdl_gen/generators/__init__.py deleted file mode 100644 index 8c9152bd1c..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/generators/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# vhdl_gen/generators/__init__.py -from vhdl_gen.generators.dispatchers import PortToQueueDispatcher, QueueToPortDispatcher -from vhdl_gen.generators.group_allocator import GroupAllocator -from vhdl_gen.generators.lsq import LSQ - -__all__ = [ - "PortToQueueDispatcher", "QueueToPortDispatcher", - "GroupAllocator", - "LSQ", -] diff --git a/tools/backend/lsq-generator-python/vhdl_gen/generators/group_allocator.py b/tools/backend/lsq-generator-python/vhdl_gen/generators/group_allocator.py deleted file mode 100644 index d87dbdbe3d..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/generators/group_allocator.py +++ /dev/null @@ -1,416 +0,0 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.signals import Logic, LogicArray, LogicVec, LogicVecArray -from vhdl_gen.operators import Op, WrapSub, Mux1HROM, CyclicLeftShift, CyclicPriorityMasking -from vhdl_gen.utils import MaskLess -from vhdl_gen.configs import Configs - - -class GroupAllocator: - def __init__( - self, - name: str, - suffix: str, - configs: Configs - ): - """ - Group Allocator - - Models a group allocator for a Load-Store Queue (LSQ) system. - - This class encapsulates the logic for generating a VHDL module that allocates - space for groups of memory operations (loads and stores) in the load queue and - the store queue. - - Parameters: - name : Base name of the group allocator. - suffix : Suffix appended to the entity name. - configs : configuration generated from JSON - - Instance Variable: - self.module_name = name + suffix : Entity and architecture identifier - - Example: - ga = GroupAllocator( - name="config_0_core", - suffix="_ga", - configs=configs - ) - - # You can later generate VHDL entity and architecture by - # ga.generate(...) - # You can later instantiate VHDL entity by - # ga.instantiate(...) - """ - - self.name = name - self.configs = configs - self.module_name = name + suffix - - def generate(self, path_rtl) -> None: - """ - Generates the VHDL 'entity' and 'architecture' sections for a group allocator. - - Parameters: - path_rtl : Output directory for VHDL files. - - Output: - Appends the 'entity' and 'architecture' definitions - to the .vhd file at /.vhd. - Entity and architecture use the identifier: - - Example (Group Allocator): - ga.generate(path_rtl) - - produces in rtl/config_0_core.vhd: - - entity config_0_core_ga is - port( - rst : in std_logic; - clk : in std_logic; - ... - ); - end entity; - - architecture arch of config_0_core_ga is - -- signals generated here - begin - -- group allocator logic here - end architecture; - - """ - - # ctx: VHDLContext for code generation state. - # When we generate VHDL entity and architecture, we can use this context as a local variable. - # We only need to get the context as a parameter when we instantiate the module. - # It saves all information we need when we generate VHDL entity and architecture code. - ctx = VHDLContext() - - ctx.tabLevel = 1 - ctx.tempCount = 0 - ctx.signalInitString = '' - ctx.portInitString = '\tport(\n\t\trst : in std_logic;\n\t\tclk : in std_logic' - ctx.regInitString = '\tprocess (clk, rst) is\n' + '\tbegin\n' - arch = '' - - # IOs - group_init_valid_i = LogicArray( - ctx, 'group_init_valid', 'i', self.configs.numGroups) - group_init_ready_o = LogicArray( - ctx, 'group_init_ready', 'o', self.configs.numGroups) - - ldq_tail_i = LogicVec(ctx, 'ldq_tail', 'i', self.configs.ldqAddrW) - ldq_head_i = LogicVec(ctx, 'ldq_head', 'i', self.configs.ldqAddrW) - ldq_empty_i = Logic(ctx, 'ldq_empty', 'i') - - stq_tail_i = LogicVec(ctx, 'stq_tail', 'i', self.configs.stqAddrW) - stq_head_i = LogicVec(ctx, 'stq_head', 'i', self.configs.stqAddrW) - stq_empty_i = Logic(ctx, 'stq_empty', 'i') - - ldq_wen_o = LogicArray(ctx, 'ldq_wen', 'o', self.configs.numLdqEntries) - num_loads_o = LogicVec(ctx, 'num_loads', 'o', self.configs.ldqAddrW) - num_loads = LogicVec(ctx, 'num_loads', 'w', self.configs.ldqAddrW) - if (self.configs.ldpAddrW > 0): - ldq_port_idx_o = LogicVecArray( - ctx, 'ldq_port_idx', 'o', self.configs.numLdqEntries, self.configs.ldpAddrW) - - stq_wen_o = LogicArray(ctx, 'stq_wen', 'o', self.configs.numStqEntries) - num_stores_o = LogicVec(ctx, 'num_stores', 'o', self.configs.stqAddrW) - num_stores = LogicVec(ctx, 'num_stores', 'w', self.configs.stqAddrW) - if (self.configs.stpAddrW > 0): - stq_port_idx_o = LogicVecArray( - ctx, 'stq_port_idx', 'o', self.configs.numStqEntries, self.configs.stpAddrW) - - ga_ls_order_o = LogicVecArray( - ctx, 'ga_ls_order', 'o', self.configs.numLdqEntries, self.configs.numStqEntries) - - # The number of empty load and store is calculated with cyclic subtraction. - # If the empty signal is high, then set the number to max value. - loads_sub = LogicVec(ctx, 'loads_sub', 'w', self.configs.ldqAddrW) - stores_sub = LogicVec(ctx, 'stores_sub', 'w', self.configs.stqAddrW) - empty_loads = LogicVec(ctx, 'empty_loads', 'w', - self.configs.emptyLdAddrW) - empty_stores = LogicVec(ctx, 'empty_stores', 'w', - self.configs.emptyStAddrW) - - arch += WrapSub(ctx, loads_sub, ldq_head_i, - ldq_tail_i, self.configs.numLdqEntries) - arch += WrapSub(ctx, stores_sub, stq_head_i, - stq_tail_i, self.configs.numStqEntries) - - arch += Op(ctx, empty_loads, self.configs.numLdqEntries, 'when', ldq_empty_i, 'else', - '(', '\'0\'', '&', loads_sub, ')') - arch += Op(ctx, empty_stores, self.configs.numStqEntries, 'when', stq_empty_i, 'else', - '(', '\'0\'', '&', stores_sub, ')') - - # Generate handshake signals - group_init_ready = LogicArray( - ctx, 'group_init_ready', 'w', self.configs.numGroups) - group_init_hs = LogicArray( - ctx, 'group_init_hs', 'w', self.configs.numGroups) - - for i in range(0, self.configs.numGroups): - arch += Op(ctx, group_init_ready[i], - '\'1\'', 'when', - '(', empty_loads, '>=', ( - self.configs.gaNumLoads[i], self.configs.emptyLdAddrW), ')', 'and', - '(', empty_stores, '>=', ( - self.configs.gaNumStores[i], self.configs.emptyStAddrW), ')', - 'else', '\'0\'') - - if (self.configs.gaMulti): - group_init_and = LogicArray( - ctx, 'group_init_and', 'w', self.configs.numGroups) - ga_rr_mask = LogicVec(ctx, 'ga_rr_mask', 'r', - self.configs.numGroups) - ga_rr_mask.regInit() - for i in range(0, self.configs.numGroups): - arch += Op(ctx, group_init_and[i], - group_init_ready[i], 'and', group_init_valid_i[i]) - arch += Op(ctx, group_init_ready_o[i], group_init_hs[i]) - arch += CyclicPriorityMasking(ctx, group_init_hs, - group_init_and, ga_rr_mask) - for i in range(0, self.configs.numGroups): - arch += Op(ctx, (ga_rr_mask, (i+1) % - self.configs.numGroups), (group_init_hs, i)) - else: - for i in range(0, self.configs.numGroups): - arch += Op(ctx, group_init_ready_o[i], group_init_ready[i]) - arch += Op(ctx, group_init_hs[i], - group_init_ready[i], 'and', group_init_valid_i[i]) - - # ROM value - if (self.configs.ldpAddrW > 0): - ldq_port_idx_rom = LogicVecArray( - ctx, 'ldq_port_idx_rom', 'w', self.configs.numLdqEntries, self.configs.ldpAddrW) - if (self.configs.stpAddrW > 0): - stq_port_idx_rom = LogicVecArray( - ctx, 'stq_port_idx_rom', 'w', self.configs.numStqEntries, self.configs.stpAddrW) - ga_ls_order_rom = LogicVecArray( - ctx, 'ga_ls_order_rom', 'w', self.configs.numLdqEntries, self.configs.numStqEntries) - ga_ls_order_temp = LogicVecArray( - ctx, 'ga_ls_order_temp', 'w', self.configs.numLdqEntries, self.configs.numStqEntries) - if (self.configs.ldpAddrW > 0): - arch += Mux1HROM(ctx, ldq_port_idx_rom, - self.configs.gaLdPortIdx, group_init_hs) - if (self.configs.stpAddrW > 0): - arch += Mux1HROM(ctx, stq_port_idx_rom, - self.configs.gaStPortIdx, group_init_hs) - arch += Mux1HROM(ctx, ga_ls_order_rom, self.configs.gaLdOrder, - group_init_hs, MaskLess) - arch += Mux1HROM(ctx, num_loads, - self.configs.gaNumLoads, group_init_hs) - arch += Mux1HROM(ctx, num_stores, - self.configs.gaNumStores, group_init_hs) - arch += Op(ctx, num_loads_o, num_loads) - arch += Op(ctx, num_stores_o, num_stores) - - ldq_wen_unshifted = LogicArray( - ctx, 'ldq_wen_unshifted', 'w', self.configs.numLdqEntries) - stq_wen_unshifted = LogicArray( - ctx, 'stq_wen_unshifted', 'w', self.configs.numStqEntries) - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, ldq_wen_unshifted[i], - '\'1\'', 'when', - num_loads, '>', (i, self.configs.ldqAddrW), - 'else', '\'0\'' - ) - for i in range(0, self.configs.numStqEntries): - arch += Op(ctx, stq_wen_unshifted[i], - '\'1\'', 'when', - num_stores, '>', (i, self.configs.stqAddrW), - 'else', '\'0\'' - ) - - # Shift the arrays - if (self.configs.ldpAddrW > 0): - arch += CyclicLeftShift(ctx, ldq_port_idx_o, - ldq_port_idx_rom, ldq_tail_i) - if (self.configs.stpAddrW > 0): - arch += CyclicLeftShift(ctx, stq_port_idx_o, - stq_port_idx_rom, stq_tail_i) - arch += CyclicLeftShift(ctx, ldq_wen_o, ldq_wen_unshifted, ldq_tail_i) - arch += CyclicLeftShift(ctx, stq_wen_o, stq_wen_unshifted, stq_tail_i) - for i in range(0, self.configs.numLdqEntries): - arch += CyclicLeftShift(ctx, - ga_ls_order_temp[i], ga_ls_order_rom[i], stq_tail_i) - arch += CyclicLeftShift(ctx, ga_ls_order_o, - ga_ls_order_temp, ldq_tail_i) - - ###### Write To File ###### - ctx.portInitString += '\n\t);' - if (self.configs.gaMulti): - ctx.regInitString += '\tend process;\n' - else: - ctx.regInitString = '' - - # Write to the file - with open(f'{path_rtl}/{self.name}.vhd', 'a') as file: - file.write('\n\n') - file.write(ctx.library) - file.write(f'entity {self.module_name} is\n') - file.write(ctx.portInitString) - file.write('\nend entity;\n\n') - file.write(f'architecture arch of {self.module_name} is\n') - file.write(ctx.signalInitString) - file.write('begin\n' + arch + '\n') - file.write(ctx.regInitString + 'end architecture;\n') - - def instantiate( - self, - ctx: VHDLContext, - group_init_valid_i: LogicArray, - group_init_ready_o: LogicArray, - ldq_tail_i: LogicVec, - ldq_head_i: LogicVec, - ldq_empty_i: Logic, - stq_tail_i: LogicVec, - stq_head_i: LogicVec, - stq_empty_i: Logic, - ldq_wen_o: LogicArray, - num_loads_o: LogicVec, - ldq_port_idx_o: LogicVecArray, - stq_wen_o: LogicArray, - num_stores_o: LogicVec, - stq_port_idx_o: LogicVecArray, - ga_ls_order_o: LogicVecArray - ) -> str: - """ - Group Allocator Instantiation - - Creates the VHDL port mapping for the group allocator entity. - - Parameters: - ctx : VHDLContext for code generation state. - group_init_valid_i : Group Allocator handshake valid signal - group_init_ready_o : Group Allocator handshake ready signal - ldq_tail_i : Load queue tail - ldq_head_i : Load queue head - ldq_empty_i : (boolean) load queue empty - stq_tail_i : Store queue tail - stq_head_i : Store queue head - stq_empty_i : (boolean) store queue empty - ldq_wen_o : Load queue write enable - num_loads_o : The number of loads - ldq_port_idx_o : Load queue port index - stq_wen_o : Store queue write enable - num_stores_o : The number of stores - stq_port_idx_o : Store queue port index - ga_ls_order_o : Group Allocator load-store order matrix - - Returns: - VHDL instantiation string for inclusion in the architecture body. - - Example: - arch += ga.instantiate( - ctx, - group_init_valid_i = group_init_valid_i, - group_init_ready_o = group_init_ready_o, - ldq_tail_i = ldq_tail, - ldq_head_i = ldq_head, - ldq_empty_i = ldq_empty, - stq_tail_i = stq_tail, - stq_head_i = stq_head, - stq_empty_i = stq_empty, - ldq_wen_o = ldq_wen, - num_loads_o = num_loads, - ldq_port_idx_o = ldq_port_idx, - stq_wen_o = stq_wen, - num_stores_o = num_stores, - stq_port_idx_o = stq_port_idx, - ga_ls_order_o = ga_ls_order - ) - - This generates, inside 'config_0_core.vhd' and under the 'architecture config_0_core', the following instantiation - - architecture arch of config_0_core is - signal ... - begin - ... - config_0_core_ga : entity work.config_0_core_ga - port map( - rst => rst, - clk => clk, - group_init_valid_0_i => group_init_valid_0_i, - group_init_ready_0_o => group_init_ready_0_o, - ldq_tail_i => ldq_tail_q, - ldq_head_i => ldq_head_q, - ldq_empty_i => ldq_empty, - stq_tail_i => stq_tail_q, - stq_head_i => stq_head_q, - stq_empty_i => stq_empty, - ldq_wen_0_o => ldq_wen_0, - ldq_wen_1_o => ldq_wen_1, - num_loads_o => num_loads, - ldq_port_idx_0_o => ldq_port_idx_0_d, - ldq_port_idx_1_o => ldq_port_idx_1_d, - stq_wen_0_o => stq_wen_0, - stq_wen_1_o => stq_wen_1, - stq_port_idx_0_o => stq_port_idx_0_d, - stq_port_idx_1_o => stq_port_idx_1_d, - ga_ls_order_0_o => ga_ls_order_0, - ga_ls_order_1_o => ga_ls_order_1, - num_stores_o => num_stores - ); - ... - end architecture; - """ - - arch = ctx.get_current_indent( - ) + f'{self.module_name} : entity work.{self.module_name}\n' - ctx.tabLevel += 1 - arch += ctx.get_current_indent() + f'port map(\n' - ctx.tabLevel += 1 - - arch += ctx.get_current_indent() + f'rst => rst,\n' - arch += ctx.get_current_indent() + f'clk => clk,\n' - - for i in range(0, self.configs.numGroups): - arch += ctx.get_current_indent() + \ - f'group_init_valid_{i}_i => {group_init_valid_i.getNameRead(i)},\n' - for i in range(0, self.configs.numGroups): - arch += ctx.get_current_indent() + \ - f'group_init_ready_{i}_o => {group_init_ready_o.getNameWrite(i)},\n' - - arch += ctx.get_current_indent() + \ - f'ldq_tail_i => {ldq_tail_i.getNameRead()},\n' - arch += ctx.get_current_indent() + \ - f'ldq_head_i => {ldq_head_i.getNameRead()},\n' - arch += ctx.get_current_indent() + \ - f'ldq_empty_i => {ldq_empty_i.getNameRead()},\n' - - arch += ctx.get_current_indent() + \ - f'stq_tail_i => {stq_tail_i.getNameRead()},\n' - arch += ctx.get_current_indent() + \ - f'stq_head_i => {stq_head_i.getNameRead()},\n' - arch += ctx.get_current_indent() + \ - f'stq_empty_i => {stq_empty_i.getNameRead()},\n' - - for i in range(0, self.configs.numLdqEntries): - arch += ctx.get_current_indent() + \ - f'ldq_wen_{i}_o => {ldq_wen_o.getNameWrite(i)},\n' - arch += ctx.get_current_indent() + \ - f'num_loads_o => {num_loads_o.getNameWrite()},\n' - if (self.configs.ldpAddrW > 0): - for i in range(0, self.configs.numLdqEntries): - arch += ctx.get_current_indent() + \ - f'ldq_port_idx_{i}_o => {ldq_port_idx_o.getNameWrite(i)},\n' - - for i in range(0, self.configs.numStqEntries): - arch += ctx.get_current_indent() + \ - f'stq_wen_{i}_o => {stq_wen_o.getNameWrite(i)},\n' - if (self.configs.stpAddrW > 0): - for i in range(0, self.configs.numStqEntries): - arch += ctx.get_current_indent() + \ - f'stq_port_idx_{i}_o => {stq_port_idx_o.getNameWrite(i)},\n' - - for i in range(0, self.configs.numLdqEntries): - arch += ctx.get_current_indent() + \ - f'ga_ls_order_{i}_o => {ga_ls_order_o.getNameWrite(i)},\n' - - arch += ctx.get_current_indent() + \ - f'num_stores_o => {num_stores_o.getNameWrite()}\n' - - ctx.tabLevel -= 1 - arch += ctx.get_current_indent() + f');\n' - ctx.tabLevel -= 1 - return arch diff --git a/tools/backend/lsq-generator-python/vhdl_gen/generators/lsq.py b/tools/backend/lsq-generator-python/vhdl_gen/generators/lsq.py deleted file mode 100644 index 598cbf86f0..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/generators/lsq.py +++ /dev/null @@ -1,1038 +0,0 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.signals import * -from vhdl_gen.operators import * -from vhdl_gen.configs import Configs - -import vhdl_gen.generators.lsq_submodule_wrapper as lsq_submodule_wrapper - - -class LSQ: - def __init__( - self, - name: str, - suffix: str, - configs: Configs - ): - """ - LSQ - - Models the top-level Load-Store Queue (LSQ) module. - - This class integrates all necessary sub-components to form a complete LSQ. - It is responsible for generating the top-level VHDL entity that wires - together the Group Allocator, various Port/Queue Dispatchers, and the core - queue logic with dependency checking. - - Parameters: - name : Base name of the LSQ. "_core" - suffix : Suffix appended to the name to form the VHDL entity name. - Since LSQ is the top module, you do not need to add any suffix. - configs : configuration generated from JSON - - - Instance Variable: - self.module_name = name + suffix : Entity and architecture identifier - - - Example: - lsq_core = LSQ("config_0_core", '', configs) - - # You can later generate VHDL entity and architecture by - # lsq_core.generate(...) - - # Instantiation of the LSQ module does not use this class. - # It considers more conditions, and it is done in lsq-generator.py. - - """ - - self.name = name - self.module_name = name + suffix - self.configs = configs - - def generate(self, lsq_submodules, path_rtl) -> None: - """ - Generates the VHDL 'entity' and 'architecture' sections for an LSQ. - - This function appends the following to the file '/.vhd: - 1. 'entity ' declaration - 2. 'architecture arch of ' implementation - - The generated code also instantitates: - - Group Allocator - - Port-to-Queue Dispatcher - - Load Address Port Dispatcher - - Store Address Port Dispatcher - - Store Data Port Dispatcher - - Queue-to-Port Dispatcher - - Load Data Port Dispatcher - - (Optionally) Store Backward Port Dispatcher - - Parameters: - lsq_submodules : A collection of objects representing submodules whose VHDL entity - definitions are already generated. This parameter is used to - generate their port map instantiations. - path_rtl : Output directory for VHDL files. - - Output: - Appends the 'entity' and 'architecture' definitions - to the .vhd file at /.vhd. - Entity and architecture use the identifier: - - Example: - lsq_core.generate(lsq_submodules, path_rtl) - - """ - - ctx = VHDLContext() - - # Initialize the global parameters - ctx.tabLevel = 1 - ctx.tempCount = 0 - ctx.signalInitString = '' - ctx.portInitString = '\tport(\n\t\trst : in std_logic;\n\t\tclk : in std_logic' - ctx.regInitString = '\tprocess (clk, rst) is\n' + '\tbegin\n' - arch = '' - - ###### LSQ Architecture ###### - ###### IOs ###### - - # group initialzation signals - group_init_valid_i = LogicArray( - ctx, 'group_init_valid', 'i', self.configs.numGroups) - group_init_ready_o = LogicArray( - ctx, 'group_init_ready', 'o', self.configs.numGroups) - - # Memory access ports, i.e., the connection "kernel -> LSQ" - # Load address channel (addr, valid, ready) from kernel, contains signals: - ldp_addr_i = LogicVecArray( - ctx, 'ldp_addr', 'i', self.configs.numLdPorts, self.configs.addrW) - ldp_addr_valid_i = LogicArray( - ctx, 'ldp_addr_valid', 'i', self.configs.numLdPorts) - ldp_addr_ready_o = LogicArray( - ctx, 'ldp_addr_ready', 'o', self.configs.numLdPorts) - - # Load data channel (data, valid, ready) to kernel - ldp_data_o = LogicVecArray( - ctx, 'ldp_data', 'o', self.configs.numLdPorts, self.configs.dataW) - ldp_data_valid_o = LogicArray( - ctx, 'ldp_data_valid', 'o', self.configs.numLdPorts) - ldp_data_ready_i = LogicArray( - ctx, 'ldp_data_ready', 'i', self.configs.numLdPorts) - - # Store address channel (addr, valid, ready) from kernel - stp_addr_i = LogicVecArray( - ctx, 'stp_addr', 'i', self.configs.numStPorts, self.configs.addrW) - stp_addr_valid_i = LogicArray( - ctx, 'stp_addr_valid', 'i', self.configs.numStPorts) - stp_addr_ready_o = LogicArray( - ctx, 'stp_addr_ready', 'o', self.configs.numStPorts) - - # Store data channel (data, valid, ready) from kernel - stp_data_i = LogicVecArray( - ctx, 'stp_data', 'i', self.configs.numStPorts, self.configs.dataW) - stp_data_valid_i = LogicArray( - ctx, 'stp_data_valid', 'i', self.configs.numStPorts) - stp_data_ready_o = LogicArray( - ctx, 'stp_data_ready', 'o', self.configs.numStPorts) - - if self.configs.stResp: - stp_exec_valid_o = LogicArray( - ctx, 'stp_exec_valid', 'o', self.configs.numStPorts) - stp_exec_ready_i = LogicArray( - ctx, 'stp_exec_ready', 'i', self.configs.numStPorts) - - # queue empty signal - empty_o = Logic(ctx, 'empty', 'o') - - # Memory interface: i.e., the connection LSQ -> AXI - # We assume that the memory interface has - # 1. A read request channel (rreq) and a read response channel (rresp). - # 2. A write request channel (wreq) and a write response channel (wresp). - rreq_valid_o = LogicArray( - ctx, 'rreq_valid', 'o', self.configs.numLdMem) - rreq_ready_i = LogicArray( - ctx, 'rreq_ready', 'i', self.configs.numLdMem) - rreq_id_o = LogicVecArray( - ctx, 'rreq_id', 'o', self.configs.numLdMem, self.configs.idW) - rreq_addr_o = LogicVecArray( - ctx, 'rreq_addr', 'o', self.configs.numLdMem, self.configs.addrW) - - rresp_valid_i = LogicArray( - ctx, 'rresp_valid', 'i', self.configs.numLdMem) - rresp_ready_o = LogicArray( - ctx, 'rresp_ready', 'o', self.configs.numLdMem) - rresp_id_i = LogicVecArray( - ctx, 'rresp_id', 'i', self.configs.numLdMem, self.configs.idW) - rresp_data_i = LogicVecArray( - ctx, 'rresp_data', 'i', self.configs.numLdMem, self.configs.dataW) - - wreq_valid_o = LogicArray( - ctx, 'wreq_valid', 'o', self.configs.numStMem) - wreq_ready_i = LogicArray( - ctx, 'wreq_ready', 'i', self.configs.numStMem) - wreq_id_o = LogicVecArray( - ctx, 'wreq_id', 'o', self.configs.numStMem, self.configs.idW) - wreq_addr_o = LogicVecArray( - ctx, 'wreq_addr', 'o', self.configs.numStMem, self.configs.addrW) - wreq_data_o = LogicVecArray( - ctx, 'wreq_data', 'o', self.configs.numStMem, self.configs.dataW) - - wresp_valid_i = LogicArray( - ctx, 'wresp_valid', 'i', self.configs.numStMem) - wresp_ready_o = LogicArray( - ctx, 'wresp_ready', 'o', self.configs.numStMem) - wresp_id_i = LogicVecArray( - ctx, 'wresp_id', 'i', self.configs.numStMem, self.configs.idW) - - #! If this is the lsq master, then we need the following logic - #! Define new interfaces needed by dynamatic - if (self.configs.master): - memStart_ready = Logic(ctx, 'memStart_ready', 'o') - memStart_valid = Logic(ctx, 'memStart_valid', 'i') - ctrlEnd_ready = Logic(ctx, 'ctrlEnd_ready', 'o') - ctrlEnd_valid = Logic(ctx, 'ctrlEnd_valid', 'i') - memEnd_ready = Logic(ctx, 'memEnd_ready', 'i') - memEnd_valid = Logic(ctx, 'memEnd_valid', 'o') - - #! Add extra signals required - memStartReady = Logic(ctx, 'memStartReady', 'w') - memEndValid = Logic(ctx, 'memEndValid', 'w') - ctrlEndReady = Logic(ctx, 'ctrlEndReady', 'w') - temp_gen_mem = Logic(ctx, 'TEMP_GEN_MEM', 'w') - - #! The memory completion signal cannot be set to 1 when any group is allocating: - no_curr_ga = "(not (" + " or ".join(group_init_valid_i.getNameRead(i) - for i in range(group_init_valid_i.length)) + "))" - - #! Define the needed logic - arch += "\t-- This signal indicates that all mem. ops are completed and func. can return.\n" - arch += "\t-- LSQ can return iff all the following conditions are true:\n" - arch += "\t-- 1. No more upcoming BBs containing memory accesses.\n" - arch += "\t-- 2. Both store and load queues are empty.\n" - arch += "\t-- 3. No GA in the same cycle.\n" - arch += f"\tTEMP_GEN_MEM <= {ctrlEnd_valid.getNameRead()} and stq_empty and ldq_empty and {no_curr_ga};\n" - - arch += "\t-- Define logic for the new interfaces needed by dynamatic\n" - arch += "\tprocess (clk) is\n\tbegin\n" - arch += '\t' * 2 + "if rising_edge(clk) then\n" - arch += '\t' * 3 + "if rst = '1' then\n" - arch += '\t' * 4 + "memStartReady <= '1';\n" - arch += '\t' * 4 + "memEndValid <= '0';\n" - arch += '\t' * 4 + "ctrlEndReady <= '0';\n" - arch += '\t' * 3 + "else\n" - arch += '\t' * 4 + \ - "memStartReady <= (memEndValid and memEnd_ready_i) or ((not (memStart_valid_i and memStartReady)) and memStartReady);\n" - arch += '\t' * 4 + "memEndValid <= TEMP_GEN_MEM or memEndValid;\n" - arch += '\t' * 4 + \ - "ctrlEndReady <= (not (ctrlEnd_valid_i and ctrlEndReady)) and (TEMP_GEN_MEM or ctrlEndReady);\n" - arch += '\t' * 3 + "end if;\n" - arch += '\t' * 2 + "end if;\n" - arch += "\tend process;\n\n" - - #! Assign signals for the newly added ports - arch += "\t-- Update new memory interfaces\n" - arch += Op(ctx, memStart_ready, memStartReady) - arch += Op(ctx, ctrlEnd_ready, ctrlEndReady) - arch += Op(ctx, memEnd_valid, memEndValid) - - ###### Queue Registers ###### - # Load Queue Entries - ldq_alloc = LogicArray(ctx, 'ldq_alloc', 'r', - self.configs.numLdqEntries) - ldq_issue = LogicArray(ctx, 'ldq_issue', 'r', - self.configs.numLdqEntries) - if (self.configs.ldpAddrW > 0): - ldq_port_idx = LogicVecArray( - ctx, 'ldq_port_idx', 'r', self.configs.numLdqEntries, self.configs.ldpAddrW) - else: - ldq_port_idx = None - ldq_addr_valid = LogicArray( - ctx, 'ldq_addr_valid', 'r', self.configs.numLdqEntries) - ldq_addr = LogicVecArray(ctx, 'ldq_addr', 'r', - self.configs.numLdqEntries, self.configs.addrW) - ldq_data_valid = LogicArray( - ctx, 'ldq_data_valid', 'r', self.configs.numLdqEntries) - ldq_data = LogicVecArray(ctx, 'ldq_data', 'r', - self.configs.numLdqEntries, self.configs.dataW) - - # Store Queue Entries - stq_alloc = LogicArray(ctx, 'stq_alloc', 'r', - self.configs.numStqEntries) - if self.configs.stResp: - stq_exec = LogicArray(ctx, 'stq_exec', 'r', - self.configs.numStqEntries) - if (self.configs.stpAddrW > 0): - stq_port_idx = LogicVecArray( - ctx, 'stq_port_idx', 'r', self.configs.numStqEntries, self.configs.stpAddrW) - else: - stq_port_idx = None - stq_addr_valid = LogicArray( - ctx, 'stq_addr_valid', 'r', self.configs.numStqEntries) - stq_addr = LogicVecArray(ctx, 'stq_addr', 'r', - self.configs.numStqEntries, self.configs.addrW) - stq_data_valid = LogicArray( - ctx, 'stq_data_valid', 'r', self.configs.numStqEntries) - stq_data = LogicVecArray(ctx, 'stq_data', 'r', - self.configs.numStqEntries, self.configs.dataW) - - # Order for load-store - store_is_older = LogicVecArray( - ctx, 'store_is_older', 'r', self.configs.numLdqEntries, self.configs.numStqEntries) - - # Pointers - ldq_tail = LogicVec(ctx, 'ldq_tail', 'r', self.configs.ldqAddrW) - ldq_head = LogicVec(ctx, 'ldq_head', 'r', self.configs.ldqAddrW) - - stq_tail = LogicVec(ctx, 'stq_tail', 'r', self.configs.stqAddrW) - stq_head = LogicVec(ctx, 'stq_head', 'r', self.configs.stqAddrW) - stq_issue = LogicVec(ctx, 'stq_issue', 'r', self.configs.stqAddrW) - stq_resp = LogicVec(ctx, 'stq_resp', 'r', self.configs.stqAddrW) - - # Entry related signals - # From port dispatchers - ldq_wen = LogicArray(ctx, 'ldq_wen', 'w', self.configs.numLdqEntries) - ldq_addr_wen = LogicArray( - ctx, 'ldq_addr_wen', 'w', self.configs.numLdqEntries) - ldq_reset = LogicArray(ctx, 'ldq_reset', 'w', - self.configs.numLdqEntries) - stq_wen = LogicArray(ctx, 'stq_wen', 'w', self.configs.numStqEntries) - stq_addr_wen = LogicArray( - ctx, 'stq_addr_wen', 'w', self.configs.numStqEntries) - stq_data_wen = LogicArray( - ctx, 'stq_data_wen', 'w', self.configs.numStqEntries) - stq_reset = LogicArray(ctx, 'stq_reset', 'w', - self.configs.numStqEntries) - # From Read/Write Block - ldq_data_wen = LogicArray( - ctx, 'ldq_data_wen', 'w', self.configs.numLdqEntries) - ldq_issue_set = LogicArray( - ctx, 'ldq_issue_set', 'w', self.configs.numLdqEntries) - if self.configs.stResp: - stq_exec_set = LogicArray( - ctx, 'stq_exec_set', 'w', self.configs.numStqEntries) - # Form Group Allocator - ga_ls_order = LogicVecArray( - ctx, 'ga_ls_order', 'w', self.configs.numLdqEntries, self.configs.numStqEntries) - - # Pointer related signals - # For updating pointers - num_loads = LogicVec(ctx, 'num_loads', 'w', self.configs.ldqAddrW) - num_stores = LogicVec(ctx, 'num_stores', 'w', self.configs.stqAddrW) - stq_issue_en = Logic(ctx, 'stq_issue_en', 'w') - stq_resp_en = Logic(ctx, 'stq_resp_en', 'w') - # Generated by pointers - ldq_empty = Logic(ctx, 'ldq_empty', 'w') - stq_empty = Logic(ctx, 'stq_empty', 'w') - ldq_head_oh = LogicVec(ctx, 'ldq_head_oh', 'w', - self.configs.numLdqEntries) - stq_head_oh = LogicVec(ctx, 'stq_head_oh', 'w', - self.configs.numStqEntries) - - arch += BitsToOH(ctx, ldq_head_oh, ldq_head) - arch += BitsToOH(ctx, stq_head_oh, stq_head) - - # indicates tail pointer was just updated (i.e., new stores were allocated) - stq_tail_update = Logic(ctx, 'stq_tail_update', 'r') - stq_tail_update.regInit() - arch += Reduce(ctx, stq_tail_update, num_stores, 'or') - - # Pipelining Strategy: - # The signals are always passed through the pipeline stages (*_pcomp, - # *_p0, *_p1). If the pipeline stage is enabled, the signal will be - # registered (the signal type is 'r' for register). Otherwise, the - # signal type is 'w' for wire, and the pipeline stage is effectively - # bypassed. If the signals are registers, we need to conditionally call - # regInit(). - pipe_comp_type = 'r' if self.configs.pipeComp else 'w' - pipe0_type = 'r' if self.configs.pipe0 else 'w' - pipe1_type = 'r' if self.configs.pipe1 else 'w' - - # update queue entries - # load queue - ldq_wen_pcomp = LogicArray( - ctx, 'ldq_wen_pcomp', pipe_comp_type, self.configs.numLdqEntries) - ldq_wen_p0 = LogicArray( - ctx, 'ldq_wen_p0', pipe0_type, self.configs.numLdqEntries) - ldq_alloc_next = LogicArray( - ctx, 'ldq_alloc_next', 'w', self.configs.numLdqEntries) - if self.configs.pipeComp: - ldq_wen_pcomp.regInit() - if self.configs.pipe0: - ldq_wen_p0.regInit() - - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, ldq_alloc_next[i], - 'not', ldq_reset[i], 'and', ldq_alloc[i] - ) - arch += Op(ctx, ldq_alloc[i], - ldq_wen[i], 'or', ldq_alloc_next[i] - ) - arch += Op(ctx, ldq_wen_pcomp[i], ldq_wen[i]) - arch += Op(ctx, ldq_wen_p0[i], ldq_wen_pcomp[i]) - arch += Op(ctx, ldq_issue[i], - 'not', ldq_wen_p0[i], 'and', - '(', ldq_issue_set[i], 'or', ldq_issue[i], ')' - ) - arch += Op(ctx, ldq_addr_valid[i], - 'not', ldq_wen[i], 'and', - '(', ldq_addr_wen[i], 'or', ldq_addr_valid[i], ')' - ) - arch += Op(ctx, ldq_data_valid[i], - 'not', ldq_wen[i], 'and', - '(', ldq_data_wen[i], 'or', ldq_data_valid[i], ')' - ) - # store queue - stq_alloc_next = LogicArray( - ctx, 'stq_alloc_next', 'w', self.configs.numStqEntries) - for i in range(0, self.configs.numStqEntries): - arch += Op(ctx, stq_alloc_next[i], - 'not', stq_reset[i], 'and', stq_alloc[i] - ) - arch += Op(ctx, stq_alloc[i], - stq_wen[i], 'or', stq_alloc_next[i] - ) - if self.configs.stResp: - arch += Op(ctx, stq_exec[i], - 'not', stq_wen[i], 'and', - '(', stq_exec_set[i], 'or', stq_exec[i], ')' - ) - arch += Op(ctx, stq_addr_valid[i], - 'not', stq_wen[i], 'and', - '(', stq_addr_wen[i], 'or', stq_addr_valid[i], ')' - ) - arch += Op(ctx, stq_data_valid[i], - 'not', stq_wen[i], 'and', - '(', stq_data_wen[i], 'or', stq_data_valid[i], ')' - ) - - # order matrix - # store_is_older(i,j) = (not stq_reset(j) and (stq_alloc(j) or ga_ls_order(i, j))) - # when ldq_wen(i) - # else not stq_reset(j) and store_is_older(i, j) - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, (store_is_older, i, j), - '(', 'not', (stq_reset, j), 'and', '(', (stq_alloc, - j), 'or', (ga_ls_order, i, j), ')', ')', - 'when', (ldq_wen, i), 'else', - 'not', (stq_reset, j), 'and', (store_is_older, i, j) - ) - - # pointers update - ldq_not_empty = Logic(ctx, 'ldq_not_empty', 'w') - stq_not_empty = Logic(ctx, 'stq_not_empty', 'w') - arch += Reduce(ctx, ldq_not_empty, ldq_alloc, 'or') - arch += Op(ctx, ldq_empty, 'not', ldq_not_empty) - arch += MuxLookUp(ctx, stq_not_empty, stq_alloc, stq_head) - arch += Op(ctx, stq_empty, 'not', stq_not_empty) - arch += Op(ctx, empty_o, ldq_empty, 'and', stq_empty) - - arch += WrapAdd(ctx, ldq_tail, ldq_tail, num_loads, - self.configs.numLdqEntries) - arch += WrapAdd(ctx, stq_tail, stq_tail, num_stores, - self.configs.numStqEntries) - arch += WrapAddConst(ctx, stq_issue, stq_issue, 1, - self.configs.numStqEntries) - arch += WrapAddConst(ctx, stq_resp, stq_resp, 1, - self.configs.numStqEntries) - - ldq_tail_oh = LogicVec(ctx, 'ldq_tail_oh', 'w', - self.configs.numLdqEntries) - arch += BitsToOH(ctx, ldq_tail_oh, ldq_tail) - ldq_head_next_oh = LogicVec( - ctx, 'ldq_head_next_oh', 'w', self.configs.numLdqEntries) - ldq_head_next = LogicVec(ctx, 'ldq_head_next', - 'w', self.configs.ldqAddrW) - ldq_head_sel = Logic(ctx, 'ldq_head_sel', 'w') - if self.configs.headLag: - # Update the head pointer according to the valid signal of last cycle - arch += CyclicPriorityMasking(ctx, - ldq_head_next_oh, ldq_alloc, ldq_tail_oh) - arch += Reduce(ctx, ldq_head_sel, ldq_alloc, 'or') - else: - arch += CyclicPriorityMasking(ctx, ldq_head_next_oh, - ldq_alloc_next, ldq_tail_oh) - arch += Reduce(ctx, ldq_head_sel, ldq_alloc_next, 'or') - arch += OHToBits(ctx, ldq_head_next, ldq_head_next_oh) - arch += Op(ctx, ldq_head, ldq_head_next, 'when', - ldq_head_sel, 'else', ldq_tail) - - stq_tail_oh = LogicVec(ctx, 'stq_tail_oh', 'w', - self.configs.numStqEntries) - arch += BitsToOH(ctx, stq_tail_oh, stq_tail) - stq_head_next_oh = LogicVec( - ctx, 'stq_head_next_oh', 'w', self.configs.numStqEntries) - stq_head_next = LogicVec(ctx, 'stq_head_next', - 'w', self.configs.stqAddrW) - stq_head_sel = Logic(ctx, 'stq_head_sel', 'w') - if self.configs.stResp: - if self.configs.headLag: - # Update the head pointer according to the valid signal of last cycle - arch += CyclicPriorityMasking(ctx, - stq_head_next_oh, stq_alloc, stq_tail_oh) - arch += Reduce(ctx, stq_head_sel, stq_alloc, 'or') - else: - arch += CyclicPriorityMasking(ctx, stq_head_next_oh, - stq_alloc_next, stq_tail_oh) - arch += Reduce(ctx, stq_head_sel, stq_alloc_next, 'or') - arch += OHToBits(ctx, stq_head_next, stq_head_next_oh) - arch += Op(ctx, stq_head, stq_head_next, 'when', - stq_head_sel, 'else', stq_tail) - else: - arch += WrapAddConst(ctx, stq_head_next, stq_head, - 1, self.configs.numStqEntries) - arch += Op(ctx, stq_head_sel, wresp_valid_i[0]) - arch += Op(ctx, stq_head, stq_head_next, 'when', - stq_head_sel, 'else', stq_head) - - # Load Queue Entries - ldq_alloc.regInit(init=[0]*self.configs.numLdqEntries) - ldq_issue.regInit() - if (self.configs.ldpAddrW > 0): - ldq_port_idx.regInit(ldq_wen) - ldq_addr_valid.regInit() - ldq_addr.regInit(ldq_addr_wen) - ldq_data_valid.regInit() - ldq_data.regInit(ldq_data_wen) - - # Store Queue Entries - stq_alloc.regInit(init=[0]*self.configs.numStqEntries) - if self.configs.stResp: - stq_exec.regInit() - if (self.configs.stpAddrW > 0): - stq_port_idx.regInit(stq_wen) - stq_addr_valid.regInit() - stq_addr.regInit(stq_addr_wen) - stq_data_valid.regInit() - stq_data.regInit(stq_data_wen) - - # Order for load-store - store_is_older.regInit() - - # Pointers - ldq_tail.regInit(init=0) - ldq_head.regInit(init=0) - - stq_tail.regInit(init=0) - stq_head.regInit(init=0) - stq_issue.regInit(enable=stq_issue_en, init=0) - stq_resp.regInit(enable=stq_resp_en, init=0) - - ###### Entity Instantiation ###### - - # Group Allocator - arch += lsq_submodules.group_allocator.instantiate( - ctx, - group_init_valid_i, group_init_ready_o, - ldq_tail, ldq_head, ldq_empty, - stq_tail, stq_head, stq_empty, - ldq_wen, num_loads, ldq_port_idx, - stq_wen, num_stores, stq_port_idx, - ga_ls_order - ) - - # When the condition "lsq_submodules.ptq_dispatcher_lda != None" is not true: - # The dispatcher module will be set to None when there are zero load ports. - # In this case, do not instantiate dispatching logic when there are zero load ports. - # - WARNING: This logic needs more testing - # - TODO: Also remove the load queue when there are zero load ports. - if lsq_submodules.ptq_dispatcher_lda != None: - # Load Address Port Dispatcher - arch += lsq_submodules.ptq_dispatcher_lda.instantiate( - ctx, - ldp_addr_i, ldp_addr_valid_i, ldp_addr_ready_o, - ldq_alloc, ldq_addr_valid, ldq_port_idx, ldq_addr, ldq_addr_wen, ldq_head_oh - ) - - # When the condition "lsq_submodules.qtp_dispatcher_ldd != None" is not true: - # The dispatcher module will be set to None when there are zero load ports. - # In this case, do not instantiate dispatching logic when there are zero load ports. - # - WARNING: This logic needs more testing - # - TODO: Also remove the load queue when there are zero load ports. - if lsq_submodules.qtp_dispatcher_ldd != None: - # Load Data Port Dispatcher - arch += lsq_submodules.qtp_dispatcher_ldd.instantiate( - ctx, - ldp_data_o, ldp_data_valid_o, ldp_data_ready_i, - ldq_alloc, ldq_data_valid, ldq_port_idx, ldq_data, ldq_reset, ldq_head_oh - ) - - # Store Address Port Dispatcher - arch += lsq_submodules.ptq_dispatcher_sta.instantiate( - ctx, - stp_addr_i, stp_addr_valid_i, stp_addr_ready_o, - stq_alloc, stq_addr_valid, stq_port_idx, stq_addr, stq_addr_wen, stq_head_oh - ) - - # Store Data Port Dispatcher - arch += lsq_submodules.ptq_dispatcher_std.instantiate( - ctx, - stp_data_i, stp_data_valid_i, stp_data_ready_o, - stq_alloc, stq_data_valid, stq_port_idx, stq_data, stq_data_wen, stq_head_oh - ) - - # Store Backward Port Dispatcher - if self.configs.stResp: - arch += lsq_submodules.qtp_dispatcher_stb.instantiate( - ctx, - None, stp_exec_valid_o, stp_exec_ready_i, - stq_alloc, stq_exec, stq_port_idx, None, stq_reset, stq_head_oh - ) - - ###### Dependency Check ###### - load_idx_oh = LogicVecArray( - ctx, 'load_idx_oh', 'w', self.configs.numLdMem, self.configs.numLdqEntries) - load_en = LogicArray(ctx, 'load_en', 'w', self.configs.numLdMem) - - # Multiple store channels not yet implemented - assert (self.configs.numStMem == 1) - store_idx = LogicVec(ctx, 'store_idx', 'w', self.configs.stqAddrW) - store_en = Logic(ctx, 'store_en', 'w') - - # Matrix Generation - ld_st_conflict = LogicVecArray( - ctx, 'ld_st_conflict', 'w', self.configs.numLdqEntries, self.configs.numStqEntries) - can_bypass = LogicVecArray( - ctx, 'can_bypass', 'w', self.configs.numLdqEntries, self.configs.numStqEntries) - can_bypass_p0 = LogicVecArray( - ctx, 'can_bypass_p0', pipe0_type, self.configs.numLdqEntries, self.configs.numStqEntries) - if self.configs.pipe0: - can_bypass_p0.regInit(init=[0]*self.configs.numLdqEntries) - - ldq_alloc_pcomp = LogicArray( - ctx, 'ldq_alloc_pcomp', pipe_comp_type, self.configs.numLdqEntries) - ldq_addr_valid_pcomp = LogicArray( - ctx, 'ldq_addr_valid_pcomp', pipe_comp_type, self.configs.numLdqEntries) - stq_alloc_pcomp = LogicArray( - ctx, 'stq_alloc_pcomp', pipe_comp_type, self.configs.numStqEntries) - stq_addr_valid_pcomp = LogicArray( - ctx, 'stq_addr_valid_pcomp', pipe_comp_type, self.configs.numStqEntries) - stq_data_valid_pcomp = LogicArray( - ctx, 'stq_data_valid_pcomp', pipe_comp_type, self.configs.numStqEntries) - stq_tail_update_pcomp = Logic(ctx, 'stq_tail_update_pcomp', pipe_comp_type) - # addr_valid_pcomp is always a wire: combines other registers signals - addr_valid_pcomp = LogicVecArray( - ctx, 'addr_valid_pcomp', 'w', self.configs.numLdqEntries, self.configs.numStqEntries) - addr_same_pcomp = LogicVecArray( - ctx, 'addr_same_pcomp', pipe_comp_type, self.configs.numLdqEntries, self.configs.numStqEntries) - store_is_older_pcomp = LogicVecArray( - ctx, 'store_is_older_pcomp', pipe_comp_type, self.configs.numLdqEntries, self.configs.numStqEntries) - - # combinational signal indicating whether a load has already completed (assuming it is allocated), meaning the - # data (= read response) from memory has been received - load_completed = LogicArray(ctx, 'load_completed', 'w', self.configs.numLdqEntries) - # combinational signal indicating whether a store has already completed (assuming it is allocated), meaning the - # write response from memory has been received - store_completed = LogicArray(ctx, 'store_completed', 'w', self.configs.numStqEntries) - - if self.configs.pipeComp: - ldq_alloc_pcomp.regInit(init=[0]*self.configs.numLdqEntries) - ldq_addr_valid_pcomp.regInit() - stq_alloc_pcomp.regInit(init=[0]*self.configs.numStqEntries) - stq_addr_valid_pcomp.regInit() - stq_data_valid_pcomp.regInit() - stq_tail_update_pcomp.regInit() - addr_same_pcomp.regInit() - store_is_older_pcomp.regInit() - - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, (ldq_alloc_pcomp, i), (ldq_alloc, i)) - arch += Op(ctx, (ldq_addr_valid_pcomp, i), - (ldq_addr_valid, i)) - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, (stq_alloc_pcomp, j), (stq_alloc, j)) - arch += Op(ctx, (stq_addr_valid_pcomp, j), - (stq_addr_valid, j)) - arch += Op(ctx, (stq_data_valid_pcomp, j), - (stq_data_valid, j)) - arch += Op(ctx, stq_tail_update_pcomp, stq_tail_update) - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, (store_is_older_pcomp, i, j), - (store_is_older, i, j)) - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, (addr_valid_pcomp, i, j), - (ldq_addr_valid_pcomp, i), 'and', (stq_addr_valid_pcomp, j)) - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, (addr_same_pcomp, i, j), '\'1\'', 'when', - (ldq_addr, i), '=', (stq_addr, j), 'else', '\'0\'') - - for i in range(self.configs.numLdqEntries): - # No need to use pipelined ldq_data_valid here: As soon as the load entry has valid data (in the queue - # itself, not the pipeline), the load is considered completed. - arch += Op(ctx, load_completed[i], ldq_data_valid[i]) - for i in range(self.configs.numStqEntries): - if self.configs.stResp: - # No need to use pipelined stq_exec here: As soon as the store response has been received from memory, - # the store is considered completed. - arch += Op(ctx, store_completed[i], stq_exec[i]) - else: - # If the store queue entry is not valid (anymore), the store has completed. - arch += Op(ctx, store_completed[i], 'not', stq_alloc[i]) - - # A load conflicts with a store when: - # 1. The store entry is valid, and - # 2. The store entry hasn't completed (received write response from memory), and - # 3. The store is older than the load, and - # 4. The address conflicts(same or invalid store address). - # NOTE: Because we only consider non-completed stores to conflict with a load, bypass will - # not forward from any stores which are already completed (but still allocated). However, - # such loads only exist if store responses or pipe0 are enabled, which is not the case by - # default. - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, - (ld_st_conflict, i, j), - (stq_alloc_pcomp, j), 'and', - 'not', (store_completed, j), 'and', - (store_is_older_pcomp, i, j), 'and', - '(', (addr_same_pcomp, i, - j), 'or', 'not', (stq_addr_valid_pcomp, j), ')' - ) - - # A conflicting store entry can be bypassed to a load entry when: - # 1. The load entry is valid, and - # 2. The load entry is not issued yet, and - # 3. The address of the load-store pair are both valid and values the same. - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, - (can_bypass_p0, i, j), - (ldq_alloc_pcomp, i), 'and', - (stq_data_valid_pcomp, j), 'and', - (addr_same_pcomp, i, j), 'and', - (addr_valid_pcomp, i, j) - ) - for i in range(0, self.configs.numLdqEntries): - for j in range(0, self.configs.numStqEntries): - arch += Op(ctx, - (can_bypass, i, j), - 'not', (ldq_issue, i), 'and', - (can_bypass_p0, i, j) - ) - - # Load - - load_conflict = LogicArray( - ctx, 'load_conflict', 'w', self.configs.numLdqEntries) - load_req_valid = LogicArray( - ctx, 'load_req_valid', 'w', self.configs.numLdqEntries) - can_load = LogicArray( - ctx, 'can_load', 'w', self.configs.numLdqEntries) - can_load_p0 = LogicArray( - ctx, 'can_load_p0', pipe0_type, self.configs.numLdqEntries) - if self.configs.pipe0: - can_load_p0.regInit(init=[0]*self.configs.numLdqEntries) - - # The load conflicts with any store - for i in range(0, self.configs.numLdqEntries): - arch += Reduce(ctx, - load_conflict[i], ld_st_conflict[i], 'or') - # The load is valid when the entry is valid and not yet issued, the load address should also be valid. - # We do not need to check ldq_data_valid, since unissued load request cannot have valid data. - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, load_req_valid[i], ldq_alloc_pcomp[i], - 'and', ldq_addr_valid_pcomp[i]) - # Generate list for loads that does not face dependency issue - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, can_load_p0[i], 'not', - load_conflict[i], 'and', load_req_valid[i]) - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, can_load[i], 'not', - ldq_issue[i], 'and', can_load_p0[i]) - - ldq_head_oh_p0 = LogicVec( - ctx, 'ldq_head_oh_p0', pipe0_type, self.configs.numLdqEntries) - if self.configs.pipe0: - ldq_head_oh_p0.regInit() - arch += Op(ctx, ldq_head_oh_p0, ldq_head_oh) - - can_load_list = [] - can_load_list.append(can_load) - for w in range(0, self.configs.numLdMem): - arch += CyclicPriorityMasking( - ctx, load_idx_oh[w], can_load_list[w], ldq_head_oh_p0) - arch += Reduce(ctx, load_en[w], can_load_list[w], 'or') - if (w+1 != self.configs.numLdMem): - load_idx_oh_LogicArray = LogicArray( - ctx, f'load_idx_oh_Array_{w+1}', 'w', self.configs.numLdqEntries) - arch += VecToArray(ctx, - load_idx_oh_LogicArray, load_idx_oh[w]) - can_load_list.append(LogicArray( - ctx, f'can_load_list_{w+1}', 'w', self.configs.numLdqEntries)) - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, can_load_list[w+1][i], 'not', - load_idx_oh_LogicArray[i], 'and', can_load_list[w][i]) - - # Store - # When pipelining (pipe0) is enabled, this uses look-ahead to the next store entry to reduce the critical path. - # Both the current and next stores are checked for validity and conflicts, and the result is multiplexed "late - # in the clock cycle" to reduce the critical path. When pipelining is disabled, only the current store entry is - # checked, so there is no need for computing the signals for the next store entry, and for the multiplexing. - - # Store request is valid if the entry is allocated and has valid address+data. - store_req_valid_arr = LogicArray(ctx, 'store_req_valid_arr', 'w', self.configs.numStqEntries) - for i in range(self.configs.numStqEntries): - arch += Op(ctx, store_req_valid_arr[i], stq_alloc_pcomp[i], 'and', stq_addr_valid_pcomp[i], 'and', stq_data_valid_pcomp[i]) - - store_conflict = Logic(ctx, 'store_conflict', 'w') - store_req_valid_p0 = Logic(ctx, 'store_req_valid_p0', pipe0_type) - st_ld_conflict_p0 = LogicVec(ctx, 'st_ld_conflict_p0', pipe0_type, self.configs.numLdqEntries) - if self.configs.pipe0: - store_req_valid_p0.regInit(init=0) - st_ld_conflict_p0.regInit() - - # next issue pointer (needed for look-ahead when pipelining is enabled and for stalling store issue) - stq_issue_next = LogicVec(ctx, 'stq_issue_next', 'w', self.configs.stqAddrW) - arch += WrapAddConst(ctx, stq_issue_next, stq_issue, 1, self.configs.numStqEntries) - - # checks for current and next (if needed) store entry - store_req_valid_curr = Logic(ctx, 'store_req_valid_curr', 'w') - st_ld_conflict_curr = LogicVec(ctx, 'st_ld_conflict_curr', 'w', self.configs.numLdqEntries) - if self.configs.pipe0: - # with pipelining: also compute for the next entry - store_req_valid_next = Logic(ctx, 'store_req_valid_next', 'w') - st_ld_conflict_next = LogicVec(ctx, 'st_ld_conflict_next', 'w', self.configs.numLdqEntries) - - # validity lookup - arch += MuxLookUp(ctx, store_req_valid_curr, store_req_valid_arr, stq_issue) - if self.configs.pipe0: - # with pipelining: also compute for the next entry - arch += MuxLookUp(ctx, store_req_valid_next, store_req_valid_arr, stq_issue_next) - - # A store conflicts with a load when: - # 1. The load entry is valid, and - # 2. The load entry hasn't completed (received data from memory), and - # 3. The load is older than the store, and - # 4. The address conflicts(same or invalid store address). - # Index order are reversed for store matrix. - for i in range(self.configs.numLdqEntries): - arch += Op(ctx, - (st_ld_conflict_curr, i), - (ldq_alloc_pcomp, i), 'and', - 'not', (load_completed, i), 'and', - 'not', MuxIndex( - store_is_older_pcomp[i], stq_issue), 'and', - '(', MuxIndex( - addr_same_pcomp[i], stq_issue), 'or', 'not', (ldq_addr_valid_pcomp, i), ')' - ) - if self.configs.pipe0: - # with pipelining: also compute for the next entry - for i in range(self.configs.numLdqEntries): - arch += Op(ctx, - (st_ld_conflict_next, i), - (ldq_alloc_pcomp, i), 'and', - 'not', (load_completed, i), 'and', - 'not', MuxIndex( - store_is_older_pcomp[i], stq_issue_next), 'and', - '(', MuxIndex( - addr_same_pcomp[i], stq_issue_next), 'or', 'not', (ldq_addr_valid_pcomp, i), ')' - ) - - if self.configs.pipe0: - # with pipelining: multiplex between current and next store entry - # Multiplex from current and next - arch += Op(ctx, st_ld_conflict_p0, st_ld_conflict_next, - 'when', stq_issue_en, 'else', st_ld_conflict_curr) - arch += Op(ctx, store_req_valid_p0, store_req_valid_next, 'when', - stq_issue_en, 'else', store_req_valid_curr) - else: - # without pipelining: only consider current store entry - arch += Op(ctx, st_ld_conflict_p0, st_ld_conflict_curr) - arch += Op(ctx, store_req_valid_p0, store_req_valid_curr) - - # Stalling Store Issue - # For small queues relative to the memory latency, it is possible that all store entries - # have been allocated and are in-flight to the memory. In this case, the store issue - # pointer would wrap around and re-issue the same stores a second time. To avoid this, we - # stall store issue once the issue pointer catches up to the tail pointer (i.e., when all - # store entries are in-flight), and only allow store issue to proceed when the tail pointer - # moves (indicating a store entry has been freed up and subsequently allocated again). - store_issue_stall_p0 = Logic(ctx, 'store_issue_stall', 'r') - store_issue_stall_set = Logic(ctx, 'store_issue_stall_set', 'w') - store_issue_stall_reset = Logic(ctx, 'store_issue_stall_reset', 'w') - store_issue_stall_p0.regInit(init=0) - arch += Op(ctx, store_issue_stall_set, stq_issue_en, 'when', '(', stq_issue_next, '=', stq_tail, ')', 'else', "'0'") - arch += Op(ctx, store_issue_stall_reset, stq_tail_update_pcomp) - arch += Op(ctx, store_issue_stall_p0, 'not', store_issue_stall_reset, 'and', '(', store_issue_stall_p0, 'or', store_issue_stall_set, ')') - - # The store conflicts with any load - arch += Reduce(ctx, store_conflict, st_ld_conflict_p0, 'or') - arch += Op(ctx, store_en, 'not', store_conflict, 'and', store_req_valid_p0, 'and', 'not', store_issue_stall_p0) - arch += Op(ctx, store_idx, stq_issue) - - # Bypass - bypass_idx_oh_p0 = LogicVecArray( - ctx, 'bypass_idx_oh_p0', pipe0_type, self.configs.numLdqEntries, self.configs.numStqEntries) - bypass_en = LogicArray(ctx, 'bypass_en', 'w', - self.configs.numLdqEntries) - if self.configs.pipe0: - bypass_idx_oh_p0.regInit() - if self.configs.bypass: - stq_last_oh = LogicVec( - ctx, 'stq_last_oh', 'w', self.configs.numStqEntries) - arch += BitsToOHSub1(ctx, stq_last_oh, stq_tail) - for i in range(0, self.configs.numLdqEntries): - bypass_en_vec = LogicVec( - ctx, f'bypass_en_vec_{i}', 'w', self.configs.numStqEntries) - # Search for the youngest store that is older than the load and conflicts - arch += CyclicPriorityMasking( - ctx, bypass_idx_oh_p0[i], ld_st_conflict[i], stq_last_oh, True) - # Check if the youngest conflict store can bypass with the load - arch += Op(ctx, bypass_en_vec, - bypass_idx_oh_p0[i], 'and', can_bypass[i]) - arch += Reduce(ctx, bypass_en[i], bypass_en_vec, 'or') - else: - # bypass disabled: tie bypass signals low - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, bypass_en[i], 0) - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, bypass_idx_oh_p0[i], 0) - - # Pipeline Stage 1 - - # load registers (if enabled, w/ backpressure) - load_idx_oh_p1 = LogicVecArray( - ctx, 'load_idx_oh_p1', pipe1_type, self.configs.numLdMem, self.configs.numLdqEntries) - load_en_p1 = LogicArray( - ctx, 'load_en_p1', pipe1_type, self.configs.numLdMem) - # store registers (if enabled, w/ backpressure) - store_idx_p1 = LogicVec( - ctx, 'store_idx_p1', pipe1_type, self.configs.stqAddrW) - store_en_p1 = Logic( - ctx, 'store_en_p1', pipe1_type) - # bypass registers (if enabled, w/o backpressure) - bypass_idx_oh_p1 = LogicVecArray( - ctx, 'bypass_idx_oh_p1', pipe1_type, self.configs.numLdqEntries, self.configs.numStqEntries) - bypass_en_p1 = LogicArray( - ctx, 'bypass_en_p1', pipe1_type, self.configs.numLdqEntries) - - load_p1_ready = LogicArray(ctx, 'load_p1_ready', 'w', self.configs.numLdMem) - store_p1_ready = Logic(ctx, 'store_p1_ready', 'w') - - if self.configs.pipe1: - # pipeline register control signals (load_*_p1, store_*_p1) - # This implements a pipeline register stage with backpressure and - # with a # combinational path from output ready to input ready. We - # are ready # for new data if either there is a handshake at the - # output (*_hs), # or the register is currently empty (not *_en_p1). - load_hs = LogicArray(ctx, 'load_hs', 'w', self.configs.numLdMem) - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, load_hs[w], load_en_p1[w], 'and', rreq_ready_i[w]) - arch += Op(ctx, load_p1_ready[w], load_hs[w], 'or', 'not', load_en_p1[w]) - store_hs = Logic(ctx, 'store_hs', 'w') - arch += Op(ctx, store_hs, store_en_p1, 'and', wreq_ready_i[0]) - arch += Op(ctx, store_p1_ready, store_hs, 'or', 'not', store_en_p1) - # register init - load_idx_oh_p1.regInit(enable=load_p1_ready) - load_en_p1.regInit(init=[0]*self.configs.numLdMem, enable=load_p1_ready) - store_idx_p1.regInit(enable=store_p1_ready) - store_en_p1.regInit(init=0, enable=store_p1_ready) - bypass_idx_oh_p1.regInit() - bypass_en_p1.regInit(init=[0]*self.configs.numLdqEntries) - else: - # non-pipelined "pseudo-control" signals - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, load_p1_ready[w], rreq_ready_i[w], 'and', load_en[w]) - arch += Op(ctx, store_p1_ready, wreq_ready_i[0]) - - # pipeline register assignments - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, load_idx_oh_p1[w], load_idx_oh[w]) - arch += Op(ctx, load_en_p1[w], load_en[w]) - arch += Op(ctx, store_idx_p1, store_idx) - arch += Op(ctx, store_en_p1, store_en) - for i in range(0, self.configs.numLdqEntries): - arch += Op(ctx, bypass_idx_oh_p1[i], bypass_idx_oh_p0[i]) - arch += Op(ctx, bypass_en_p1[i], bypass_en[i]) - - ###### Read/Write ###### - # Read Request - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, rreq_valid_o[w], load_en_p1[w]) - arch += OHToBits(ctx, rreq_id_o[w], load_idx_oh_p1[w]) - arch += Mux1H(ctx, rreq_addr_o[w], ldq_addr, load_idx_oh_p1[w]) - - for i in range(0, self.configs.numLdqEntries): - ldq_issue_set_vec = LogicVec( - ctx, f'ldq_issue_set_vec_{i}', 'w', self.configs.numLdMem) - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, (ldq_issue_set_vec, w), - '(', (load_idx_oh, w, i), 'and', - (load_p1_ready, w), ')', 'or', - (bypass_en, i) - ) - arch += Reduce(ctx, ldq_issue_set[i], ldq_issue_set_vec, 'or') - - # Write Request - arch += Op(ctx, wreq_valid_o[0], store_en_p1) - arch += Op(ctx, wreq_id_o[0], 0) - arch += MuxLookUp(ctx, wreq_addr_o[0], stq_addr, store_idx_p1) - arch += MuxLookUp(ctx, wreq_data_o[0], stq_data, store_idx_p1) - arch += Op(ctx, stq_issue_en, store_en, 'and', store_p1_ready) - - # Read Response and Bypass - for i in range(0, self.configs.numLdqEntries): - # check each read response channel for each load - read_idx_oh = LogicArray( - ctx, f'read_idx_oh_{i}', 'w', self.configs.numLdMem) - read_valid = Logic(ctx, f'read_valid_{i}', 'w') - read_data = LogicVec( - ctx, f'read_data_{i}', 'w', self.configs.dataW) - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, read_idx_oh[w], rresp_valid_i[w], 'when', - '(', rresp_id_i[w], '=', (i, self.configs.idW), ')', 'else', '\'0\'') - arch += Mux1H(ctx, read_data, rresp_data_i, read_idx_oh) - arch += Reduce(ctx, read_valid, read_idx_oh, 'or') - # multiplex from store queue data - bypass_data = LogicVec( - ctx, f'bypass_data_{i}', 'w', self.configs.dataW) - arch += Mux1H(ctx, bypass_data, stq_data, bypass_idx_oh_p1[i]) - # multiplex from read and bypass data - arch += Op(ctx, ldq_data[i], read_data, 'or', bypass_data) - arch += Op(ctx, ldq_data_wen[i], - bypass_en_p1[i], 'or', read_valid) - for w in range(0, self.configs.numLdMem): - arch += Op(ctx, rresp_ready_o[w], '\'1\'') - - # Write Response - if self.configs.stResp: - for i in range(0, self.configs.numStqEntries): - arch += Op(ctx, stq_exec_set[i], - wresp_valid_i[0], 'when', - '(', stq_resp, '=', (i, self.configs.stqAddrW), ')', - 'else', '\'0\'' - ) - else: - for i in range(0, self.configs.numStqEntries): - arch += Op(ctx, stq_reset[i], - wresp_valid_i[0], 'when', - '(', stq_resp, '=', (i, self.configs.stqAddrW), ')', - 'else', '\'0\'' - ) - arch += Op(ctx, stq_resp_en, wresp_valid_i[0]) - arch += Op(ctx, wresp_ready_o[0], '\'1\'') - - ###### Write To File ###### - ctx.portInitString += '\n\t);' - ctx.regInitString += '\tend process;\n' - - # Write to the file - with open(f'{path_rtl}/{self.name}.vhd', 'a') as file: - # with open(name + '.vhd', 'w') as file: - file.write(ctx.library) - file.write(f'entity {self.module_name} is\n') - file.write(ctx.portInitString) - file.write('\nend entity;\n\n') - file.write(f'architecture arch of {self.module_name} is\n') - file.write(ctx.signalInitString) - file.write('begin\n' + arch + '\n') - file.write(ctx.regInitString + 'end architecture;\n') - - def instantiate(self, **kwargs) -> str: - """ - *Instantiation of LSQ is in lsq-generator.py. - """ - pass diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/__init__.py b/tools/backend/lsq-generator-python/vhdl_gen/operators/__init__.py deleted file mode 100644 index 24da3d1798..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# vhdl_gen/operators/__init__.py -from vhdl_gen.operators.assign import Op -from vhdl_gen.operators.arithmetic import WrapAdd, WrapAddConst, WrapSub -from vhdl_gen.operators.shifts import CyclicLeftShift -from vhdl_gen.operators.reduction import Reduce -from vhdl_gen.operators.mux import Mux1H, Mux1HROM, MuxIndex, MuxLookUp -from vhdl_gen.operators.masking import CyclicPriorityMasking -from vhdl_gen.operators.conversions import VecToArray, BitsToOH, BitsToOHSub1, OHToBits - -__all__ = [ - "Op", - "WrapAdd", "WrapAddConst", "WrapSub", - "CyclicLeftShift", - "Reduce", - "Mux1H", "Mux1HROM", "MuxIndex", "MuxLookUp", - "CyclicPriorityMasking", - "VecToArray", "BitsToOH", "BitsToOHSub1", "OHToBits", -] diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/arithmetic.py b/tools/backend/lsq-generator-python/vhdl_gen/operators/arithmetic.py deleted file mode 100644 index d896d3cf0c..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/arithmetic.py +++ /dev/null @@ -1,88 +0,0 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * - - -def WrapAdd(ctx: VHDLContext, out, in_a, in_b, max: int) -> str: - """ - if "max" is power of 2: - out = in_a + in_b - else: - "sum", "res" -> one extra bit to extend the bit-width - Concatenates '0' to each input to extend the bit-width - - sum = in_a + in_b - - if sum >= max: - out = sum - max - else - out = sum - """ - - str_ret = ctx.get_current_indent() + '-- WrapAdd Begin\n' - str_ret += ctx.get_current_indent() + f'-- WrapAdd({out.name}, {in_a.name}, {in_b.name}, {max})\n' - if (isPow2(max)): - str_ret += ctx.get_current_indent() + f'{out.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned({in_a.getNameRead()}) + unsigned({in_b.getNameRead()}));\n' - else: - ctx.use_temp() - sum = LogicVec(ctx, ctx.get_temp('sum'), 'w', out.size + 1) - res = LogicVec(ctx, ctx.get_temp('res'), 'w', out.size + 1) - str_ret += ctx.get_current_indent() + f'{sum.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned(\'0\' & {in_a.getNameRead()}) + unsigned(\'0\' & {in_b.getNameRead()}));\n' - str_ret += ctx.get_current_indent() + f'{res.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned({sum.getNameRead()}) - {max}) ' + \ - f'when unsigned({sum.getNameRead()}) >= {max} else {sum.getNameRead()};\n' - str_ret += ctx.get_current_indent() + f'{out.getNameWrite()} <= {res.getNameRead()}({out.size-1} downto 0);\n' - str_ret += ctx.get_current_indent() + '-- WrapAdd End\n\n' - return str_ret - - -def WrapAddConst(ctx: VHDLContext, out, in_a, const: int, max: int) -> str: - """ - if "max" is power of 2: - out = in_a + const - else: - if in_a + const >= max: - out = in_a + const - max - else: - out = in_a + const - """ - - str_ret = ctx.get_current_indent() + '-- WrapAdd Begin\n' - str_ret += ctx.get_current_indent() + f'-- WrapAdd({out.name}, {in_a.name}, {const}, {max})\n' - if (isPow2(max)): - str_ret += ctx.get_current_indent() + f'{out.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned({in_a.getNameRead()}) + {const});\n' - else: - str_ret += ctx.get_current_indent() + f'{out.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned({in_a.getNameRead()}) - {max - const}) ' + \ - f'when unsigned({in_a.getNameRead()}) >= {max - const} else ' + \ - f'std_logic_vector(unsigned({in_a.getNameRead()}) + {const});\n' - str_ret += ctx.get_current_indent() + '-- WrapAdd End\n\n' - return str_ret - - -def WrapSub(ctx: VHDLContext, out, in_a, in_b, max: int) -> str: - """ - if "max" is power of 2: - out = in_a - in_b - else: - if in_a >= in_b: - out = in_a - in_b - else: - out = (in_a + max) - in_b - """ - - str_ret = ctx.get_current_indent() + '-- WrapSub Begin\n' - str_ret += ctx.get_current_indent() + f'-- WrapSub({out.name}, {in_a.name}, {in_b.name}, {max})\n' - if (isPow2(max)): - str_ret += ctx.get_current_indent() + f'{out.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned({in_a.getNameRead()}) - unsigned({in_b.getNameRead()}));\n' - else: - str_ret += ctx.get_current_indent() + f'{out.getNameWrite()} <= ' + \ - f'std_logic_vector(unsigned({in_a.getNameRead()}) - unsigned({in_b.getNameRead()})) ' + \ - f'when {in_a.getNameRead()} >= {in_b.getNameRead()} else\n' + '\t'*(ctx.tabLevel+1) + \ - f'std_logic_vector({max} - unsigned({in_b.getNameRead()}) + unsigned({in_a.getNameRead()}));\n' - str_ret += ctx.get_current_indent() + '-- WrapAdd End\n\n' - return str_ret diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/assign.py b/tools/backend/lsq-generator-python/vhdl_gen/operators/assign.py deleted file mode 100644 index 2b3fdebe2d..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/assign.py +++ /dev/null @@ -1,41 +0,0 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * - - -def Op(ctx: VHDLContext, out, *list_in) -> str: - """ - Generates a proper VHDL assignment statement. - - Args: - out: LHS of the assignment - *list_in: A sequence of RHS elements - - Example: Op(ctx, valid, a,'when', b, 'else', 0) - valid <= a when b else '0' - - """ - if type(out) == tuple: - if len(out) == 2: - str_ret = ctx.get_current_indent() + f'{out[0].getNameWrite(out[1])} <=' - else: - str_ret = ctx.get_current_indent() + f'{out[0].getNameWrite(out[1], out[2])} <=' - else: - str_ret = ctx.get_current_indent() + f'{out.getNameWrite()} <=' - size = None if type(out) == Logic else out.size - for arg in list_in: - if type(arg) == str: - str_ret += ' ' + arg - elif type(arg) == int: - str_ret += ' ' + IntToBits(arg, size) - elif type(arg) == tuple: - if type(arg[0]) == int: - str_ret += ' ' + IntToBits(arg[0], arg[1]) - elif len(arg) == 2: - str_ret += ' ' + arg[0].getNameRead(arg[1]) - else: - str_ret += ' ' + arg[0].getNameRead(arg[1], arg[2]) - else: - str_ret += ' ' + arg.getNameRead() - str_ret += ';\n' - return str_ret diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/conversions.py b/tools/backend/lsq-generator-python/vhdl_gen/operators/conversions.py deleted file mode 100644 index 4c08d4eae3..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/conversions.py +++ /dev/null @@ -1,87 +0,0 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * -from vhdl_gen.operators import * - - -def VecToArray(ctx: VHDLContext, dout, din) -> str: - """ - Converts LogicVec to LogicArray - - Parameter: - dout (LogicArray) - din (LogicVec) - - Example: - din = "0101" - dout = ('0'; '1'; '0'; '1') - """ - size = din.size - assert dout.length == size - str_ret = '' - for i in range(0, size): - str_ret += Op(ctx, (dout, i), (din, i)) - return str_ret - - -def BitsToOH(ctx: VHDLContext, dout, din) -> str: - """ - Convert a binary vector into its one-hot representation in VHDL. - - Example: - din = "01" - dout = "0010" - """ - str_ret = ctx.get_current_indent() + '-- Bits To One-Hot Begin\n' - str_ret += ctx.get_current_indent() + f'-- BitsToOH({dout.name}, {din.name})\n' - for i in range(0, dout.size): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(i)} <= ' \ - f'\'1\' when {din.getNameRead()} = {IntToBits(i, din.size)} else \'0\';\n' - str_ret += ctx.get_current_indent() + '-- Bits To One-Hot End\n\n' - return str_ret - - -def BitsToOHSub1(ctx: VHDLContext, dout, din) -> str: - """ - Convert a binary vector into its one-hot representation in VHDL. - The result one-hot representation should be cyclic right shifted. - - Example: - din = "01" - dout = "0001" - """ - str_ret = ctx.get_current_indent() + '-- Bits To One-Hot Begin\n' - str_ret += ctx.get_current_indent() + f'-- BitsToOHSub1({dout.name}, {din.name})\n' - for i in range(0, dout.size): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(i)} <= ' \ - f'\'1\' when {din.getNameRead()} = {IntToBits((i+1) % dout.size, din.size)} else \'0\';\n' - str_ret += ctx.get_current_indent() + '-- Bits To One-Hot End\n\n' - return str_ret - - -def OHToBits(ctx: VHDLContext, dout, din) -> str: - """ - Generate VHDL code to convert a one-hot vector into its binary index. - - Example: - din = "0010" - dout = "01" - """ - - str_ret = ctx.get_current_indent() + '-- One-Hot To Bits Begin\n' - str_ret += ctx.get_current_indent() + f'-- OHToBits({dout.name}, {din.name})\n' - size = dout.size - size_in = din.size - ctx.use_temp() - for i in range(0, size): - temp_in = LogicArray(ctx, ctx.get_temp(f'in_{i}'), 'w', size_in) - temp_out = Logic(ctx, ctx.get_temp(f'out_{i}'), 'w') - for j in range(0, size_in): - if ((j // (2**i)) % 2 == 1): - str_ret += Op(ctx, (temp_in, j), (din, j)) - else: - str_ret += Op(ctx, (temp_in, j), '\'0\'') - str_ret += Reduce(ctx, temp_out, temp_in, 'or', False) - str_ret += Op(ctx, (dout, i), temp_out) - str_ret += ctx.get_current_indent() + '-- One-Hot To Bits End\n\n' - return str_ret diff --git a/tools/backend/lsq-generator-python/vhdl_gen/operators/masking.py b/tools/backend/lsq-generator-python/vhdl_gen/operators/masking.py deleted file mode 100644 index 3fadba2ab9..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/operators/masking.py +++ /dev/null @@ -1,120 +0,0 @@ -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * -from vhdl_gen.signals import * -from vhdl_gen.operators import * - - -def CyclicPriorityMasking(ctx: VHDLContext, dout, din, base, reverse=False) -> str: - """ - Parameters: - dout (LogicVecArray, LogicArray, LogicVec): - Destination to write the masked result. - One youngest or oldest bit set to '1' and the other to '0' per each Array - din (LogicVecArray, LogicArray, LogicVec): - Input data to be masked. - base (LogicVec): - Binary pivot index for the rotation mask. - reverse (bool, optional): - Choose direction of masking. - False -> Find the oldest (Searching direction: base to MSB -> LSB to base) - True -> Find the youngest (Searching direction: base to LSB -> MSB to base) - - Example: - 1. din1 = 010110 2. din2 = 100100 3. din3 = 000110 - base = 001000 base = 001000 base = 001000 - reverse = False reverse = True reverse = False - - dout1= 010000 dout2= 000100 dout3= 000010 - (base to MSB) (base to LSB) (base to MSB -> LSB to base) - - Behavior (with the Example 1): - double_in = 010110 010110 - base = 000000 001000 - double_in - base = 010110 001110 - ~(double_in - base) = 101001 110001 - double_out = double_in & ~(double_in - base) - = 000000 010000 - dout = 000000 | 010000 - = 010000 - - Example (LogicVecArray din): - 1. din = 010 - 000 - 100 - 010 - base = 001 - reverse = False - - priority masking -> (0th col) [0010] with base = 1 -> 0010 - priority masking -> (1st col) [1001] with base = 1 -> 0001 - priority masking -> (2nd col) [0000] with base = 1 -> 0000 - - -> dout = 000 - 000 - 100 - 010 - """ - - str_ret = ctx.get_current_indent() + '-- Priority Masking Begin\n' - str_ret += ctx.get_current_indent() + f'-- CyclicPriorityMask({dout.name}, {din.name}, {base.name})\n' - ctx.use_temp() - if (type(din) == LogicVecArray): - assert (reverse == False) - for i in range(0, din.size): - size = din.length - double_in = LogicVec(ctx, ctx.get_temp(f'double_in_{i}'), 'w', size*2) - for j in range(0, size): - str_ret += Op(ctx, (double_in, j), (din, j, i)) - str_ret += Op(ctx, (double_in, j+size), (din, j, i)) - double_out = LogicVec(ctx, ctx.get_temp(f'double_out_{i}'), 'w', size*2) - str_ret += Op(ctx, double_out, double_in, 'and', 'not', - 'std_logic_vector(', 'unsigned(', double_in, ')', '-', 'unsigned(', (0, size), '&', base, ')', ')' - ) - for j in range(0, size): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(j, i)} <= ' + \ - f'{double_out.getNameRead(j)} or {double_out.getNameRead(j+size)};\n' - else: - if reverse: - if (type(din) == LogicArray): - size = din.length - else: - size = din.size - double_in = LogicVec(ctx, ctx.get_temp('double_in'), 'w', size*2) - for i in range(0, size): - str_ret += Op(ctx, (double_in, i), (din, size-1-i)) - str_ret += Op(ctx, (double_in, i+size), (din, size-1-i)) - base_rev = LogicVec(ctx, ctx.get_temp('base_rev'), 'w', size) - for i in range(0, size): - str_ret += Op(ctx, (base_rev, i), (base, size-1-i)) - double_out = LogicVec(ctx, ctx.get_temp('double_out'), 'w', size*2) - str_ret += Op(ctx, double_out, double_in, 'and', 'not', - 'std_logic_vector(', 'unsigned(', double_in, ')', '-', 'unsigned(', (0, size), '&', base_rev, ')', ')' - ) - for i in range(0, size): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(size-1-i)} <= ' + \ - f'{double_out.getNameRead(i)} or {double_out.getNameRead(i+size)};\n' - else: - if (type(din) == LogicArray): - size = din.length - double_in = LogicVec(ctx, ctx.get_temp('double_in'), 'w', size*2) - for i in range(0, size): - str_ret += Op(ctx, (double_in, i), (din, i)) - str_ret += Op(ctx, (double_in, i+size), (din, i)) - else: - size = din.size - double_in = LogicVec(ctx, ctx.get_temp('double_in'), 'w', size*2) - str_ret += Op(ctx, double_in, din, '&', din) - double_out = LogicVec(ctx, ctx.get_temp('double_out'), 'w', size*2) - str_ret += Op(ctx, double_out, double_in, 'and', 'not', - 'std_logic_vector(', 'unsigned(', double_in, ')', '-', 'unsigned(', (0, size), '&', base, ')', ')' - ) - if (type(dout) == LogicVec): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite()} <= ' + \ - f'{double_out.getNameRead()}({size-1} downto 0) or ' + \ - f'{double_out.getNameRead()}({2*size-1} downto {size});\n' - else: - for i in range(0, size): - str_ret += ctx.get_current_indent() + f'{dout.getNameWrite(i)} <= ' + \ - f'{double_out.getNameRead(i)} or {double_out.getNameRead(i+size)};\n' - str_ret += ctx.get_current_indent() + '-- Priority Masking End\n\n' - return str_ret diff --git a/tools/backend/lsq-generator-python/vhdl_gen/signals.py b/tools/backend/lsq-generator-python/vhdl_gen/signals.py deleted file mode 100644 index e54c2b2bee..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/signals.py +++ /dev/null @@ -1,384 +0,0 @@ -# ===----------------------------------------------------------------------===# -# VHDL Signal Definition -# ===----------------------------------------------------------------------===# -# This section defined Python classes that generate VHDL signal declarations. -# -# - class Logic : (std_logic) one‑bit signal wire / port / register -# - class LogicVec : (std_logic_vector) Multi-bit signal. -# - class LogicArray : (Multiple std_logic) Array of individual std_logic signals. -# - class LogicVecArray : (Multiple std_logic_vector) Array of std_logic_vector signals. - -# -# std_logic bit -# - -from vhdl_gen.context import VHDLContext -from vhdl_gen.utils import * - - -class Logic: - """ - A one-bit VHDL std_logic signal. - - Logic class encapsulates wires, ports, and registers in the code generator, - handling name with '_i', '_o', '_q', '_d' suffixes. - - Attributes: - ctx (VHDLContext): Context for code generation. - name (str): The base name of the signal. - type (str): - 'i' input port (_i: in std_logic) - 'o' output port (_o: out std_logic) - 'w' internal wire (signal : std_logic) - 'r' register (_q) for the registered value - (_d) for the next-cycled value - - Methods: - getNameRead(): Returns the name we should use when reading the signal. (e.g. _q for a register type) - getNameWrite(): Returns the name to write to. (e.g. _d for a register type) - signalInit(): Appends the VHDL signal/port declaration. - regInit(): Appends the VHDL register initialization block. - """ - - # Signal name - name = '' - # Signal type, 'i' for input, 'o' for output, 'w' for wire, 'r' for register - type = '' - - def __init__(self, ctx: VHDLContext, name: str, type: str = 'w', init: bool = True) -> None: - """ - init: If True, immediately generates the corresponding std_logic in VHDL. - True when we instantiate Logic. - False when we instantiate LogicVec, LogicArray, and LogicVecArray. - """ - # Type should be one of the four types. - assert (type in ('i', 'o', 'w', 'r')) - self.ctx = ctx - self.name = name - self.type = type - if (init): - self.signalInit() - - def __repr__(self) -> str: - """ - Print Logic with useful information. - """ - # Signal type - type = '' - if (self.type == 'w'): - type = 'wire' - elif (self.type == 'i'): - type = 'input' - elif (self.type == 'o'): - type = 'output' - elif (self.type == 'r'): - type = 'reg' - return f'name: {self.name}\n' + f'type: {type}\n' + f'size: single bit\n' - - def getNameRead(self, sufix='') -> str: - """ - Returns the name we should use when reading the signal. - - Example (Pseudo-code) - If you want to do "Logic a = Logic b + Logic c" - -> getNameWrite(a) = getNameRead(b) + getNameRead(c) - """ - if (self.type == 'w'): - return self.name + sufix - elif (self.type == 'r'): - return self.name + sufix + '_q' - elif (self.type == 'i'): - return self.name + sufix + '_i' - elif (self.type == 'o'): - raise TypeError(f'Cannot read from the output signal \"{self.name}\"!') - - def getNameWrite(self, sufix='') -> str: - """ - Returns the name to write to. - - Example in the getNameRead() method. - """ - if (self.type == 'w'): - return self.name + sufix - elif (self.type == 'r'): - return self.name + sufix + '_d' - elif (self.type == 'i'): - raise TypeError(f'Cannot write to the input signal \"{self.name}\"!') - elif (self.type == 'o'): - return self.name + sufix + '_o' - - def signalInit(self, sufix='') -> None: - """ - Appends the appropriate declaration or port line for this signal to a global buffer. - """ - if (self.type == 'w'): - self.ctx.add_signal_str(f'\tsignal {self.name + sufix} : std_logic;\n') - elif (self.type == 'r'): - self.ctx.add_signal_str(f'\tsignal {self.name + sufix}_d : std_logic;\n') - self.ctx.add_signal_str(f'\tsignal {self.name + sufix}_q : std_logic;\n') - elif (self.type == 'i'): - self.ctx.add_port_str(';\n') - self.ctx.add_port_str(f'\t\t{self.name + sufix}_i : in std_logic') - elif (self.type == 'o'): - self.ctx.add_port_str(';\n') - self.ctx.add_port_str(f'\t\t{self.name + sufix}_o : out std_logic') - - def regInit(self, enable=None, init=None) -> None: - """ - Generates a clocked process snippet that sets up the register's behavior. - For example, - - if (rst = '1') then - _q <= '0'; - elsif (rising_edge(clk)) then - _q <= _d; - end if; - """ - assert (self.type == 'r') - if (init != None): - self.ctx.add_reg_str('\t\tif (rst = \'1\') then\n') - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead()} <= {IntToBits(init)};\n') - self.ctx.add_reg_str('\t\telsif (rising_edge(clk)) then\n') - else: - self.ctx.add_reg_str('\t\tif (rising_edge(clk)) then\n') - if (enable != None): - self.ctx.add_reg_str(f'\t\t\tif ({enable.getNameRead()} = \'1\') then\n') - self.ctx.add_reg_str(f'\t\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n') - self.ctx.add_reg_str('\t\t\tend if;\n') - else: - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n') - self.ctx.add_reg_str('\t\tend if;\n') - -# -# std_logic_vec -# - - -class LogicVec(Logic): - """ - Like 'class Logic', but for M-bit vectors. - - Inherits all methods and suffix rules of Logic in default. - Additionally, it has additional features. - - Attributes: - size (int): bit-width of vector (M) - - Methods: - Indexable reads/writes of LogicVec components - Access a certain i-th bit of LogicVec via getNameRead(i), getNameWrite(i) - - LogicVec (size=3) : "101" - LogicArray (length=3): [1, - 0, - 1] - LogicVecArray (size=3, length=2): [101, - 010] - """ - # Signal name - name = '' - # Signal type, 'i' for input, 'o' for output, 'w' for wire, 'r' for register - type = '' - size = 1 - - def __init__(self, ctx: VHDLContext, name: str, type: str = 'w', size: int = 1, init: bool = True) -> None: - Logic.__init__(self, ctx, name, type, False) - assert (size > 0) - self.size = size - if (init): - self.signalInit() - - def __repr__(self) -> str: - # Signal type - type = '' - if (self.type == 'w'): - type = 'wire' - elif (self.type == 'i'): - type = 'input' - elif (self.type == 'o'): - type = 'output' - elif (self.type == 'r'): - type = 'reg' - return f'name: {self.name}\n' + f'type: {type}\n' + f'size: {self.size}\n' - - def getNameRead(self, i=None, sufix='') -> str: - if (i == None): - return Logic.getNameRead(self, sufix) - else: - assert (i < self.size) - return Logic.getNameRead(self, sufix) + f'({i})' - - def getNameWrite(self, i=None, sufix='') -> str: - if (i == None): - return Logic.getNameWrite(self, sufix) - else: - assert (i < self.size) - return Logic.getNameWrite(self, sufix) + f'({i})' - - def signalInit(self, sufix=''): - if (self.type == 'w'): - self.ctx.add_signal_str(f'\tsignal {self.name + sufix} : std_logic_vector({self.size-1} downto 0);\n') - elif (self.type == 'r'): - self.ctx.add_signal_str(f'\tsignal {self.name + sufix}_d : std_logic_vector({self.size-1} downto 0);\n') - self.ctx.add_signal_str(f'\tsignal {self.name + sufix}_q : std_logic_vector({self.size-1} downto 0);\n') - elif (self.type == 'i'): - self.ctx.add_port_str(';\n') - self.ctx.add_port_str(f'\t\t{self.name + sufix}_i : in std_logic_vector({self.size-1} downto 0)') - elif (self.type == 'o'): - self.ctx.add_port_str(';\n') - self.ctx.add_port_str(f'\t\t{self.name + sufix}_o : out std_logic_vector({self.size-1} downto 0)') - - def regInit(self, enable=None, init=None) -> None: - assert (self.type == 'r') - if (init != None): - self.ctx.add_reg_str('\t\tif (rst = \'1\') then\n') - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead()} <= {IntToBits(init, self.size)};\n') - self.ctx.add_reg_str('\t\telsif (rising_edge(clk)) then\n') - else: - self.ctx.add_reg_str('\t\tif (rising_edge(clk)) then\n') - if (enable != None): - self.ctx.add_reg_str(f'\t\t\tif ({enable.getNameRead()} = \'1\') then\n') - self.ctx.add_reg_str(f'\t\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n') - self.ctx.add_reg_str('\t\t\tend if;\n') - else: - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n') - self.ctx.add_reg_str('\t\tend if;\n') - -# -# An array of std_logic -# - - -class LogicArray(Logic): - """ - Represents a N-length array of one-bit VHDL std_logic. - Generates total of N one-bit std_logic. - - Each element (total N) is generated as a separate Logic(name + f'_{i}', type) - For example, - signal _0 : std_logic; - signal _1 : std_logic; - ... - signal _{N-1} : std_logic; - - Attributes: - length (int): number of elements in the array. - - Methods: - Indexable reads/writes of LogicArray components - Access a certain i-th element of LogicArray via getNameRead(i), getNameWrite(i) - """ - length = 1 - - def __init__(self, ctx: VHDLContext, name: str, type: str = 'w', length: int = 1): - self.length = length - Logic.__init__(self, ctx, name, type, False) - self.signalInit() - - def __repr__(self) -> str: - return Logic.__repr__(self) + f'array length: {self.length}' - - def getNameRead(self, i) -> str: - assert i in range(0, self.length) - return Logic.getNameRead(self, f'_{i}') - - def getNameWrite(self, i) -> str: - assert i in range(0, self.length) - return Logic.getNameWrite(self, f'_{i}') - - def signalInit(self) -> None: - for i in range(0, self.length): - Logic.signalInit(self, f'_{i}') - - def __getitem__(self, i) -> Logic: - assert i in range(0, self.length) - return Logic(self.ctx, self.name + f'_{i}', self.type, False) - - def regInit(self, enable=None, init=None) -> None: - assert (self.type == 'r') - if (init != None): - self.ctx.add_reg_str('\t\tif (rst = \'1\') then\n') - for i in range(0, self.length): - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead(i)} <= {IntToBits(init[i])};\n') - self.ctx.add_reg_str('\t\telsif (rising_edge(clk)) then\n') - else: - self.ctx.add_reg_str('\t\tif (rising_edge(clk)) then\n') - if (enable != None): - for i in range(0, self.length): - self.ctx.add_reg_str(f'\t\t\tif ({enable.getNameRead(i)} = \'1\') then\n') - self.ctx.add_reg_str(f'\t\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n') - self.ctx.add_reg_str('\t\t\tend if;\n') - else: - for i in range(0, self.length): - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n') - self.ctx.add_reg_str('\t\tend if;\n') - -# -# An array of std_logic vector -# - - -class LogicVecArray(LogicVec): - """ - Represents a N-length array of M-bit VHDL std_logic_vec. - Generates total of N M-bit std_logic_vec. - - Each element (total N) is generated as a separate LogicVec - For example, - signal _0 : std_logic_vector(M-1 downto 0); - signal _1 : std_logic_vector(M-1 downto 0); - … - signal _{N-1} : std_logic_vector(M-1 downto 0); - - Attributes: - length (int): number of entries (N). - size (int): bit-width of each vector (M). - - Methods: - Indexable reads/writes of LogicVecArray components - Access a certain i-th LogicVec of LogicVecArray via getNameRead(i), getNameWrite(i) - """ - length = 1 - - def __init__(self, ctx: VHDLContext, name: str, type: str = 'w', length: int = 1, size: int = 1): - self.length = length - LogicVec.__init__(self, ctx, name, type, size, False) - self.signalInit() - - def __repr__(self) -> str: - return LogicVec.__repr__(self) + f'array length: {self.length}' - - def getNameRead(self, i, j=None) -> str: - assert i in range(0, self.length) - return LogicVec.getNameRead(self, j, f'_{i}') - - def getNameWrite(self, i, j=None) -> str: - assert i in range(0, self.length) - return LogicVec.getNameWrite(self, j, f'_{i}') - - def signalInit(self) -> None: - for i in range(0, self.length): - LogicVec.signalInit(self, f'_{i}') - - def __getitem__(self, i) -> LogicVec: - assert i in range(0, self.length) - return LogicVec(self.ctx, self.name + f'_{i}', self.type, self.size, False) - - def regInit(self, enable=None, init=None) -> None: - assert (self.type == 'r') - if (init != None): - self.ctx.add_reg_str('\t\tif (rst = \'1\') then\n') - for i in range(0, self.length): - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead(i)} <= {IntToBits(init[i], self.size)};\n') - self.ctx.add_reg_str('\t\telsif (rising_edge(clk)) then\n') - else: - self.ctx.add_reg_str('\t\tif (rising_edge(clk)) then\n') - if (enable != None): - for i in range(0, self.length): - self.ctx.add_reg_str(f'\t\t\tif ({enable.getNameRead(i)} = \'1\') then\n') - self.ctx.add_reg_str(f'\t\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n') - self.ctx.add_reg_str('\t\t\tend if;\n') - else: - for i in range(0, self.length): - self.ctx.add_reg_str(f'\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n') - self.ctx.add_reg_str('\t\tend if;\n') diff --git a/tools/backend/lsq-generator-python/vhdl_gen/utils.py b/tools/backend/lsq-generator-python/vhdl_gen/utils.py deleted file mode 100644 index aa8ad4a77c..0000000000 --- a/tools/backend/lsq-generator-python/vhdl_gen/utils.py +++ /dev/null @@ -1,460 +0,0 @@ -# -# Dynamatic is under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -# - -# This file redesigned the classes to represent the signals in the VHDL file -import re -import math - -# ===----------------------------------------------------------------------===# -# VHDL Signal Type Definition -# ===----------------------------------------------------------------------===# - -# -# std_logic: a single bit -# - - -class VHDLLogicType: - """The functionality of this class is similar to Class Logic - Instead of storing the string in a global variable, - this class now directly return the generated string - """ - - def __init__(self, name: str, type: str = "w"): - # Type must be one of the following 4 letters - assert type in ("i", "o", "w", "r") - self.name = name - self.type = type - - def __repr__(self) -> str: - # Signal type - type = "" - if self.type == "w": - type = "wire" - elif self.type == "i": - type = "input" - elif self.type == "o": - type = "output" - elif self.type == "r": - type = "reg" - return f"name: {self.name}\n" + f"type: {type}\n" + f"size: single bit\n" - - def getNameRead(self, suffix="") -> str: - if self.type == "w": - return self.name + suffix - elif self.type == "r": - return self.name + suffix + "_q" - elif self.type == "i": - return self.name + suffix - elif self.type == "o": - raise TypeError(f'Cannot read from the output signal "{self.name}"!') - - def getNameWrite(self, suffix="") -> str: - if self.type == "w": - return self.name + suffix - elif self.type == "r": - return self.name + suffix + "_d" - elif self.type == "i": - raise TypeError(f'Cannot write to the input signal "{self.name}"!') - elif self.type == "o": - return self.name + suffix - - def signalInit(self, suffix: str = ""): - signal_str = "" - - # Change the name of the signal to match the naming convention - # in Dynamatic - if suffix != "": - name_list = self.name.split("_") - - if suffix == "0": - new_name_list = name_list[:-1] + [suffix, name_list[-1]] - else: - new_name_list = name_list[:-2] + [suffix, name_list[-1]] - - self.name = "_".join(new_name_list) - - # Check the signal type - if self.type == "w": - signal_str += f"\tsignal {self.name} : std_logic;\n" - elif self.type == "r": - signal_str += f"\tsignal {self.name}_d : std_logic;\n" - signal_str += f"\tsignal {self.name}_q : std_logic;\n" - elif self.type == "i": - signal_str += ";\n" - signal_str += f"\t\t{self.name} : in std_logic" - elif self.type == "o": - signal_str += ";\n" - signal_str += f"\t\t{self.name} : out std_logic" - - return signal_str - - def regInit(self, enable=None, init=None): - assert self.type == "r" - - reg_init_str = "" - - # Process Content init - if init != None: - reg_init_str += "\t\tif (rst = '1') then\n" - reg_init_str += f"\t\t\t{self.getNameRead()} <= {IntToBits(init)};\n" - reg_init_str += "\t\telsif (rising_edge(clk)) then\n" - else: - reg_init_str += "\t\tif (rising_edge(clk)) then\n" - - if enable != None: - reg_init_str += f"\t\t\tif ({enable.getNameRead()} = '1') then\n" - reg_init_str += f"\t\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n" - reg_init_str += "\t\t\tend if;\n" - else: - reg_init_str += f"\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n" - - reg_init_str += "\t\tend if;\n" - - return reg_init_str - - -# -# std_logic_vec -# - - -class VHDLLogicVecType(VHDLLogicType): - """The functionality of this class is similar to Class LogicVec - Instead of storing the string in a global variable, - this class now directly return the generated string - - The port initialization is removed from the class init function - """ - - def __init__(self, name: str, type: str = "w", size: int = 1): - VHDLLogicType.__init__(self, name, type) - - # The size must be larger than 0 - assert size > 0 - self.size = size - - def __repr__(self) -> str: - # Signal type - type = "" - if self.type == "w": - type = "wire" - elif self.type == "i": - type = "input" - elif self.type == "o": - type = "output" - elif self.type == "r": - type = "reg" - return f"name: {self.name}\n" + f"type: {type}\n" + f"size: {self.size}\n" - - def getNameRead(self, i=None, suffix="") -> str: - if i == None: - return VHDLLogicType.getNameRead(self, suffix) - else: - assert i < self.size - return VHDLLogicType.getNameRead(self, suffix) + f"({i})" - - def getNameWrite(self, i=None, suffix="") -> str: - if i == None: - return VHDLLogicType.getNameWrite(self, suffix) - else: - assert i < self.size - return VHDLLogicType.getNameWrite(self, suffix) + f"({i})" - - def signalInit(self, suffix=""): - signal_str = "" - - # Change the name of the signal to match the naming convention - # in Dynamatic - if suffix != "": - name_list = self.name.split("_") - if suffix == "0": - new_name_list = name_list[:-1] + [suffix, name_list[-1]] - else: - new_name_list = name_list[:-2] + [suffix, name_list[-1]] - - self.name = "_".join(new_name_list) - - if self.type == "w": - signal_str += ( - f"\tsignal {self.name} : std_logic_vector({self.size - 1} downto 0);\n" - ) - elif self.type == "r": - signal_str += ( - f"\tsignal {self.name}_d : std_logic_vector({self.size - 1} downto 0);\n" - ) - signal_str += ( - f"\tsignal {self.name}_q : std_logic_vector({self.size - 1} downto 0);\n" - ) - elif self.type == "i": - # For the wrapper, we don't add i/o in the port name - signal_str += ";\n" - signal_str += ( - f"\t\t{self.name} : in std_logic_vector({self.size - 1} downto 0)" - ) - elif self.type == "o": - # For the wrapper, we don't add i/o in the port name - signal_str += ";\n" - signal_str += ( - f"\t\t{self.name} : out std_logic_vector({self.size - 1} downto 0)" - ) - - return signal_str - - def regInit(self, enable=None, init=None): - reg_str = "" - assert self.type == "r" - if init != None: - reg_str += "\t\tif (rst = '1') then\n" - reg_str += f"\t\t\t{self.getNameRead()} <= {IntToBits(init, self.size)};\n" - reg_str += "\t\telsif (rising_edge(clk)) then\n" - else: - reg_str += "\t\tif (rising_edge(clk)) then\n" - - if enable != None: - reg_str += f"\t\t\tif ({enable.getNameRead()} = '1') then\n" - reg_str += f"\t\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n" - reg_str += "\t\t\tend if;\n" - else: - reg_str += f"\t\t\t{self.getNameRead()} <= {self.getNameWrite()};\n" - reg_init_str += "\t\tend if;\n" - - return reg_str - - -# -# An array of std_logic -# - - -class VHDLLogicTypeArray(VHDLLogicType): - """An array of std_logic""" - - def __init__(self, name: str, type: str = "w", length: int = 1): - VHDLLogicType.__init__(self, name, type) - self.length = length - - def __repr__(self) -> str: - return VHDLLogicType.__repr__(self) + f"array length: {self.length}" - - def getNameRead(self, i) -> str: - assert i in range(0, self.length) - return VHDLLogicType.getNameRead(self) - - def getNameWrite(self, i) -> str: - assert i in range(0, self.length) - return VHDLLogicType.getNameWrite(self) - - def signalInit(self): - """We return all the definitions as a string""" - signals_str = "" - - for i in range(0, self.length): - signals_str += VHDLLogicType.signalInit(self, f"{i}") - - return signals_str - - def __getitem__(self, i) -> VHDLLogicType: - assert i in range(0, self.length) - name = re.sub( - r"(\D+)\d+(\D+)", lambda m: f"{m.group(1)}{i}{m.group(2)}", self.name - ) - return VHDLLogicType(name, self.type) - - def regInit(self, enable=None, init=None): - assert self.type == "r" - - # Define the output string - reg_init_str = "" - - if init != None: - reg_init_str += "\t\tif (rst = '1') then\n" - for i in range(0, self.length): - reg_init_str += ( - f"\t\t\t{self.getNameRead(i)} <= {IntToBits(init[i])};\n" - ) - reg_init_str += "\t\telsif (rising_edge(clk)) then\n" - else: - reg_init_str += "\t\tif (rising_edge(clk)) then\n" - - if enable != None: - for i in range(0, self.length): - reg_init_str += f"\t\t\tif ({enable.getNameRead(i)} = '1') then\n" - reg_init_str += ( - f"\t\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n" - ) - reg_init_str += "\t\t\tend if;\n" - else: - for i in range(0, self.length): - reg_init_str += ( - f"\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n" - ) - - reg_init_str += "\t\tend if;\n" - - return reg_init_str - - -# -# An array of std_logic vector -# - - -class VHDLLogicVecTypeArray(VHDLLogicVecType): - """An array of std_logic vector""" - - def __init__(self, name: str, type: str = "w", length: int = 1, size: int = 1): - self.length = length - VHDLLogicVecType.__init__(self, name, type, size) - - def __repr__(self) -> str: - return VHDLLogicVecType.__repr__(self) + f"array length: {self.length}" - - def getNameRead(self, i, j=None) -> str: - assert i in range(0, self.length) - return VHDLLogicVecType.getNameRead(self, i) - - def getNameWrite(self, i, j=None) -> str: - assert i in range(0, self.length) - return VHDLLogicVecType.getNameWrite(self, j) - - def __getitem__(self, i) -> VHDLLogicVecType: - assert i in range(0, self.length) - name = re.sub( - r"(\D+)\d+(\D+)", lambda m: f"{m.group(1)}{i}{m.group(2)}", self.name - ) - return VHDLLogicVecType(name, self.type, self.size) - - def signalInit(self): - sig_init_str = "" - - for i in range(0, self.length): - sig_init_str += VHDLLogicVecType.signalInit(self, f"{i}") - - return sig_init_str - - def regInit(self, enable=None, init=None): - # Define the output string - reg_init_str = "" - - assert self.type == "r" - - if init != None: - reg_init_str += "\t\tif (rst = '1') then\n" - for i in range(0, self.length): - reg_init_str += ( - f"\t\t\t{self.getNameRead(i)} <= {IntToBits(init[i], self.size)};\n" - ) - reg_init_str += "\t\telsif (rising_edge(clk)) then\n" - else: - reg_init_str += "\t\tif (rising_edge(clk)) then\n" - - if enable != None: - for i in range(0, self.length): - reg_init_str += f"\t\t\tif ({enable.getNameRead(i)} = '1') then\n" - reg_init_str += ( - f"\t\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n" - ) - reg_init_str += "\t\t\tend if;\n" - else: - for i in range(0, self.length): - reg_init_str += ( - f"\t\t\t{self.getNameRead(i)} <= {self.getNameWrite(i)};\n" - ) - - reg_init_str += "\t\tend if;\n" - - return reg_init_str - - -def OpTab(out, tabLevel, *list_in) -> str: - if type(out) == tuple: - if len(out) == 2: - str_ret = "\t" * tabLevel + f"{out[0].getNameWrite(out[1])} <=" - else: - str_ret = "\t" * tabLevel + f"{out[0].getNameWrite(out[1], out[2])} <=" - else: - str_ret = "\t" * tabLevel + f"{out.getNameWrite()} <=" - if type(out) == VHDLLogicType: - size = 1 - else: - size = out.size - for arg in list_in: - if type(arg) == str: - str_ret += " " + arg - elif type(arg) == int: - str_ret += " " + IntToBits(arg, size) - elif type(arg) == tuple: - if type(arg[0]) == int: - str_ret += " " + IntToBits(arg[0], arg[1]) - elif len(arg) == 2: - str_ret += " " + arg[0].getNameRead(arg[1]) - else: - str_ret += " " + arg[0].getNameRead(arg[1], arg[2]) - else: - str_ret += " " + arg.getNameRead() - str_ret += ";\n" - return str_ret - - -# ===----------------------------------------------------------------------===# -# Helper Function -# ===----------------------------------------------------------------------===# - - -def MaskLess(din, size) -> str: - """ - Example: - MaskLess(3, 5) # Output: "00111" - MaskLess(2, 6) # Output: "000011" - MaskLess(5, 5) # Output: "11111" - MaskLess(0, 4) # Output: "0000" - """ - if (din > size): - raise ValueError("Unknown value!") - return '\"' + '0'*(size-din) + '1'*din + '\"' - - -def IntToBits(din, size=None) -> str: - if size == None: - if din: - return "'1'" - else: - return "'0'" - else: - str_ret = '"' - for i in range(0, size): - if din % 2 == 0: - str_ret = "0" + str_ret - else: - str_ret = "1" + str_ret - din = din // 2 - str_ret = '"' + str_ret - if din != 0: - raise ValueError("Unknown value!") - return str_ret - - -def Zero(size) -> str: - if size == None: - return "'0'" - else: - return '"' + "0" * size + '"' - - -def GetValue(row, i) -> int: - if len(row) > i: - return row[i] - else: - return 0 - - -def isPow2(value: int) -> bool: - return (value & (value-1) == 0) and value != 0 - - -def log2Ceil(value: int) -> int: - return math.ceil(math.log2(value))