# Copyright 2024 Thales DIS France SAS # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Original Author: Oukalrazqou Abdessamii """Module is used to gather all utils and function to generate the csr and isa documents""" import io import os import re import yaml from yaml import BaseLoader import rstcloth import json from mako.template import Template import libs.isa_updater import libs.csr_updater import libs.spike_updater import libs.csr_factorizer from rstcloth import RstCloth from mdutils.mdutils import MdUtils from libs.isa_updater import isa_filter from libs.csr_updater import csr_formatter from libs.spike_updater import spike_formatter from libs.csr_factorizer import factorizer pattern_warl = r"\b(?:warl|wlrl|ro_constant|ro_variable|rw|ro)\b" # pattern to detect warl in field pattern_legal_dict = r"\[(0x[0-9A-Fa-f]+)(.*?(0x[0-9A-Fa-f]+))?\]" # pattern to detect if warl field is dict pattern_legal_list = r"\[(0x[0-9A-Fa-f]+)(.*?(0x[0-9A-Fa-f]+))?\]" # pattern to detect if warl field is a list Factorizer_pattern = r"\d+" # pattern to detect factorized fields class DocumentClass: """Document class""" def __init__(self, name): self.name = name self.memoryMapList = [] def addMemoryMap(self, memoryMap): self.memoryMapList.append(memoryMap) class MemoryMapClass: """memory map class""" def __init__(self, name): self.name = name self.addressBlockList = [] def addAddressBlock(self, addressBlock): self.addressBlockList.append(addressBlock) class AddressBlockClass: """address block class""" def __init__(self, name): self.name = name self.registerList = [] self.suffix = "" def addRegister(self, reg): assert isinstance(reg, RegisterClass) self.registerList.append(reg) def setRegisterList(self, registerList): self.registerList = registerList def returnAsString(self): raise NotImplementedError( "method returnAsString() is virtual and must be overridden." ) class RegisterClass: """register class""" def __init__( self, name, address, resetValue, size, access, desc, RV32, RV64, field ): self.name = name self.address = address self.resetValue = resetValue self.size = size self.access = access self.desc = desc self.RV32 = RV32 self.RV64 = RV64 self.field = field class Field: """field class""" def __init__( self, name, bitlegal, fieldreset, bitmsb, bitlsb, bitWidth, fieldDesc, fieldaccess, andMask=None, orMask=None, ): self.name = name self.bitlegal = bitlegal self.fieldreset = fieldreset self.bitmsb = bitmsb self.bitlsb = bitlsb self.bitWidth = bitWidth self.fieldDesc = fieldDesc self.fieldaccess = fieldaccess self.andMask = andMask self.orMask = orMask # --------------------------------------------------------------# class Render: """Collection of general rendering methods which can be overridden if needed for a specific output format.""" @staticmethod def is_decimal(value): """return a bool checking if value is decimal""" try: int( value ) # Alternatively, use float(value) if you want to check for floating point numbers return True except ValueError: return False @staticmethod def range(start, end): """Return a string representing the range START..END, inclusive. START and END are strings representing numerical values.""" if Render.is_decimal(start): start = hex(int(start)) if Render.is_decimal(end): end = hex(int(end)) return f"{start} - {end}" @staticmethod def value_set(values): """Return a string representing the set of values in VALUES. VALUES is a list of strings.""" # values = [hex(int(value, 16)) if '0x' in value and '-' not in value else value for value in values] return ", ".join(values) @staticmethod def bitmask(andMask, orMask): """Return a string representing the masking pattern defined by ANDMASK and ORMASK. ANDMASK and ORMASK are strings representing AND and OR mask values, respectively. """ return f"masked: & {andMask} | {orMask}" @staticmethod def fieldtype(typ): """Return a string representing the printable type of a register field.""" upcased = typ.upper() if upcased == "RO_CONSTANT": return "ROCST" elif upcased == "RO_VARIABLE": return "ROVAR" else: return upcased class CoreConfig: def __init__( self, isa, marchid, misa_we, misa_we_enable, misaligned, mmu_mode, mvendorid, pmpaddr0, pmpcfg0, pmpregions, priv, status_fs_field_we, status_fs_field_we_enable, status_vs_field_we, status_vs_field_we_enable, ): self.isa = isa self.marchid = marchid self.misa_we = misa_we self.misa_we_enable = misa_we_enable self.misaligned = misaligned self.mmu_mode = mmu_mode self.mvendorid = mvendorid self.pmpaddr0 = pmpaddr0 self.pmpcfg0 = pmpcfg0 self.pmpregions = pmpregions self.priv = priv self.status_fs_field_we = status_fs_field_we self.status_fs_field_we_enable = status_fs_field_we_enable self.status_vs_field_we = status_vs_field_we self.status_vs_field_we_enable = status_vs_field_we_enable class Spike: def __init__( self, bootrom, bootrom_base, bootrom_size, dram, dram_base, dram_size, generic_core_config, max_steps, max_steps_enabled, isa, priv, core_configs, ): self.bootrom = bootrom self.bootrom_base = bootrom_base self.bootrom_size = bootrom_size self.dram = dram self.dram_base = dram_base self.dram_size = dram_size self.generic_core_config = generic_core_config self.max_steps = max_steps self.max_steps_enabled = max_steps_enabled self.isa = isa self.priv = priv self.core_configs = core_configs # --------------------------------------------------------------# class ISAdocumentClass: """ISA document class""" def __init__(self, name): self.name = name self.instructions = [] def addInstructionMapBlock(self, InstructionMap): self.instructions.append(InstructionMap) class InstructionMapClass: """ISA instruction map c.2n lass""" def __init__(self, name): self.name = name self.InstructionBlockList = [] def addInstructionBlock(self, InstructionBlock): self.InstructionBlockList.append(InstructionBlock) class Instruction: """ISA instruction class""" def __init__( self, key, Extension_Name, descr, OperationName, Name, Format, Description, pseudocode, invalid_values, exception_raised, ): self.key = key self.Extension_Name = Extension_Name self.descr = descr self.OperationName = OperationName self.Name = Name self.Format = Format self.Description = Description self.invalid_values = invalid_values self.pseudocode = pseudocode self.exception_raised = exception_raised class InstructionBlockClass: """ISA instruction block class""" def __init__(self, name): self.name = name self.Instructionlist = [] self.suffix = "" def addInstruction(self, Inst): assert isinstance(Inst, Instruction) self.Instructionlist.append(Inst) def setInstructionList(self, Instructionlist): self.Instructionlist = Instructionlist def returnAsString(self): raise NotImplementedError( "method returnAsString() is virtual and must be overridden." ) class RstAddressBlock(AddressBlockClass): """Generates a ReStructuredText file from a IP-XACT register description""" def __init__(self, name): super().__init__("csr") self.name = name self.registerList = [] self.suffix = ".rst" def sort_address(self): for reg in self.registerList: if "-" in reg.address: start, end = reg.address.split("-") return int(start, 16), int(end, 16) return int(reg.address, 16), int(reg.address, 16) def get_access_privilege(self, reg): """Registers with address bits [11:10] == 2'b11 are Read-Only as per privileged ISA spec.""" # Handle register address ranges separated by dashes. if (int(reg.address.split("-")[0], 0) & 0xC00) == 0xC00: return "RO" else: return "RW" def returnAsString(self): registerlist = sorted(self.registerList, key=lambda reg: reg.address) r = RstCloth(io.StringIO()) # with default parameter, sys.stdout is used regNameList = [reg.name.upper() for reg in registerlist] regAddressList = [reg.address for reg in registerlist] regPrivModeList = [reg.access for reg in registerlist] regPrivAccessList = [self.get_access_privilege(reg) for reg in registerlist] regDescrList = [reg.desc for reg in registerlist] regRV32List = [reg.RV32 for reg in registerlist] regRV64List = [reg.RV64 for reg in registerlist] r.directive( "..", content=[ "Copyright (c) 2024 OpenHW Group", "Copyright (c) 2024 Thales", "SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1", "Author: Abdessamii Oukalrazqou", ], ) r.title(self.name) # Use the name of the addressBlock as title r.newline() r.h2("Conventions") r.newline() r.content( """In the subsequent sections, register fields are labeled with one of the following abbreviations: """ ) r.newline() r.li( """WPRI (Writes Preserve Values, Reads Ignore Values): read/write field reserved for future use. For forward compatibility, implementations that do not furnish these fields must make them read-only zero.""" ) r.li( """WLRL (Write/Read Only Legal Values): read/write CSR field that specifies behavior for only a subset of possible bit encodings, with other bit encodings reserved.""" ) r.li( """WARL (Write Any Values, Reads Legal Values): read/write CSR fields which are only defined for a subset of bit encodings, but allow any value to be written while guaranteeing to return a legal value whenever read.""" ) r.li( """ROCST (Read-Only Constant): A special case of WARL field which admits only one legal value, and therefore, behaves as a constant field that silently ignores writes.""" ) r.li( """ROVAR (Read-Only Variable): A special case of WARL field which can take multiple legal values but cannot be modified by software and depends only on the architectural state of the hart.""" ) r.newline() r.content( """In particular, a register that is not internally divided into multiple fields can be considered as containing a single field of XLEN bits. This allows to clearly represent read-write registers holding a single legal value (typically zero).""" ) r.newline() r.h2("Register Summary") summary_table = [] for i, _ in enumerate(regNameList): if regRV32List[i] | regRV64List[i]: summary_table.append( [ regAddressList[i], f"`{regNameList[i]} <#{regNameList[i]}>`_", # RW or RO privileges are set in the official RISC-V specification # and are encoded in bits [11:10] of the reg's address (2'b11 == "RO"). # See Tables 4 through 8 in section 2.2 of the Priv spec v20240411. regPrivModeList[i] + regPrivAccessList[i], str(regDescrList[i]), ] ) r.table( header=["Address", "Register Name", "Privilege", "Description"], data=summary_table, ) r.h2("Register Description") for reg in registerlist: if reg.RV32 | reg.RV64: reg_table = [] r.newline() r.directive(".. _" + reg.name.upper() + ":") r.h3(reg.name.upper()) r.newline() r.field("Address", (reg.address)) if reg.resetValue: # display the resetvalue in hex notation in the full length of the register r.field( "Reset Value", "0x" + f"{reg.resetValue[2:].zfill(int(reg.size/4))}", ) # RO/RW privileges are encoded in register address. r.field("Privilege", reg.access + self.get_access_privilege(reg)) r.field("Description", reg.desc) for field in reg.field: if field.bitWidth == 1: # only one bit -> no range needed bits = f"{field.bitlsb}" else: bits = f"[{field.bitmsb}:{field.bitlsb}]" _line = [ bits, field.name.upper(), field.fieldreset, field.fieldaccess, field.bitlegal, ] _line.append(field.fieldDesc) reg_table.append(_line) _headers = ["Bits", "Field Name", "Reset Value", "Type", "Legal Values"] _headers.append("Description") reg_table = sorted( reg_table, key=lambda x: int(x[0].strip("[]").split(":")[0]) ) # table of the register r.table(header=_headers, data=reg_table) return r.data class InstrstBlock(InstructionBlockClass): """Generates a ISA ReStructuredText file from RISC-V Config Yaml register description""" def __init__(self, name): super().__init__("isa") self.name = name self.Instructionlist = [] self.suffix = ".rst" def returnAsString(self): r = rstcloth.RstCloth( io.StringIO() ) # with default parameter, sys.stdout is used InstrNameList = [reg.key for reg in self.Instructionlist] InstrDescrList = [reg.descr for reg in self.Instructionlist] InstrExtList = [reg.Extension_Name for reg in self.Instructionlist] r.directive( "..", content=[ "Copyright (c) 2024 OpenHW Group", "Copyright (c) 2024 Thales", "SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1", "Author: Abdessamii Oukalrazqou", ], ) r.title(self.name) # Use the name of the addressBlock as title r.newline() r.h2("Instructions") summary_table = [] for i, _ in enumerate(InstrNameList): summary_table.append( [ str(InstrExtList[i]), str(InstrNameList[i]) + "_", str(InstrDescrList[i]), ] ) r.table(header=["Subset Name", "Name", "Description"], data=summary_table) for reg in self.Instructionlist: reg_table = [] if len(reg.Name) > 0: _headers = [ "Name", "Format", "Pseudocode", "Invalid_values", "Exception_raised", "Description", "Op Name", ] r.h2(reg.key) r.newline() for fieldIndex in list(range(len(reg.Name))): _line = [ reg.Name[fieldIndex], reg.Format[fieldIndex], reg.pseudocode[fieldIndex], reg.invalid_values[fieldIndex], reg.exception_raised[fieldIndex], reg.Description[fieldIndex], ] _line.append(reg.OperationName[fieldIndex]) reg_table.append(_line) # table of the register r.table(header=_headers, data=reg_table) return r.data class AdocAddressBlock(AddressBlockClass): """Generates an AsciiDoc file from a IP-XACT register description""" def __init__(self, name): super().__init__("csr") self.name = name self.registerList = [] self.suffix = ".adoc" def get_access_privilege(self, reg): """Registers with address bits [11:10] == 2'b11 are Read-Only as per privileged ISA spec.""" # Handle register address ranges separated by dashes. if (int(reg.address.split("-")[0], 0) & 0xC00) == 0xC00: return "RO" else: return "RW" def generate_label(self, name): return "_" + name.replace('[','').replace(']','').upper() def returnAsString(self): registerlist = sorted(self.registerList, key=lambda reg: reg.address) r = "" regNameList = [reg.name.upper() for reg in registerlist] regAddressList = [reg.address for reg in registerlist] regPrivModeList = [reg.access for reg in registerlist] regPrivAccessList = [self.get_access_privilege(reg) for reg in registerlist] regDescrList = [reg.desc for reg in registerlist] regRV32List = [reg.RV32 for reg in registerlist] regRV64List = [reg.RV64 for reg in registerlist] r += "////\n" r += " Copyright (c) 2024 OpenHW Group\n" r += " Copyright (c) 2024 Thales\n" r += " SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1\n" r += " Author: Abdessamii Oukalrazqou\n" r += "////\n\n" r += "=== %s\n\n"%self.name r += "==== Conventions\n\n" r += "In the subsequent sections, register fields are labeled with one of the following abbreviations:\n\n" r += "* WPRI (Writes Preserve Values, Reads Ignore Values): read/write field reserved\n" r += "for future use. For forward compatibility, implementations that do not\n" r += "furnish these fields must make them read-only zero.\n" r += "* WLRL (Write/Read Only Legal Values): read/write CSR field that specifies\n" r += "behavior for only a subset of possible bit encodings, with other bit encodings\n" r += "reserved.\n" r += "* WARL (Write Any Values, Reads Legal Values): read/write CSR fields which are\n" r += "only defined for a subset of bit encodings, but allow any value to be written\n" r += "while guaranteeing to return a legal value whenever read.\n" r += "* ROCST (Read-Only Constant): A special case of WARL field which admits only one\n" r += "legal value, and therefore, behaves as a constant field that silently ignores\n" r += "writes.\n" r += "* ROVAR (Read-Only Variable): A special case of WARL field which can take\n" r += "multiple legal values but cannot be modified by software and depends only on\n" r += "the architectural state of the hart.\n\n" r += "In particular, a register that is not internally divided\n" r += "into multiple fields can be considered as containing a single field of XLEN bits.\n" r += "This allows to clearly represent read-write registers holding a single legal value\n" r += "(typically zero).\n\n" r += "==== Register Summary\n\n" r += "|===\n" r += "|Address | Register Name | Privilege | Description\n\n" for i, _ in enumerate(regNameList): if regRV32List[i] | regRV64List[i]: r += "|" + regAddressList[i] + \ f"| `<<{self.generate_label(regNameList[i])},{regNameList[i].upper()}>>`" + \ "|" + regPrivModeList[i] + regPrivAccessList[i] + \ "|" + str(regDescrList[i]) + "\n" r += "|===\n\n" r += "==== Register Description\n\n" for reg in registerlist: if reg.RV32 | reg.RV64: r += "[[%s]]\n"%self.generate_label(reg.name) r += "===== %s\n\n"%reg.name.upper() r += "Address:: %s\n"%reg.address if reg.resetValue: # display the resetvalue in hex notation in the full length of the register r += "Reset Value:: 0x%s\n"%f"{reg.resetValue[2:].zfill(int(reg.size/4))}" # RO/RW privileges are encoded in register address. r += "Privilege:: %s\n"%(reg.access + self.get_access_privilege(reg)) r += "Description:: %s\n\n"%(reg.desc) reg_table = [] for field in reg.field: if field.bitWidth == 1: # only one bit -> no range needed bits = f"{field.bitlsb}" else: bits = f"[{field.bitmsb}:{field.bitlsb}]" _line = [ bits, field.name.upper(), field.fieldreset, field.fieldaccess, ( Render.bitmask(field.andMask, field.orMask) if field.andMask and field.orMask else field.bitlegal ), ] _line.append(field.fieldDesc) reg_table.append(_line) reg_table = sorted( reg_table, key=lambda x: int(x[0].strip("[]").split(":")[0]) ) # table of the register r += "|===\n" r += "| Bits | Field Name | Reset Value | Type | Legal Values | Description\n\n" for reg in reg_table: for col in reg: if col == 'Reserved': col = "_Reserved_" r +="| %s "%col.replace('\n','').replace('|', '\|') r += "\n" r += "|===\n\n" return r class InstadocBlock(InstructionBlockClass): """Generates a ISA AsciiDoc file from RISC-V Config Yaml register description""" def __init__(self, name): super().__init__("isa") self.name = name self.Instructionlist = [] self.suffix = ".adoc" def returnAsString(self): r = "" InstrNameList = [reg.key for reg in self.Instructionlist] InstrDescrList = [reg.descr for reg in self.Instructionlist] InstrExtList = [reg.Extension_Name for reg in self.Instructionlist] r += "////\n" r += " Copyright (c) 2024 OpenHW Group\n" r += " Copyright (c) 2024 Thales\n" r += " SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1\n" r += " Author: Abdessamii Oukalrazqou\n" r += "////\n\n" r += "=== %s\n\n"%self.name r += "==== Instructions\n\n" r += "|===\n" r += "|Subset Name | Name | Description\n\n" for i, _ in enumerate(InstrNameList): r += "|%s | %s | %s\n"%(str(InstrExtList[i]), str(InstrNameList[i]), str(InstrDescrList[i]).replace('\n','')) r += "|===\n\n" for reg in self.Instructionlist: reg_table = [] if len(reg.Name) > 0: r += "==== %s\n\n"%reg.key r += "|===\n" r += "| Name | Format | Pseudocode|Invalid_values | Exception_raised | Description| Op Name\n\n" for fieldIndex in list(range(len(reg.Name))): _line = [ reg.Name[fieldIndex], reg.Format[fieldIndex], reg.pseudocode[fieldIndex].replace('|','\|'), reg.invalid_values[fieldIndex], reg.exception_raised[fieldIndex], reg.Description[fieldIndex], ] _line.append(reg.OperationName[fieldIndex]) for col in _line: r +="| %s "%col.replace('\n','') r += "\n" r += "|===\n\n" return r class InstmdBlock(InstructionBlockClass): """Generates an ISA Markdown file from a RISC Config Yaml register description""" def __init__(self, name): super().__init__("isa") self.name = name self.Instructionlist = [] self.suffix = ".md" self.mdFile = MdUtils(file_name="none", title="") def returnAsString(self): InstrNameList = [reg.key for reg in self.Instructionlist] InstrDescrList = [reg.descr for reg in self.Instructionlist] InstrExtList = [reg.Extension_Name for reg in self.Instructionlist] licence = [ "", ] for l in licence: self.mdFile.write(l + "\n") self.mdFile.new_header( level=1, title=self.name ) # Use the name of the addressBlock as title self.mdFile.new_paragraph() self.mdFile.new_header(level=2, title="Instructions") # summary header = ["Subset Name", "Name ", "Description"] rows = [] for i, _ in enumerate(InstrNameList): InstrDescrList[i] = str(InstrDescrList[i]).replace("\n", " ") rows.extend( [ str(InstrExtList[i]), f"[{InstrNameList[i]}](#{InstrNameList[i]})", str(InstrDescrList[i]), ] ) self.mdFile.new_table( columns=len(header), rows=len(InstrNameList) + 1, # header + data text=header + rows, text_align="left", ) # all registers for reg in self.Instructionlist: if len(reg.Name) > 0: headers = [ "Name", "Format", "Pseudocode", "Invalid_values", "Exception_raised", "Description", "Op Name", ] self.returnMdRegDesc(reg.key) reg_table = [] for fieldIndex in list(range(len(reg.Name))): reg_table.append(reg.Name[fieldIndex].ljust(15)) reg.Format[fieldIndex] = ( f"[{reg.Format[fieldIndex]}](#{reg.Format[fieldIndex]})" ) reg_table.append(reg.Format[fieldIndex]) reg.pseudocode[fieldIndex] = str( reg.pseudocode[fieldIndex] ).replace("\n", " ") reg_table.append(reg.pseudocode[fieldIndex]) reg_table.append(reg.invalid_values[fieldIndex]) reg.exception_raised[fieldIndex] = str( reg.exception_raised[fieldIndex] ).replace("\n", " ") reg_table.append(reg.exception_raised[fieldIndex].ljust(40)) reg.Description[fieldIndex] = str( reg.Description[fieldIndex] ).replace("\n", " ") reg_table.append(reg.Description[fieldIndex]) reg_table.append(reg.OperationName[fieldIndex]) self.mdFile.new_table( columns=len(headers), rows=len(reg.Description) + 1, text=headers + reg_table, text_align="left", ) return self.mdFile.file_data_text def returnMdRegDesc(self, name): self.mdFile.new_header(level=3, title=name) class MdAddressBlock(AddressBlockClass): """Generates a CSR Markdown file from a RISC Config Yaml register description""" def __init__(self, name): super().__init__("csr") self.name = name self.registerList = [] self.suffix = ".md" self.mdFile = MdUtils(file_name="none", title="") def parse_bits(self, bits): if ":" in bits: msb, lsb = map(int, bits.strip("[]").split(":")[0]) else: msb = lsb = int(bits.strip("[]").split(":")[0]) return msb, lsb def returnAsString(self): registerlist = sorted(self.registerList, key=lambda reg: reg.address) regNameList = [reg.name for reg in registerlist if reg.RV32 | reg.RV64] regAddressList = [reg.address for reg in registerlist if reg.RV32 | reg.RV64] regDescrList = [reg.desc for reg in registerlist if reg.RV32 | reg.RV64] regAccessList = [ self.get_access_privilege(reg) for reg in registerlist if reg.RV32 | reg.RV64 ] regPrivModeList = [reg.access for reg in registerlist if reg.RV32 | reg.RV64] licence = [ "", ] for l in licence: self.mdFile.write(l + "\n") self.mdFile.new_header( level=1, title=self.name ) # Use the name of the addressBlock as title self.mdFile.new_paragraph() self.mdFile.new_header(level=2, title="Registers Summary") self.mdFile.new_paragraph() # summary header = ["Address", "Register Name", "Privilege", "Description"] rows = [] for i, _ in enumerate(regNameList): regDescrList[i] = str(regDescrList[i]).replace("\n", " ") rows.extend( [ regAddressList[i], f"[{regNameList[i].upper()}](#{regNameList[i].upper()})", regPrivModeList[i] + regAccessList[i], str(regDescrList[i]), ] ) self.mdFile.new_table( columns=len(header), rows=len(regNameList) + 1, # header + data text=header + rows, text_align="left", ) # all registers self.mdFile.new_header(level=3, title="Registers Description") for reg in registerlist: if reg.RV64 | reg.RV32: self.returnMdRegDesc( reg.name, reg.address, reg.resetValue, reg.desc, reg.access + self.get_access_privilege(reg), ) reg_table = [] _line = [] for field in reg.field: if field.bitWidth == 1: # only one bit -> no range needed bits = f"{field.bitlsb}" else: bits = f"[{field.bitmsb}:{field.bitlsb}]" reg_table.append( [ bits, field.name.upper(), field.fieldreset, field.fieldaccess, field.bitlegal, field.fieldDesc, ] ) _headers = ["Bits", "Field Name", "Reset Value", "Type", "Legal Values"] _headers.append("Description") reg_table = sorted( reg_table, key=lambda x: int(x[0].strip("[]").split(":")[0]) ) reg_table_flattened = [ item for sublist in reg_table for item in sublist ] self.mdFile.new_table( columns=len(_headers), rows=len(reg_table) + 1, text=_headers + reg_table_flattened, text_align="left", ) return self.mdFile.file_data_text def returnMdRegDesc(self, name, address, resetValue, desc, access): self.mdFile.new_header(level=4, title=name.upper()) self.mdFile.new_line("---") self.mdFile.new_line("**Address** " + str(address)) if resetValue: # display the resetvalue in hex notation in the full length of the register self.mdFile.new_line("**Reset Value** " + resetValue) self.mdFile.new_line("**Privilege** " + access) self.mdFile.new_line("**Description** " + desc) # -----------------------------------------------------------------------------------------------------------------------# class CsrParser: """parse CSR RISC-V config yaml file""" def __init__(self, srcFile, customFile, debugfile, target, modiFile=None): self.srcFile = srcFile self.customFile = customFile self.debugfile = debugfile self.modiFile = modiFile self.target = target def returnRegister( self, regName, registerElem, regAddress, resetValue, size, access, regDesc, fields, RV32, RV64, ): fieldList = fields field = [] if len(fieldList) > 0: for item in fieldList: andMask = None orMask = None if not isinstance(item, list): fieldDesc = registerElem.get("rv32", "")[item].get( "description", "" ) bitWidth = ( int(registerElem.get("rv32", "")[item].get("msb", "")) - int(registerElem.get("rv32", "")[item].get("lsb", "")) + 1 ) bitmsb = int(registerElem.get("rv32", "")[item].get("msb", "")) bitlsb = int(registerElem.get("rv32", "")[item].get("lsb", "")) fieldreset = hex( int(resetValue, 16) >> (bitlsb) & ((1 << ((bitWidth))) - 1) ) fieldaccess = "" legal = registerElem.get("rv32", "")[item].get("type", None) implemented = registerElem.get("rv32", "")[item].get( "implemented", None ) if legal is None: if not implemented: bitlegal = "0x0" else: bitlegal = "" fieldaccess = "ROCST" fieldDesc = fieldDesc else: warl = re.findall(pattern_warl, str(legal.keys())) if warl: fieldaccess = Render.fieldtype(warl[0]) legal_2 = ( registerElem.get("rv32", "")[item] .get("type", None) .get(warl[0], None) ) if legal_2 is None: bitlegal = "No Legal values" else: if isinstance(legal_2, dict): # TODO: Need a pattern that supports arbitrary lists of values in matches(3). pattern = r"([\w\[\]:]+)\s*(\w+)\s*(\[\s*((?:0x)?[0-9A-Fa-f]+)\s*\D+\s*(?:((?:0x)?[0-9A-Fa-f]+))?\s*])" matches = re.search( pattern, str(legal_2["legal"][0]) ) if matches: expr_type = str(matches.group(2)) if expr_type == "bitmask": andMask = str(matches.group(4)) orMask = str(matches.group(5)) legal_value = Render.bitmask( andMask, orMask ) elif expr_type == "in": if matches.group(3).find(",") >= 0: # list ==> set of values legal_value = Render.value_set( matches.group(3) .strip("[]") .split(",") ) elif matches.group(3).find(":") >= 0: # Range (start, end) = ( matches.group(3) .strip("[]") .split(":") ) legal_value = Render.range(start, end) else: # Singleton legal_value = matches.group(3).strip( "[]" ) else: legal_value = matches.group(3) bitlegal = legal_value elif isinstance(legal_2, list): pattern = r"\s*((?:0x)?[0-9A-Fa-f]+)\s*(:|,?)\s*((?:0x)?[0-9A-Fa-f]+)?" for value in legal_2: value_list = value.split(",") processed_values = [] for val in value_list: matches = re.search(pattern, val) if matches: first_value = matches.group(1) separator = matches.group(2) second_value = ( matches.group(3) if matches.group(3) else first_value ) if separator == ":": processed_value = Render.range( first_value, second_value ) else: processed_value = hex( int(first_value) ) processed_values.append(processed_value) legal_value = Render.value_set(processed_values) bitlegal = legal_value else: legal_value = hex(legal_2) bitlegal = legal_value pattern = r"((\D+)\d+(.*))-\d+" match = re.match(pattern, regName) if match: match_field = re.search(Factorizer_pattern, str(item)) if match_field: if match_field.group(0) not in {"0", "1", "2", "3"}: field_number = int(match_field.group(0)) - 8 else: field_number = match_field.group(0) fieldName = re.sub( Factorizer_pattern, f"[i*4 +{field_number}]", item, ) else: fieldName = item f = Field( fieldName, bitlegal, fieldreset, bitmsb, bitlsb, bitWidth, fieldDesc, fieldaccess, andMask, orMask, ) field.append(f) elif isinstance(item, list): for item_ in item: fieldName = f"Reserved_{item_[0]}" bitlsb = item_[0] bitmsb = item_[len(item_) - 1] legal = "" fieldaccess = "WPRI" bitWidth = int(item_[len(item_) - 1]) - int(item_[0]) + 1 fieldDesc = "Reserved" bitlegal = legal fieldreset = hex( int(resetValue, 16) >> (bitlsb) & ((1 << ((bitWidth))) - 1) ) f = Field( fieldName, bitlegal, fieldreset, bitmsb, bitlsb, bitWidth, fieldDesc, fieldaccess, ) field.append(f) elif len(fieldList) == 0: pattern = r"(\D+)\[(\d+)\-\d+\](.*)" match = re.match(pattern, regName) if match: if len(match.group(3)) > 0: name = f"{match.group(1)}[i]{match.group(3)}" regDesc = re.sub( match.group(1) + match.group(2) + match.group(3), match.group(1) + match.group(3), regDesc, ) else: name = f"{match.group(1)}[i]" regDesc = re.sub( match.group(1) + match.group(2), match.group(1), regDesc ) fieldName = name fieldDesc = regDesc else: fieldName = regName bitmsb = registerElem.get("rv32", None).get("msb", None) bitlsb = registerElem.get("rv32", None).get("lsb", None) legal = registerElem.get("rv32", "").get("type", None) fieldaccess = "" andMask = None orMask = None if legal is None: bitlegal = "" bitmask = "" fieldaccess = "RO" else: warl = re.findall(pattern_warl, str(legal.keys())) if warl: fieldaccess = Render.fieldtype(warl[0]) legal_2 = ( registerElem.get("rv32", "") .get("type", None) .get(warl[0], None) ) if legal_2 is None: bitlegal = "No Legal values" else: if isinstance(legal_2, dict): pattern = r"([\w\[\]:]+\s*(\w+)\s*)(\[\s*((?:0x)?[0-9A-Fa-f]+)\s*\D+\s*(?:((?:0x)?[0-9A-Fa-f]+))?\s*])" matches = re.search(pattern, str(legal_2["legal"][0])) if matches: legal_value = Render.range( matches.group(4), matches.group(5) ) expr_type = str(matches.group(2)) mask = matches.group(5) bitmask = mask bitlegal = legal_value if expr_type == "bitmask": andMask = str(matches.group(4)) orMask = str(matches.group(5)) elif expr_type == "in": if matches.group(3).find(",") >= 0: # list ==> set of values legal_value = Render.value_set( matches.group(3).strip("[]").split(",") ) elif matches.group(3).find(":") >= 0: # Range (start, end) = ( matches.group(3).strip("[]").split(":") ) legal_value = Render.range(start, end) else: # Singleton legal_value = matches.group(3).strip("[]") else: legal_value = matches.group(3) bitlegal = legal_value elif isinstance(legal_2, list): pattern = r"\s*((?:0x)?[0-9A-Fa-f]+)\s*(:|,?)\s*((?:0x)?[0-9A-Fa-f]+)?" for value in legal_2: value_list = value.split(",") processed_values = [] for val in value_list: matches = re.search(pattern, val) if matches: first_value = matches.group(1) separator = matches.group(2) second_value = ( matches.group(3) if matches.group(3) else first_value ) if separator == ":": processed_value = Render.range( first_value, second_value ) else: processed_value = hex(int(first_value)) processed_values.append(processed_value) legal_value = Render.value_set(processed_values) bitlegal = legal_value else: legal_value = hex(legal_2) bitlegal = legal_value fieldDesc = regDesc fieldreset = "0x" + hex(int(resetValue, 16))[2:].zfill(int(size / 4)) if bitlsb is None: bitlsb = 0 if bitmsb is None: bitmsb = 31 bitWidth = "" else: bitWidth = int(bitmsb) + 1 f = Field( fieldName, bitlegal, fieldreset, bitmsb, bitlsb, bitWidth, fieldDesc, fieldaccess, ) field.append(f) reg = RegisterClass( regName, regAddress, resetValue, size, access, regDesc, RV32, RV64, field ) return reg def returnDocument(self): with open(self.srcFile, "r", encoding="utf-8") as f: data = yaml.safe_load(f) docName = data["hart0"] size = int( data["hart0"].get("supported_xlen", "")[0] ) # depends on architecture data = csr_formatter( self.srcFile, self.customFile, self.debugfile, self.modiFile ) Registers = factorizer(data) d = DocumentClass(docName) m = MemoryMapClass(docName) a = AddressBlockClass("csr") for register in Registers: if isinstance(Registers.get(register, {}), dict): RegElement = Registers.get(register, {}) regName = register regAddress = ( (RegElement.get("address", None)) if isinstance(RegElement.get("address", None), str) else hex(RegElement.get("address", None)) ) reset = hex(RegElement.get("reset-val", "")) access = RegElement.get("priv_mode", "") if Registers.get(register, {}).get("description", "") is not None: desc = Registers.get(register, {}).get("description", "") else: desc = "" RV32 = RegElement.get("rv32", "").get("accessible", []) RV64 = RegElement.get("rv64", "").get("accessible", []) if RV32: fields = RegElement.get("rv32", "").get("fields", []) else: fields = [] r = self.returnRegister( regName, RegElement, regAddress, reset, size, access, desc, fields, RV32, RV64, ) a.addRegister(r) m.addAddressBlock(a) d.addMemoryMap(m) return d class IsaParser: """parse CSR risc-v config yaml file catch isa string""" def __init__(self, srcFile, templatefile, target, modiFile=None): self.srcFile = srcFile self.modiFile = modiFile self.templatefile = templatefile self.target = target def returnDocument(self): with open(self.srcFile, "r", encoding="utf-8") as file: yaml_data = yaml.safe_load(file) d = ISAdocumentClass("MAP") m = InstructionMapClass("ISA_B") a = InstructionBlockClass("isa") yaml_data = isa_filter(yaml_data, self.modiFile, self.templatefile) for key in yaml_data: Extension_Name = yaml_data[key].get("Subset_Name", None) Descr = yaml_data[key].get("Description", None) instructions_data = yaml_data[key].get("Instructions", None) instruction = self.returnRegister( key, Extension_Name, Descr, instructions_data ) a.addInstruction(instruction) m.addInstructionBlock(a) d.addInstructionMapBlock(m) return d def returnRegister(self, key, Extension_Name, Descr, instructions_data): OperationName = [] Name = [] Format = [] Description = [] pseudocode = [] invalid_values = [] exception_raised = [] if instructions_data: for instruction_name, instruction_data in instructions_data.items(): for instruction, data in instruction_data.items(): OperationName.append(instruction_name) if instruction is not None: Name.append(instruction) else: Name.append("") description = data.get("Description", "") # handle no or an empty description if description is not None: Description.append(description) else: Description.append("") format_str = data.get("Format", "") if format_str is not None: Format.append(format_str) else: Format.append("") pseudocode_str = data.get("Pseudocode", "") if pseudocode_str is not None: pseudocode.append(pseudocode_str) else: pseudocode.append("") exception_raised_str = data.get("Exception_Raised", "") if exception_raised_str is not None: exception_raised.append(exception_raised_str) else: exception_raised.append("") invalid_values_str = data.get("Invalid_Values", "") if invalid_values_str is not None: invalid_values.append(invalid_values_str) else: invalid_values.append("") Inst = Instruction( key, Extension_Name, Descr, OperationName, Name, Format, Description, pseudocode, invalid_values, exception_raised, ) return Inst class SpikeParser: """A class to parse data related to Spike.""" def __init__(self, srcFile, target): self.srcFile = srcFile self.target = target def returnDocument(self): with open(self.srcFile, "r", encoding="utf-8") as f: data = yaml.safe_load(f) core_configs = [] pattern = r"pmpaddr(\d+)" index = 0 bitWidth = 32 isa = "" for entry in data["hart_ids"]: M = ( "M" if data[f"hart{entry}"] .get("mstatus", {}) .get("rv32", "") .get("accessible", []) else "" ) S = ( "S" if data[f"hart{entry}"] .get("sstatus", {}) .get("rv32", "") .get("accessible", []) else "" ) U = ( "U" if data[f"hart{entry}"] .get("ustatus", {}) .get("rv32", "") .get("accessible", []) else "" ) for k in data[f"hart{entry}"].keys(): match = re.search(pattern, str(k)) if match: index += int(match.group(1)) isa = data[f"hart{entry}"]["ISA"].lower() core_config = CoreConfig( isa=data[f"hart{entry}"]["ISA"].lower(), marchid=data[f"hart{entry}"].get("marchid", {}).get("reset-val", ""), misa_we=False, misa_we_enable=True, misaligned=data[f"hart{entry}"].get("hw_data_misaligned_support", ""), mmu_mode=( "bare" if not ( (int(data[f"hart{entry}"].get("satp", {}).get("reset-val", ""))) >> 31 ) else "sv32" ), mvendorid=data[f"hart{entry}"] .get("mvendorid", {}) .get("reset-val", ""), pmpaddr0=data[f"hart{entry}"].get("pmpaddr0", {}).get("reset-val", ""), pmpcfg0=data[f"hart{entry}"].get("pmpcfg0", {}).get("reset-val", ""), pmpregions=index, priv=f"{M}{S}{U}".format(M, S, U), status_fs_field_we=False, status_fs_field_we_enable=False, status_vs_field_we=False, status_vs_field_we_enable=False, ) core_configs.append(core_config) S = Spike( bootrom=True, bootrom_base=0x10000, bootrom_size=0x1000, dram=True, dram_base=0x80000000, dram_size=0x40000000, generic_core_config=False, max_steps=200000, max_steps_enabled=False, isa=isa, priv=f"{M}{S}{U}".format(M, S, U), core_configs=core_configs, ) return S class IsaGenerator: """generate isa folder with isa docs""" def __init__(self, target): self.target = target def write(self, file_name, string): path = f"./{self.target}/isa/" if not os.path.exists(path): os.makedirs(path) _dest = os.path.join(path, file_name) print("writing file " + _dest) if not os.path.exists(os.path.dirname(_dest)): os.makedirs(os.path.dirname(_dest)) with open(_dest, "w", encoding="utf-8") as f: f.write(string) def generateISA(self, generatorClass, document): for InstructionMap in document.instructions: for InstructionBlock in InstructionMap.InstructionBlockList: blockName = InstructionBlock.name block = generatorClass(InstructionBlock.name) block.setInstructionList(InstructionBlock.Instructionlist) s = block.returnAsString() file_name = blockName + block.suffix self.write(file_name, s) class CsrGenerator: """generate csr folder with csr docs""" def __init__(self, target): self.target = target def write(self, file_name, string): path = f"./{self.target}/csr/" if not os.path.exists(path): os.makedirs(path) _dest = os.path.join(path, file_name) print("writing file " + _dest) if not os.path.exists(os.path.dirname(_dest)): os.makedirs(os.path.dirname(_dest)) with open(_dest, "w", encoding="utf-8") as f: f.write(string) def generateCSR(self, generatorClass, document): for memoryMap in document.memoryMapList: for addressBlock in memoryMap.addressBlockList: blockName = addressBlock.name block = generatorClass(addressBlock.name) block.setRegisterList(addressBlock.registerList) s = block.returnAsString() file_name = blockName + block.suffix self.write(file_name, s) class SpikeGenerator: """Generate spike folder with spike docs""" def __init__(self, target, temp, modiFile=None): self.target = target self.temp = temp self.modiFile = modiFile def write(self, file_name, string): path = f"./{self.target}/spike/" if not os.path.exists(path): os.makedirs(path) _dest = os.path.join(path, file_name) print("writing file " + _dest) with open(_dest, "w", encoding="utf-8") as f: yaml.dump(string, f, default_flow_style=False, sort_keys=False) def generateSpike(self, document): template = Template(filename=self.temp) s = template.render(spike=document) data = spike_formatter(yaml.load(s, Loader=BaseLoader), self.modiFile) file_name = "spike.yaml" self.write(file_name, data)