mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-06-28 09:16:22 -04:00
1565 lines
60 KiB
Python
1565 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))
|
|
)
|
|
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)
|