cva6/config/gen_from_riscv_config/scripts/libs/utils.py
khandelwaltanuj b484f5f3ee
Some checks are pending
bender-up-to-date / bender-up-to-date (push) Waiting to run
ci / build-riscv-tests (push) Waiting to run
ci / execute-riscv64-tests (push) Blocked by required conditions
ci / execute-riscv32-tests (push) Blocked by required conditions
dtlb_lu_access is done only when misalgined ex valid is 0 (#2989)
This fixes issue #2988 and #2827

---------

Co-authored-by: JeanRochCoulon <jean-roch.coulon@thalesgroup.com>
2025-06-17 08:00:48 +02:00

1569 lines
60 KiB
Python

# 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 = [
"<!--Copyright (c) 2024 OpenHW Group",
"Copyright (c) 2024 Thales",
"SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 ",
"Author: Abdessamii Oukalrazqou",
"-->",
]
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 = [
"<!--Copyright (c) 2024 OpenHW Group",
"Copyright (c) 2024 Thales",
"SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 ",
"Author: Abdessamii Oukalrazqou",
"-->",
]
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))
)
if RegElement.get("reset-val", "") != "":
reset = hex(RegElement.get("reset-val", ""))
else:
print(regName, "reset val not defined")
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)