diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c52c6d..ce48306 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] - 2021-10-30 +- Added cross combination coverage support. + ## [0.7.3] - 2021-09-02 - Updated logger to enable logging for API calls. diff --git a/docs/source/Cross_coverage.rst b/docs/source/Cross_coverage.rst new file mode 100644 index 0000000..ae4f9be --- /dev/null +++ b/docs/source/Cross_coverage.rst @@ -0,0 +1,134 @@ +############### +Cross Coverage +############### + +ISAC supports extraction of data dependencies across different instructions in the log file. It can check for possible data hazards (RAW, WAW, WAR) between instructions. +The hazards are checked for instructions belonging to the same window. The window size of the instruction is a parameter (by default taken as 32). +The coverpoints are updated whenever the conditions mentioned in them are matched for a given instruction. + +Syntax for the coverpoints +=========================== + +Each coverpoint is a string constituting of three sections - opcode list, assign list and condition list separated by ``::`` symbol. In each of these lists symbol +``?`` signifies a don't care condition. The delimiter for elements of the list is ``:`` symbol. The template of a generic coverpoint is shown below: + +``[list of opcodes]::[list of assignment statements]::[list of condition statements]`` + +Opcode List: +-------------- +This is the list of instruction names against which we check the name of the current instruction in the queue. Here ``?`` signifies that we needn't check the name +for that instruction as that won't affect the conditions of that coverpoint. Opcode list is a list of tuples and don't care conditions. + +An example of a opcode list : ``[(add,sub):?:?:(mul,mulh):?]`` + +Assign List: +------------- +This list contains the assigning statements for registers. These statements are evaluated using ``exec`` command. The register numbers are assigned to variables. +Under don't care conditions, no assignment is done. + +An example of assign list: ``[a=rd:?:?]`` + +Here a is any variable which is assigned the destination register number of the first instruction. + +Condition List: +---------------- +This contains the evaluation statements to check the data dependencies. In case of don't care conditions, we don't check for data dependecies for that +particular instruction. It conatins conditions for both source as well as destination registers. We can check for both consuming and non-consuming instructions. + +Example of condition list: ``[?:rs1==a or rs2==a]`` + +Here let a be the destination register of the first instruction (assigned by assign list). Then this checks whether any of the source registers are equal to the +destination register of the previous instruction. It basically checks for read after write hazard. + +Cross Coverage Queue +===================== + +This is list of instruction objects in which the data is pushed in the sequence they are parsed. Whenever the queue size becomes equal to the window size, we check for all potential data hazards between the front element of the queue and the rest of the instructions according to the coverpoint and then pop the first element. + +Cross Coverage Class +===================== + +def __init__(self,label,coverpoint): +--------------------------------------- + +The cross coverage class ``cross`` is initialized with the ``label`` and ``coverpoint``. ``result`` stores the coverage for that coverpoint. + +* Arguments: ``self`` instance of the class, ``label`` the instruction name, ``coverpoint`` the coverpoint to be evaluated. + + +.. code-block:: python + + def __init__(self,label,coverpoint): + self.label = label + self.coverpoint = coverpoint + self.result = 0 + +An instance of a class is created for each ``(label,coverpoint)`` pair present in the CGF and all the relevant information about the coverpoint - opcode list, assign list and condition list are extracted. + +def process(self, queue, window_size, addr_pairs): +---------------------------------------------------- + +The ``process`` function of ``cross`` evaluates each coverpoint and updates ``result`` if there is a hit. The data in the coverpoint is evaluated against their corresponding instruction in the queue i.e. if the index of an instruction is i, then it will check and assign statments at index i of the three lists of the coverpoints. If the instruction address doesn't belong to any list of addresses in ``addr_pairs`` we treat is as a don't care. These coverpoints are checked in three steps. + + - First, the name of the first instruction is checked against the corresponding entry in the opcode list. + - If the instruction is present, we assign its register value to a variable using ``exec`` command on the assign list elements. + - Then we check for the conditions in the condition list using ``eval`` command. + The above steps are repeated until the entire coverpoint is evaluated and it's a hit or the conditions are not satisfied and the loop breaks. + +* Arguments: ``self`` instance of the class, ``queue`` the list containing the instructions to be evaluated, ``window_size`` maximum number of instructions to be checked for any coverpoint, ``addr_pairs`` the addresses to be considered while evaluating an instruction +* Returns: None + +def get_metric(self): +---------------------- + +It returns the coverage for that instance. + +* Arguments: ``self`` instance of the class +* Returns: ``result`` the final coverage for that coverpoint + +Updating the CGF +======================== + + - An object dictionary ``obj_dict`` contains an instance of ``cross`` for each ``(label,coverpoint)`` pair. + - As the instrcutions are parsed, they are appended to the ``cross_cover_queue`` which is the list of instructions to be checked. + - When the size of queue becomes equal to the window size, for each entry in ``obj_dict``, the coverpoints are evaluated in ``process`` + - The first element of the queue is popped and the process is repeated. + - After all the instrcutions are parsed, all the instructions in the queue are again evaluated in the above manner till there is nothing to evaluate. + - The final metric for each ``(label,coverpoint)`` instance is updated for each node in the CGF. + +**Examples of coverpoints** + The window size is fixed and equal to 5. + + 1. RAW for an add instruction followed immediately by a subtract instruction. + + .. code-block:: python + + [(add,sub) : (add,sub) ] :: [a=rd : ? ] :: [? : rs1==a or rs2==a ] + + 2. RAW on x10 register for an add instruction followed by a subtract instruction with one non-consuming/non-updating instruction in between. + No update happens to the rd register in between. + + .. code-block:: python + + [(add,sub) : ? : (add,sub) ] :: [a=rd : ? : ? ] :: [rd==x10 : rd!=a and rs1!=a and rs2!=a : rs1==a or rs2==a ] + + 3. WAW for an add instruction followed by a subtract instruction with 3 non-consuming instructions in between. + + .. code-block:: python + + [add : ? : ? : ? : sub] :: [a=rd : ? : ? : ? : ?] :: [? : ? : ? : ? : rd==a] + + 4. WAW for add followed by subtract with 3 consuming instructions in between. + + .. code-block:: python + + [(add,sub) : ? : ? : ? : (add,sub)] :: [a=rd : ? : ? : ? : ?] :: [? : rs1==a or rs2==a : rs1==a or rs2==a : rs1==a or rs2==a : rd==a] + + 5. WAR for an add instruction followed immediately by a subtract instruction. + + .. code-block:: python + + [(add,sub) : (add,sub) ] :: [a=rs1; b=rs2 : ? ] :: [? : rd==a or rd==b ] + + + diff --git a/docs/source/cgf.rst b/docs/source/cgf.rst index 45b7b84..00fcb01 100644 --- a/docs/source/cgf.rst +++ b/docs/source/cgf.rst @@ -17,6 +17,7 @@ A covergroup is a dictionary based on the following template. These dictionaries * Register Operand Combinations * Register/Immediate Value Combinations * Control and Status Registers Value Combinations +* Cross coverage nodes Template -------- @@ -58,6 +59,10 @@ The template for defining a covergroup is as follows: csr_comb: : 0 : 0 + ... + cross_comb: + :0 + :0 @@ -308,6 +313,7 @@ A covergroup contains the following nodes: .. code-block:: python misa >> (xlen-2) == 0x01 + 3. A coverpoint which checks whether the *mie* field of *mstatus* register is 1. @@ -315,5 +321,59 @@ A covergroup contains the following nodes: mstatus && (0x8) == 0x8 +* **cross_comb** + *This node is optional.* + + This node describes the *Cross combination coverpoints* for a covergroup. Cross coverage can identify potential data hazards between instructions - Read after Write, Write after Write, Write after Read. + + * **crosscomb-str** + This string is divided into three parts - opcode list, assign list and condition list separated by :: symbol. It is parsed and all the three lists are obtained separately. The variables available for use in the expression are as follows: + + * ``instr_name`` : The instruction names in the opcode list + + * ``rs1`` : The register number of source register 1 of the current instruction in the assign list. + + * ``rs2`` : The register number of source register 2 of the current instruction in the assign list. + + * ``rd`` : The register number of destination register of the current instruction in the assign list. + + Along with the above mentioned variable any valid python comparison operators can be used in the condition list. + + + **Examples** + + The window size is fixed and equal to 5. + + 1. RAW for an add instruction followed immediately by a subtract instruction. + + .. code-block:: python + + [(add,sub) : (add,sub) ] :: [a=rd : ? ] :: [? : rs1==a or rs2==a] + + 2. RAW on x10 register for an add instruction followed by a subtract instruction with one non-consuming/non-updating instruction in between. + No update happens to the rd register in between. + + .. code-block:: python + + [(add,sub) : ? : (add,sub) ] :: [a=rd : ? : ? ] :: [rd==x10 : rd!=a and rs1!=a and rs2!=a : rs1==a or rs2==a ] + + 3. WAW for an add instruction followed by a subtract instruction with 3 non-consuming instructions in between. + + .. code-block:: python + + [add : ? : ? : ? : sub] :: [a=rd : ? : ? : ? : ?] :: [? : ? : ? : ? : rd==a] + + 4. WAW for add followed by subtract with 3 consuming instructions in between. + + .. code-block:: python + + [(add,sub) : ? : ? : ? : (add,sub)] :: [a=rd : ? : ? : ? : ?] :: [? : rs1==a or rs2==a : rs1==a or rs2==a : rs1==a or rs2==a : rd==a] + + 5. WAR for an add instruction followed immediately by a subtract instruction. + + .. code-block:: python + + [(add,sub) : (add,sub) ] :: [a=rs1; b=rs2 : ? ] :: [? : rd==a or rd==b] + diff --git a/riscv_isac/InstructionObject.py b/riscv_isac/InstructionObject.py index 2c670b3..ec4e163 100644 --- a/riscv_isac/InstructionObject.py +++ b/riscv_isac/InstructionObject.py @@ -16,6 +16,11 @@ def __init__( zimm = None, csr = None, shamt = None, + succ = None, + pred = None, + rl = None, + aq = None, + rm = None, reg_commit = None, csr_commit = None, mnemonic = None @@ -44,6 +49,11 @@ def __init__( self.zimm = zimm self.csr = csr self.shamt = shamt + self.succ = succ + self.pred = pred + self.rl = rl + self.aq = aq + self.rm = rm self.reg_commit = reg_commit self.csr_commit = csr_commit self.mnemonic = mnemonic @@ -66,6 +76,16 @@ def __str__(self): line+= ' zimm: '+ str(self.zimm) if self.shamt: line+= ' shamt: '+ str(self.shamt) + if self.succ: + line+= ' succ: '+ str(self.succ) + if self.pred: + line+= ' pred: '+ str(self.pred) + if self.rl: + line+= ' rl: '+ str(self.rl) + if self.aq: + line+= ' aq: '+ str(self.aq) + if self.rm: + line+= ' rm: '+ str(self.rm) if self.reg_commit: line+= ' reg_commit: '+ str(self.reg_commit) if self.csr_commit: diff --git a/riscv_isac/__init__.py b/riscv_isac/__init__.py index 14ce8f9..cdf5c90 100644 --- a/riscv_isac/__init__.py +++ b/riscv_isac/__init__.py @@ -4,4 +4,4 @@ __author__ = """InCore Semiconductors Pvt Ltd""" __email__ = 'info@incoresemi.com' -__version__ = '0.7.3' +__version__ = '0.8.0' diff --git a/riscv_isac/coverage.py b/riscv_isac/coverage.py index 01b2c5a..97d823a 100644 --- a/riscv_isac/coverage.py +++ b/riscv_isac/coverage.py @@ -1,972 +1,994 @@ - -# See LICENSE.incore for details -# See LICENSE.iitm for details - -import ruamel -from ruamel.yaml import YAML -import riscv_isac.utils as utils -from riscv_isac.constants import * -from riscv_isac.log import logger -from collections import Counter -import sys -from riscv_isac.utils import yaml -from riscv_isac.cgf_normalize import * -import riscv_isac.fp_dataset as fmt -import struct -import pytablewriter -import importlib -import pluggy -import riscv_isac.plugins as plugins -from riscv_isac.plugins.specification import * -import math -import multiprocessing as mp -from collections.abc import MutableMapping - - -unsgn_rs1 = ['sw','sd','sh','sb','ld','lw','lwu','lh','lhu','lb', 'lbu','flw','fld','fsw','fsd',\ - 'bgeu', 'bltu', 'sltiu', 'sltu','c.lw','c.ld','c.lwsp','c.ldsp',\ - 'c.sw','c.sd','c.swsp','c.sdsp','mulhu','divu','remu','divuw',\ - 'remuw','aes64ds','aes64dsm','aes64es','aes64esm','aes64ks2',\ - 'sha256sum0','sha256sum1','sha256sig0','sha256sig1','sha512sig0',\ - 'sha512sum1r','sha512sum0r','sha512sig1l','sha512sig0l','sha512sig1h','sha512sig0h',\ - 'sha512sig1','sha512sum0','sha512sum1','sm3p0','sm3p1','aes64im',\ - 'sm4ed','sm4ks','ror','rol','rori','rorw','rolw','roriw','clmul','clmulh',\ - 'andn','orn','xnor','pack','packh','packu','packuw','packw',\ - 'xperm.n','xperm.b','grevi','aes64ks1i', 'shfli', 'unshfli', \ - 'aes32esmi', 'aes32esi', 'aes32dsmi', 'aes32dsi'] -unsgn_rs2 = ['bgeu', 'bltu', 'sltiu', 'sltu', 'sll', 'srl', 'sra','mulhu',\ - 'mulhsu','divu','remu','divuw','remuw','aes64ds','aes64dsm','aes64es',\ - 'aes64esm','aes64ks2','sm4ed','sm4ks','ror','rol','rorw','rolw','clmul',\ - 'clmulh','andn','orn','xnor','pack','packh','packu','packuw','packw',\ - 'xperm.n','xperm.b', 'aes32esmi', 'aes32esi', 'aes32dsmi', 'aes32dsi',\ - 'sha512sum1r','sha512sum0r','sha512sig1l','sha512sig1h','sha512sig0l','sha512sig0h','fsw'] - -one_operand_finstructions = ["fsqrt.s","fmv.x.w","fcvt.wu.s","fcvt.w.s","fclass.s","fcvt.l.s","fcvt.lu.s","fcvt.s.l","fcvt.s.lu","fcvt.s.w","fcvt.s.wu","fmv.w.x"] -two_operand_finstructions = ["fadd.s","fsub.s","fmul.s","fdiv.s","fmax.s","fmin.s","feq.s","flt.s","fle.s","fsgnj.s","fsgnjn.s","fsgnjx.s"] -three_operand_finstructions = ["fmadd.s","fmsub.s","fnmadd.s","fnmsub.s"] - -one_operand_dinstructions = ["fsqrt.d","fclass.d","fcvt.w.d","fcvt.wu.d","fcvt.d.w","fcvt.d.wu","fcvt.l.d","fcvt.lu.d","fcvt.d.l","fcvt.d.lu","fmv.x.d","fmv.d.x","fcvt.s.d","fcvt.d.s"] -two_operand_dinstructions = ["fadd.d","fsub.d","fmul.d","fdiv.d","fmax.d","fmin.d","feq.d","flt.d","fle.d","fsgnj.d","fsgnjn.d","fsgnjx.d"] -three_operand_dinstructions = ["fmadd.d","fmsub.d","fnmadd.d","fnmsub.d"] - -class csr_registers(MutableMapping): - ''' - Defines the architectural state of CSR Register file. - ''' - - def __init__ (self, xlen): - ''' - Class constructor - - :param xlen: max XLEN value of the RISC-V device - - :type xlen: int - - Currently defines the CSR register files the - width of which is defined by the xlen parameter. These are - implemented as an array holding the hexadecimal representations of the - values as string. These can be accessed by both integer addresses as well as string names - - ''' - - global arch_state - - if(xlen==32): - self.csr = ['00000000']*4096 - self.csr[int('301',16)] = '40000000' # misa - else: - self.csr = ['0000000000000000']*4096 - self.csr[int('301',16)] = '8000000000000000' # misa - - # M-Mode CSRs - self.csr[int('F11',16)] = '00000000' # mvendorid - self.csr[int('306',16)] = '00000000' # mcounteren - self.csr[int('B00',16)] = '0000000000000000' # mcycle - self.csr[int('B02',16)] = '0000000000000000' # minstret - for i in range(29): # mphcounter 3-31, 3h-31h - self.csr[int('B03',16)+i] = '0000000000000000' - self.csr[int('B83',16)+i] = '00000000' - self.csr[int('320',16)] = '00000000' # mcounterinhibit - self.csr[int('B80',16)] = '00000000' # mcycleh - self.csr[int('B82',16)] = '00000000' # minstreth - - ## mtime, mtimecmp => 64 bits, platform defined memory mapping - - # S-Mode CSRs - self.csr[int('106',16)] = '00000000' # scounteren - - # F-Mode CSRs - self.csr[int('003',16)] = '00000000' # fcsr - - self.csr_regs={ - "mvendorid":int('F11',16), - "marchid":int('F12',16), - "mimpid":int('F13',16), - "mhartid":int('F14',16), - "mstatus":int('300',16), - "misa":int('301',16), - "medeleg":int('302',16), - "mideleg":int('303',16), - "mie":int('304',16), - "mtvec":int('305',16), - "mcounteren":int('306',16), - "mscratch":int('340',16), - "mepc":int('341',16), - "mcause":int('342',16), - "mtval":int('343',16), - "mip":int('344',16), - "pmpcfg0":int('3A0',16), - "pmpcfg1":int('3A1',16), - "pmpcfg2":int('3A2',16), - "pmpcfg3":int('3A3',16), - "mcycle":int('B00',16), - "minstret":int('B02',16), - "mcycleh":int('B80',16), - "minstreth":int('B82',16), - "mcountinhibit":int('320',16), - "tselect":int('7A0',16), - "tdata1":int('7A1',16), - "tdata2":int('7A2',16), - "tdata3":int('7A3',16), - "dcsr":int('7B0',16), - "dpc":int('7B1',16), - "dscratch0":int('7B2',16), - "dscratch1":int('7B3',16), - "sstatus": int('100',16), - "sedeleg": int('102',16), - "sideleg": int('103',16), - "sie": int('104',16), - "stvec": int('105',16), - "scounteren": int('106',16), - "sscratch": int('140',16), - "sepc": int('141',16), - "scause": int('142',16), - "stval": int('143',16), - "sip": int('144',16), - "satp": int('180',16), - "fcsr": int('003',16) - } - for i in range(16): - self.csr_regs["pmpaddr"+str(i)] = int('3B0',16)+i - for i in range(3,32): - self.csr_regs["mhpmcounter"+str(i)] = int('B03',16) + (i-3) - self.csr_regs["mhpmcounter"+str(i)+"h"] = int('B83',16) + (i-3) - self.csr_regs["mhpmevent"+str(i)] = int('323',16) + (i-3) - - def __setitem__ (self,key,value): - - if(key == 'frm'): - value = value[-1:] - arch_state.fcsr = value - self.csr[self.csr_regs["fcsr"]] = "00000000" - else: - if(isinstance(key, str)): - self.csr[self.csr_regs[key]] = value - else: - self.csr[key] = value - - def __iter__(self): - for entry in self.csr_regs.keys(): - yield (entry,self.csr_regs[entry],self.csr[self.csr_regs[entry]]) - - def __len__(self): - return len(self.csr) - - def __delitem__(self,key): - pass - - def __getitem__ (self,key): - if(isinstance(key, str)): - return self.csr[self.csr_regs[key]] - else: - return self.csr[key] - -class archState: - ''' - Defines the architectural state of the RISC-V device. - ''' - - def __init__ (self, xlen, flen): - ''' - Class constructor - - :param xlen: max XLEN value of the RISC-V device - :param flen: max FLEN value of the RISC-V device - - :type xlen: int - :type flen: int - - Currently defines the integer and floating point register files the - width of which is defined by the xlen and flen parameters. These are - implemented as an array holding the hexadecimal representations of the - values as string. - - The program counter is also defined as an int. - - ''' - - if xlen == 32: - self.x_rf = ['00000000']*32 - else: - self.x_rf = ['0000000000000000']*32 - - if flen == 32: - self.f_rf = ['00000000']*32 - self.fcsr = 0 - else: - self.f_rf = ['0000000000000000']*32 - self.fcsr = 0 - self.pc = 0 - -class statistics: - ''' - Class for holding statistics used for Data propagation report - ''' - - def __init__(self, xlen, flen): - ''' - This class maintains a collection of arrays which are useful in - calculating the following set of statistics: - - - STAT1 : Number of instructions that hit unique coverpoints and update the signature. - - STAT2 : Number of instructions that hit covepoints which are not unique but still update the signature - - STAT3 : Number of instructions that hit a unique coverpoint but do not update signature - - STAT4 : Number of multiple signature updates for the same coverpoint - - STAT5 : Number of times the signature was overwritten - ''' - - - self.stat1 = [] - self.stat2 = [] - self.stat3 = [] - self.stat4 = [] - self.stat5 = [] - self.code_seq = [] - self.ucode_seq = [] - self.covpt = [] - self.ucovpt = [] - self.cov_pt_sig = [] - self.last_meta = [] - -def pretty_print_yaml(myyaml): - res = '''''' - - from io import StringIO - string_stream = StringIO() - yaml.dump(myyaml,string_stream) - res = string_stream.getvalue() - string_stream.close() - return res - -def pretty_print_regfile(regfile): - res = "" - for index in range(0, 32, 4): - print('x'+str(index) + ' : ' + regfile[index] + '\t' +\ - 'x'+str(index+1) + ' : ' + regfile[index+1] + '\t' + \ - 'x'+str(index+2) + ' : ' + regfile[index+2] + '\t' + \ - 'x'+str(index+3) + ' : ' + regfile[index+3] + '\t' ) - print('\n\n') - -def gen_report(cgf, detailed): - ''' - Function to convert a CGF to a string report. A detailed report includes the individual coverpoints and the corresponding values of the same - - :param cgf: an input CGF dictionary - :param detailed: boolean value indicating a detailed report must be generated. - - :type cgf: dict - :type detailed: bool - - :return: string holding the final report - ''' - temp = cgf.copy() - for cov_labels, value in cgf.items(): - if cov_labels != 'datasets': - total_uncovered = 0 - total_categories = 0 - for categories in value: - if categories not in ['cond','config','ignore']: - for coverpoints, coverage in value[categories].items(): - if coverage == 0: - total_uncovered += 1 - total_categories += len(value[categories]) - for categories in value: - if categories not in ['cond','config','ignore']: - uncovered = 0 - for coverpoints, coverage in value[categories].items(): - if coverage == 0: - uncovered += 1 - percentage_covered = str((len(value[categories]) - uncovered)/len(value[categories])) - node_level_str = ' ' + categories + ':\n' - node_level_str += ' coverage: ' + \ - str(len(value[categories]) - uncovered) + \ - '/' + str(len(value[categories])) - temp[cov_labels][categories]['coverage'] = '{0}/{1}'.format(\ - str(len(value[categories]) - uncovered),\ - str(len(value[categories]))) - temp[cov_labels]['total_coverage'] = '{0}/{1}'.format(\ - str(total_categories-total_uncovered),\ - str(total_categories)) - return dict(temp) - -def merge_files(files,i,k): - ''' - Merges files from i to n where n is len(files) or i+k - - Arguments: - - files: List of dictionaries to be merged - i : beginning index to merge files on a given core - k : number of files to be merged - - ''' - - temp = files[i] - n = min(len(files),i+k) - for logs_cov in files[i+1:n]: - for cov_labels, value in logs_cov.items(): - if cov_labels not in temp: - temp[cov_labels] = value - continue - for categories in value: - if categories not in ['cond','config','ignore','total_coverage','coverage']: - if categories not in temp[cov_labels]: - temp[cov_labels][categories] = value[categories] - continue - for coverpoints, coverage in value[categories].items(): - if coverpoints not in temp[cov_labels][categories]: - temp[cov_labels][categories][coverpoints] = coverage - else: - temp[cov_labels][categories][coverpoints] += coverage - return temp - -def merge_fn(files, cgf, p): - - ''' - Each core is assigned ceil(n/k) processes where n is len(files) - ''' - - - pool_work = mp.Pool(processes = p) - while(len(files)>1): - n = len(files) - max_process = math.ceil(n/p) - if(max_process==1): - max_process = 2 - files = pool_work.starmap_async(merge_files,[(files,i,max_process) for i in range(0,n,max_process)]) - files = files.get() - pool_work.close() - pool_work.join() - - return files[0] - - -def merge_coverage(inp_files, cgf, detailed, xlen, p=1): - ''' - This function merges values of multiple CGF files and return a single cgf - file. This can be treated analogous to how coverage files are merged - traditionally. - - :param inp_files: an array of input CGF file names which need to be merged. - :param cgf: a cgf against which coverpoints need to be checked for. - :param detailed: a boolean value indicating if a detailed report needs to be generated - :param xlen: XLEN of the trace - :param p: Number of worker processes (>=1) - - :type inp_files: [str] - :type cgf: dict - :type detailed: bool - :type xlen: int - :type p: int - - :return: a string contain the final report of the merge. - ''' - files = [] - for logs in inp_files: - files.append(utils.load_yaml_file(logs)) - - temp = merge_fn(files,cgf,p) - for cov_labels, value in temp.items(): - for categories in value: - if categories not in ['cond','config','ignore','total_coverage','coverage']: - for coverpoints, coverage in value[categories].items(): - if coverpoints in cgf[cov_labels][categories]: - cgf[cov_labels][categories][coverpoints] += coverage - - return gen_report(cgf, detailed) - -def twos_complement(val,bits): - if (val & (1 << (bits - 1))) != 0: - val = val - (1 << bits) - return val - -def compute_per_line(instr, cgf, xlen, addr_pairs, sig_addrs): - ''' - This function checks if the current instruction under scrutiny matches a - particular coverpoint of interest. If so, it updates the coverpoints and - return the same. - - :param instr: an instructionObject of the single instruction currently parsed - :param cgf: a cgf against which coverpoints need to be checked for. - :param xlen: Max xlen of the trace - :param addr_pairs: pairs of start and end addresses for which the coverage needs to be updated - - :type instr: :class:`instructionObject` - :type cgf: dict - :type xlen: int - :type addr_pairs: (int, int) - ''' - global arch_state - global csr_regfile - global stats - - mnemonic = instr.mnemonic - commitvalue = instr.reg_commit - - # assign default values to operands - rs1 = 0 - rs2 = 0 - rs3 = 0 - rd = 0 - rs1_type = 'x' - rs2_type = 'x' - rs3_type = 'f' - rd_type = 'x' - - csr_addr = 0 - - # create signed/unsigned conversion params - if xlen == 32: - unsgn_sz = '>I' - sgn_sz = '>i' - else: - unsgn_sz = '>Q' - sgn_sz = '>q' - - # if instruction is empty then return - if instr is None: - return cgf - - # check if instruction lies within the valid region of interest - if addr_pairs: - if any([instr.instr_addr >= saddr and instr.instr_addr < eaddr for saddr,eaddr in addr_pairs]): - enable = True - else: - enable = False - else: - enable=True - - # capture the operands and their values from the regfile - if instr.rs1 is not None: - rs1 = instr.rs1[0] - rs1_type = instr.rs1[1] - if instr.rs2 is not None: - rs2 = instr.rs2[0] - rs2_type = instr.rs2[1] - if instr.rs3 is not None: - rs3 = instr.rs3[0] - rs3_type = instr.rs3[1] - - if instr.rd is not None: - rd = instr.rd[0] - is_rd_valid = True - rd_type = instr.rd[1] - else: - is_rd_valid = False - - if instr.imm is not None: - imm_val = instr.imm - if instr.shamt is not None: - imm_val = instr.shamt - - # special value conversion based on signed/unsigned operations - if instr.instr_name in unsgn_rs1: - rs1_val = struct.unpack(unsgn_sz, bytes.fromhex(arch_state.x_rf[rs1]))[0] - elif rs1_type == 'x': - rs1_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.x_rf[rs1]))[0] - elif rs1_type == 'f': - rs1_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.f_rf[rs1]))[0] - if instr.instr_name in one_operand_finstructions + two_operand_finstructions + three_operand_finstructions\ - + one_operand_dinstructions + two_operand_dinstructions + three_operand_dinstructions: - rs1_val = '0x' + (arch_state.f_rf[rs1]).lower() - - if instr.instr_name in unsgn_rs2: - rs2_val = struct.unpack(unsgn_sz, bytes.fromhex(arch_state.x_rf[rs2]))[0] - elif rs2_type == 'x': - rs2_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.x_rf[rs2]))[0] - elif rs2_type == 'f': - rs2_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.f_rf[rs2]))[0] - if instr.instr_name in two_operand_finstructions + two_operand_dinstructions + three_operand_finstructions\ - + three_operand_dinstructions: - rs2_val = '0x' + (arch_state.f_rf[rs2]).lower() - - if instr.instr_name in three_operand_finstructions + three_operand_dinstructions: - rs3_val = '0x' + (arch_state.f_rf[rs3]).lower() - - if instr.instr_name in ['csrrwi']: - arch_state.fcsr = instr.zimm - - if instr.instr_name in one_operand_finstructions + two_operand_finstructions + three_operand_finstructions\ - + one_operand_dinstructions + two_operand_dinstructions + three_operand_dinstructions: - rm = instr.rm - if(rm==7 or rm==None): - rm_val = arch_state.fcsr - else: - rm_val = rm - - arch_state.pc = instr.instr_addr - - # the ea_align variable is used by the eval statements of the - # coverpoints for conditional ops and memory ops - if instr.instr_name in ['jal','bge','bgeu','blt','bltu','beq','bne']: - ea_align = (instr.instr_addr+(imm_val<<1)) % 4 - - if instr.instr_name == "jalr": - ea_align = (rs1_val + imm_val) % 4 - - if instr.instr_name in ['sw','sh','sb','lw','lhu','lh','lb','lbu','lwu','flw','fsw']: - ea_align = (rs1_val + imm_val) % 4 - if instr.instr_name in ['ld','sd','fld','fsd']: - ea_align = (rs1_val + imm_val) % 8 - - local_dict={} - for i in csr_regfile.csr_regs: - local_dict[i] = int(csr_regfile[i],16) - - local_dict['xlen'] = xlen - - if enable : - for cov_labels,value in cgf.items(): - if cov_labels != 'datasets': - if 'opcode' in value: - if instr.instr_name in value['opcode']: - if stats.code_seq: - logger.error('Found a coverpoint without sign Upd ' + str(stats.code_seq)) - stats.stat3.append('\n'.join(stats.code_seq)) - stats.code_seq = [] - stats.covpt = [] - stats.ucovpt = [] - stats.ucode_seq = [] - - if value['opcode'][instr.instr_name] == 0: - stats.ucovpt.append('opcode : ' + instr.instr_name) - stats.covpt.append('opcode : ' + instr.instr_name) - value['opcode'][instr.instr_name] += 1 - if 'rs1' in value and 'x'+str(rs1) in value['rs1']: - if value['rs1']['x'+str(rs1)] == 0: - stats.ucovpt.append('rs1 : ' + 'x'+str(rs1)) - stats.covpt.append('rs1 : ' + 'x'+str(rs1)) - value['rs1']['x'+str(rs1)] += 1 - if 'rs2' in value and 'x'+str(rs2) in value['rs2']: - if value['rs2']['x'+str(rs2)] == 0: - stats.ucovpt.append('rs2 : ' + 'x'+str(rs2)) - stats.covpt.append('rs2 : ' + 'x'+str(rs2)) - value['rs2']['x'+str(rs2)] += 1 - if 'rd' in value and is_rd_valid and 'x'+str(rd) in value['rd']: - if value['rd']['x'+str(rd)] == 0: - stats.ucovpt.append('rd : ' + 'x'+str(rd)) - stats.covpt.append('rd : ' + 'x'+str(rd)) - value['rd']['x'+str(rd)] += 1 - - if 'rs1' in value and 'f'+str(rs1) in value['rs1']: - if value['rs1']['f'+str(rs1)] == 0: - stats.ucovpt.append('rs1 : ' + 'f'+str(rs1)) - stats.covpt.append('rs1 : ' + 'f'+str(rs1)) - value['rs1']['f'+str(rs1)] += 1 - if 'rs2' in value and 'f'+str(rs2) in value['rs2']: - if value['rs2']['f'+str(rs2)] == 0: - stats.ucovpt.append('rs2 : ' + 'f'+str(rs2)) - stats.covpt.append('rs2 : ' + 'f'+str(rs2)) - value['rs2']['f'+str(rs2)] += 1 - if 'rs3' in value and 'f'+str(rs3) in value['rs3']: - if value['rs3']['f'+str(rs3)] == 0: - stats.ucovpt.append('rs3 : ' + 'f'+str(rs3)) - stats.covpt.append('rs3 : ' + 'f'+str(rs3)) - value['rs3']['f'+str(rs3)] += 1 - if 'rd' in value and is_rd_valid and 'f'+str(rd) in value['rd']: - if value['rd']['f'+str(rd)] == 0: - stats.ucovpt.append('rd : ' + 'f'+str(rd)) - stats.covpt.append('rd : ' + 'f'+str(rd)) - value['rd']['f'+str(rd)] += 1 - - if 'op_comb' in value and len(value['op_comb']) != 0 : - for coverpoints in value['op_comb']: - if eval(coverpoints): - if cgf[cov_labels]['op_comb'][coverpoints] == 0: - stats.ucovpt.append(str(coverpoints)) - stats.covpt.append(str(coverpoints)) - cgf[cov_labels]['op_comb'][coverpoints] += 1 - if 'val_comb' in value and len(value['val_comb']) != 0: - if instr.instr_name in two_operand_finstructions: - if xlen == 64: - rs1_val = rs1_val[8:] - rs2_val = rs2_val[8:] - val_key = fmt.extract_fields(32, rs1_val, str(1)) - val_key+= " and " - val_key+= fmt.extract_fields(32, rs2_val, str(2)) - val_key+= " and " - val_key+= 'rm_val == '+ str(rm_val) - val_key+= ' #nosat' - l=[0] - l[0] = val_key - val_key = l - if(val_key[0] in cgf[cov_labels]['val_comb']): - if cgf[cov_labels]['val_comb'][val_key[0]] == 0: - stats.ucovpt.append(str(val_key[0])) - stats.covpt.append(str(val_key[0])) - cgf[cov_labels]['val_comb'][val_key[0]] += 1 - elif instr.instr_name in one_operand_finstructions: - if xlen == 64 and instr.instr_name not in ["fcvt.s.l", "fcvt.s.lu"]: - rs1_val = rs1_val[8:] - if instr.instr_name not in ["fcvt.s.l","fcvt.s.lu","fcvt.s.w","fcvt.s.wu","fmv.w.x"]: - val_key = fmt.extract_fields(32, rs1_val, str(1)) - else: - val_key = "rs1_val == "+ str(rs1_val) - val_key+= " and " - val_key+= 'rm_val == '+ str(rm_val) - val_key+= ' #nosat' - l=[0] - l[0] = val_key - val_key = l - if(val_key[0] in cgf[cov_labels]['val_comb']): - if cgf[cov_labels]['val_comb'][val_key[0]] == 0: - stats.ucovpt.append(str(val_key[0])) - stats.covpt.append(str(val_key[0])) - cgf[cov_labels]['val_comb'][val_key[0]] += 1 - elif instr.instr_name in three_operand_finstructions: - if xlen == 64: - rs1_val = rs1_val[8:] - rs2_val = rs2_val[8:] - rs3_val = rs3_val[8:] - val_key = fmt.extract_fields(32, rs1_val, str(1)) - val_key+= " and " - val_key+= fmt.extract_fields(32, rs2_val, str(2)) - val_key+= " and " - val_key+= fmt.extract_fields(32, rs3_val, str(3)) - val_key+= " and " - val_key+= 'rm_val == '+ str(rm_val) - val_key+= ' #nosat' - l=[0] - l[0] = val_key - val_key = l - print(val_key) - if(val_key[0] in cgf[cov_labels]['val_comb']): - if cgf[cov_labels]['val_comb'][val_key[0]] == 0: - stats.ucovpt.append(str(val_key[0])) - stats.covpt.append(str(val_key[0])) - cgf[cov_labels]['val_comb'][val_key[0]] += 1 - elif instr.instr_name in two_operand_dinstructions: - val_key = fmt.extract_fields(64, rs1_val, str(1)) - val_key+= " and " - val_key+= fmt.extract_fields(64, rs2_val, str(2)) - val_key+= " and " - val_key+= 'rm_val == '+ str(rm_val) - val_key+= ' #nosat' - l=[0] - l[0] = val_key - val_key = l - if(val_key[0] in cgf[cov_labels]['val_comb']): - if cgf[cov_labels]['val_comb'][val_key[0]] == 0: - stats.ucovpt.append(str(val_key[0])) - stats.covpt.append(str(val_key[0])) - cgf[cov_labels]['val_comb'][val_key[0]] += 1 - elif instr.instr_name in one_operand_dinstructions: - if instr.instr_name not in ["fcvt.d.l","fcvt.d.lu","fcvt.d.w","fcvt.d.wu","fmv.d.x"]: - val_key = fmt.extract_fields(64, rs1_val, str(1)) - else: - val_key = "rs1_val == "+ str(rs1_val) - val_key+= " and " - val_key+= 'rm_val == '+ str(rm_val) - val_key+= ' #nosat' - l=[0] - l[0] = val_key - val_key = l - if(val_key[0] in cgf[cov_labels]['val_comb']): - if cgf[cov_labels]['val_comb'][val_key[0]] == 0: - stats.ucovpt.append(str(val_key[0])) - stats.covpt.append(str(val_key[0])) - cgf[cov_labels]['val_comb'][val_key[0]] += 1 - elif instr.instr_name in three_operand_dinstructions: - val_key = fmt.extract_fields(64, rs1_val, str(1)) - val_key+= " and " - val_key+= fmt.extract_fields(64, rs2_val, str(2)) - val_key+= " and " - val_key+= fmt.extract_fields(64, rs3_val, str(3)) - val_key+= " and " - val_key+= 'rm_val == '+ str(rm_val) - val_key+= ' #nosat' - l=[0] - l[0] = val_key - val_key = l - if(val_key[0] in cgf[cov_labels]['val_comb']): - if cgf[cov_labels]['val_comb'][val_key[0]] == 0: - stats.ucovpt.append(str(val_key[0])) - stats.covpt.append(str(val_key[0])) - cgf[cov_labels]['val_comb'][val_key[0]] += 1 - else: - for coverpoints in value['val_comb']: - if type(rs1_val) is str: - if '0x' in rs1_val: - rs1_val = int(rs1_val,16) - else: - rs1_val = int(rs1_val) - if eval(coverpoints): - if cgf[cov_labels]['val_comb'][coverpoints] == 0: - stats.ucovpt.append(str(coverpoints)) - stats.covpt.append(str(coverpoints)) - cgf[cov_labels]['val_comb'][coverpoints] += 1 - if 'abstract_comb' in value \ - and len(value['abstract_comb']) != 0 : - for coverpoints in value['abstract_comb']: - if eval(coverpoints): - if cgf[cov_labels]['abstract_comb'][coverpoints] == 0: - stats.ucovpt.append(str(coverpoints)) - stats.covpt.append(str(coverpoints)) - cgf[cov_labels]['abstract_comb'][coverpoints] += 1 - - if 'csr_comb' in value and len(value['csr_comb']) != 0: - for coverpoints in value['csr_comb']: - if eval(coverpoints, {"__builtins__":None}, local_dict): - if cgf[cov_labels]['csr_comb'][coverpoints] == 0: - stats.ucovpt.append(str(coverpoints)) - stats.covpt.append(str(coverpoints)) - cgf[cov_labels]['csr_comb'][coverpoints] += 1 - elif 'opcode' not in value: - if 'csr_comb' in value and len(value['csr_comb']) != 0: - for coverpoints in value['csr_comb']: - if eval(coverpoints, {"__builtins__":None}, local_dict): - if cgf[cov_labels]['csr_comb'][coverpoints] == 0: - stats.ucovpt.append(str(coverpoints)) - stats.covpt.append(str(coverpoints)) - cgf[cov_labels]['csr_comb'][coverpoints] += 1 - - if stats.covpt: - if mnemonic is not None : - stats.code_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + mnemonic) - else: - stats.code_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + instr.instr_name) - if stats.ucovpt: - if mnemonic is not None : - stats.ucode_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + mnemonic) - else: - stats.ucode_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + instr.instr_name) - - if instr.instr_name in ['sh','sb','sw','sd','c.sw','c.sd','c.swsp','c.sdsp'] and sig_addrs: - store_address = rs1_val + imm_val - store_val = '0x'+arch_state.x_rf[rs2] - for start, end in sig_addrs: - if store_address >= start and store_address <= end: - logger.debug('Signature update : ' + str(hex(store_address))) - stats.stat5.append((store_address, store_val, stats.ucovpt, stats.code_seq)) - stats.cov_pt_sig += stats.covpt - if stats.ucovpt: - stats.stat1.append((store_address, store_val, stats.ucovpt, stats.ucode_seq)) - stats.last_meta = [store_address, store_val, stats.ucovpt, stats.ucode_seq] - stats.ucovpt = [] - elif stats.covpt: - _log = 'Op without unique coverpoint updates Signature\n' - _log += ' -- Code Sequence:\n' - for op in stats.code_seq: - _log += ' ' + op + '\n' - _log += ' -- Signature Address: {0} Data: {1}\n'.format( - str(hex(store_address)), store_val) - _log += ' -- Redundant Coverpoints hit by the op\n' - for c in stats.covpt: - _log += ' - ' + str(c) + '\n' - logger.warn(_log) - stats.stat2.append(_log + '\n\n') - stats.last_meta = [store_address, store_val, stats.covpt, stats.code_seq] - else: - if len(stats.last_meta) != 0: - _log = 'Last Coverpoint : ' + str(stats.last_meta[2]) + '\n' - _log += 'Last Code Sequence : \n\t-' + '\n\t-'.join(stats.last_meta[3]) + '\n' - else: - _log = 'Signature Update without any coverpoints hit' - _log +='Current Store : [{0}] : {1} -- Store: [{2}]:{3}\n'.format(\ - str(hex(instr.instr_addr)), mnemonic, - str(hex(store_address)), - store_val) - logger.error(_log) - stats.stat4.append(_log + '\n\n') - stats.covpt = [] - stats.code_seq = [] - stats.ucode_seq = [] - - - if commitvalue is not None: - if rd_type == 'x': - arch_state.x_rf[int(commitvalue[1])] = str(commitvalue[2][2:]) - elif rd_type == 'f': - arch_state.f_rf[int(commitvalue[1])] = str(commitvalue[2][2:]) - - csr_commit = instr.csr_commit - if csr_commit is not None: - for commits in csr_commit: - if(commits[0]=="CSR"): - csr_regfile[commits[1]] = str(commits[2][2:]) - - - return cgf - -def compute(trace_file, test_name, cgf, parser_name, decoder_name, detailed, xlen, addr_pairs - , dump, cov_labels, sig_addrs): - '''Compute the Coverage''' - - global arch_state - global csr_regfile - global stats - - temp = cgf.copy() - if cov_labels: - for groups in cgf: - if groups not in cov_labels: - del temp[groups] - cgf = temp - - if dump is not None: - dump_f = open(dump, 'w') - dump_f.write(ruamel.yaml.round_trip_dump(cgf, indent=5, block_seq_indent=3)) - dump_f.close() - sys.exit(0) - - arch_state = archState(xlen,32) - csr_regfile = csr_registers(xlen) - stats = statistics(xlen, 32) - - parser_pm = pluggy.PluginManager("parser") - parser_pm.add_hookspecs(ParserSpec) - try: - parserfile = importlib.import_module(parser_name) - except ImportError: - logger.error('Parser name invalid!') - raise SystemExit - parserclass = getattr(parserfile, parser_name) - parser_pm.register(parserclass()) - parser = parser_pm.hook - parser.setup(trace=trace_file,arch="rv"+str(xlen)) - - decoder_pm = pluggy.PluginManager("decoder") - decoder_pm.add_hookspecs(DecoderSpec) - try: - instructionObjectfile = importlib.import_module(decoder_name) - except ImportError: - logger.error('Decoder name invalid!') - raise SystemExit - decoderclass = getattr(instructionObjectfile, "disassembler") - decoder_pm.register(decoderclass()) - decoder = decoder_pm.hook - decoder.setup(arch="rv"+str(xlen)) - - iterator = iter(parser.__iter__()[0]) - for instrObj_temp in iterator: - instr = instrObj_temp.instr - if instr is None: - continue - instrObj = (decoder.decode(instrObj_temp = instrObj_temp))[0] - logger.debug(instrObj) - rcgf = compute_per_line(instrObj, cgf, xlen, - addr_pairs, sig_addrs) - - rpt_str = gen_report(rcgf, detailed) - logger.info('Writing out updated cgf : ' + test_name + '.cgf') - dump_file = open(test_name+'.cgf', 'w') - dump_file.write(ruamel.yaml.round_trip_dump(rcgf, indent=5, block_seq_indent=3)) - dump_file.close() - - if sig_addrs: - logger.info('Creating Data Propagation Report : ' + test_name + '.md') - writer = pytablewriter.MarkdownTableWriter() - writer.headers = ["s.no","signature", "coverpoints", "code"] - total_categories = 0 - for cov_labels, value in cgf.items(): - if cov_labels != 'datasets': - # rpt_str += cov_labels + ':\n' - total_uncovered = 0 - total_categories = 0 - for categories in value: - if categories not in ['cond','config','ignore', 'total_coverage', 'coverage']: - for coverpoints, coverage in value[categories].items(): - if coverage == 0: - total_uncovered += 1 - total_categories += len(value[categories]) - - addr_pairs_hex = [] - for x in addr_pairs: - _x = (hex(x[0]), hex(x[1])) - addr_pairs_hex.append(_x) - sig_addrs_hex = [] - for x in sig_addrs: - if xlen == 64: - _x = (hex(x[0]), hex(x[1]), str(int((x[1]-x[0])/8)) + ' dwords') - else: - _x = (hex(x[0]), hex(x[1]), str(int((x[1]-x[0])/4)) + ' words') - sig_addrs_hex.append(_x) - - cov_set = set() - count = 1 - stat5_log = [] - for addr,val,cover,code in stats.stat1: - sig = ('[{0}]
{1}'.format(str(hex(addr)), str(val))) - cov = '' - for c in cover: - cov += '- ' + str(c) + '
\n' - cov_set.add(c) - cod = '' - for i in code: - cod += str(i) + '
\n' - - row = [count, sig, cov, cod] - writer.value_matrix.append(row) - count += 1 - f =open(test_name+'.md','w') - if xlen == 64: - sig_count = 2*len(stats.stat5) - else: - sig_count = len(stats.stat5) - - stat2_log = '' - for _l in stats.stat2: - stat2_log += _l + '\n\n' - - stat4_log = '' - for _l in stats.stat4: - stat4_log += _l + '\n\n' - - stat3_log = '' - for _l in stats.stat3: - stat3_log += _l + '\n\n' - - stat5_log = '' - sig_set = set() - overwrites = 0 - for addr, val, cover, code in stats.stat5: - if addr in sig_set: - stat5_log += ('[{0}]
{1}'.format(str(hex(addr)), str(val))) - stat5_log += code + '\n\n' - overwrites += 1 - sig_set.add(addr) - logger.error('Found overwrite in Signature at Addr : ' + - str(addr)) - - f.write(dpr_template.format(str(xlen), - str(addr_pairs_hex), - str(sig_addrs_hex), - str(cov_labels), - test_name, - total_categories, - len(stats.stat5), - len(set(stats.cov_pt_sig)), - len(stats.stat1), - len(stats.stat2), - len(stats.stat3), - len(stats.stat4), - len(stat5_log), - stat2_log, - stat3_log, - stat4_log, - stat5_log)) - f.write(writer.dumps()) - f.close() - - return rpt_str - + +# See LICENSE.incore for details +# See LICENSE.iitm for details + +import ruamel +from ruamel.yaml import YAML +import riscv_isac.utils as utils +from riscv_isac.constants import * +from riscv_isac.log import logger +from collections import Counter +import sys +from riscv_isac.utils import yaml +from riscv_isac.cgf_normalize import * +import riscv_isac.fp_dataset as fmt +import struct +import pytablewriter +import importlib +import pluggy +import riscv_isac.plugins as plugins +from riscv_isac.plugins.specification import * +import math +import multiprocessing as mp +from collections.abc import MutableMapping + + +unsgn_rs1 = ['sw','sd','sh','sb','ld','lw','lwu','lh','lhu','lb', 'lbu','flw','fld','fsw','fsd'\ + 'bgeu', 'bltu', 'sltiu', 'sltu','c.lw','c.ld','c.lwsp','c.ldsp',\ + 'c.sw','c.sd','c.swsp','c.sdsp','mulhu','divu','remu','divuw',\ + 'remuw','aes64ds','aes64dsm','aes64es','aes64esm','aes64ks2',\ + 'sha256sum0','sha256sum1','sha256sig0','sha256sig1','sha512sig0',\ + 'sha512sum1r','sha512sum0r','sha512sig1l','sha512sig0l','sha512sig1h','sha512sig0h',\ + 'sha512sig1','sha512sum0','sha512sum1','sm3p0','sm3p1','aes64im',\ + 'sm4ed','sm4ks','ror','rol','rori','rorw','rolw','roriw','clmul','clmulh',\ + 'andn','orn','xnor','pack','packh','packu','packuw','packw',\ + 'xperm.n','xperm.b','grevi','aes64ks1i', 'shfli', 'unshfli', \ + 'aes32esmi', 'aes32esi', 'aes32dsmi', 'aes32dsi'] +unsgn_rs2 = ['bgeu', 'bltu', 'sltiu', 'sltu', 'sll', 'srl', 'sra','mulhu',\ + 'mulhsu','divu','remu','divuw','remuw','aes64ds','aes64dsm','aes64es',\ + 'aes64esm','aes64ks2','sm4ed','sm4ks','ror','rol','rorw','rolw','clmul',\ + 'clmulh','andn','orn','xnor','pack','packh','packu','packuw','packw',\ + 'xperm.n','xperm.b', 'aes32esmi', 'aes32esi', 'aes32dsmi', 'aes32dsi',\ + 'sha512sum1r','sha512sum0r','sha512sig1l','sha512sig1h','sha512sig0l','sha512sig0h'] + +class cross(): + + def __init__(self,label,coverpoint): + + self.label = label + self.coverpoint = coverpoint + self.result = 0 + + ## Extract relevant information from coverpt + self.data = self.coverpoint.split('::') + self.ops = [i for i in self.data[0][1:-1].split(':')] + self.assign_lst = [i for i in self.data[1][1:-1].split(':')] + self.cond_lst = [i for i in self.data[2][1:-1].split(':')] + + def process(self, queue, window_size, addr_pairs): + + ''' + Check whether the coverpoint is a hit or not and update the metric + ''' + if(len(self.ops)>window_size or len(self.ops)>len(queue)): + return + + for index in range(len(self.ops)): + instr = queue[index] + instr_name = instr.instr_name + if addr_pairs: + if not (any([instr.instr_addr >= saddr and instr.instr_addr < eaddr for saddr,eaddr in addr_pairs])): + continue + + rd = None + rs1 = None + rs2 = None + rs3 = None + imm = None + zimm = None + csr = None + shamt = None + succ = None + pred = None + rl = None + aq = None + rm = None + + if instr.rd is not None: + rd = int(instr.rd[0]) + if instr.rs1 is not None: + rs1 = int(instr.rs1[0]) + if instr.rs2 is not None: + rs2 = int(instr.rs2[0]) + if instr.rs3 is not None: + rs3 = int(instr.rs3[0]) + if instr.imm is not None: + imm = int(instr.imm) + if instr.zimm is not None: + zimm = int(instr.zimm) + if instr.csr is not None: + csr = instr.csr + if instr.shamt is not None: + shamt = int(instr.shamt) + if instr.succ is not None: + succ = int(instr.succ) + if instr.pred is not None: + pred = int(instr.pred) + if instr.rl is not None: + rl = int(instr.rl) + if instr.aq is not None: + aq = int(instr.aq) + if instr.rm is not None: + rm = int(instr.rm) + + + if(self.ops[index] != '?'): + check_lst = [i for i in self.ops[index][1:-1].split(',')] + if (instr_name not in check_lst): + break + if (self.cond_lst[index] != '?'): + if(eval(self.cond_lst[index])): + if(index==len(self.ops)-1): + self.result = self.result + 1 + else: + break + if(self.assign_lst[index] != '?'): + exec(self.assign_lst[index]) + + def get_metric(self): + return self.result + + +class csr_registers(MutableMapping): + ''' + Defines the architectural state of CSR Register file. + ''' + + def __init__ (self, xlen): + ''' + Class constructor + + :param xlen: max XLEN value of the RISC-V device + + :type xlen: int + + Currently defines the CSR register files the + width of which is defined by the xlen parameter. These are + implemented as an array holding the hexadecimal representations of the + values as string. These can be accessed by both integer addresses as well as string names + + ''' + + if(xlen==32): + self.csr = ['00000000']*4096 + self.csr[int('301',16)] = '40000000' # misa + else: + self.csr = ['0000000000000000']*4096 + self.csr[int('301',16)] = '8000000000000000' # misa + + # M-Mode CSRs + self.csr[int('F11',16)] = '00000000' # mvendorid + self.csr[int('306',16)] = '00000000' # mcounteren + self.csr[int('B00',16)] = '0000000000000000' # mcycle + self.csr[int('B02',16)] = '0000000000000000' # minstret + for i in range(29): # mphcounter 3-31, 3h-31h + self.csr[int('B03',16)+i] = '0000000000000000' + self.csr[int('B83',16)+i] = '00000000' + self.csr[int('320',16)] = '00000000' # mcounterinhibit + self.csr[int('B80',16)] = '00000000' # mcycleh + self.csr[int('B82',16)] = '00000000' # minstreth + + ## mtime, mtimecmp => 64 bits, platform defined memory mapping + + # S-Mode CSRs + self.csr[int('106',16)] = '00000000' # scounteren + + self.csr_regs={ + "mvendorid":int('F11',16), + "marchid":int('F12',16), + "mimpid":int('F13',16), + "mhartid":int('F14',16), + "mstatus":int('300',16), + "misa":int('301',16), + "medeleg":int('302',16), + "mideleg":int('303',16), + "mie":int('304',16), + "mtvec":int('305',16), + "mcounteren":int('306',16), + "mscratch":int('340',16), + "mepc":int('341',16), + "mcause":int('342',16), + "mtval":int('343',16), + "mip":int('344',16), + "pmpcfg0":int('3A0',16), + "pmpcfg1":int('3A1',16), + "pmpcfg2":int('3A2',16), + "pmpcfg3":int('3A3',16), + "mcycle":int('B00',16), + "minstret":int('B02',16), + "mcycleh":int('B80',16), + "minstreth":int('B82',16), + "mcountinhibit":int('320',16), + "tselect":int('7A0',16), + "tdata1":int('7A1',16), + "tdata2":int('7A2',16), + "tdata3":int('7A3',16), + "dcsr":int('7B0',16), + "dpc":int('7B1',16), + "dscratch0":int('7B2',16), + "dscratch1":int('7B3',16), + "sstatus": int('100',16), + "sedeleg": int('102',16), + "sideleg": int('103',16), + "sie": int('104',16), + "stvec": int('105',16), + "scounteren": int('106',16), + "sscratch": int('140',16), + "sepc": int('141',16), + "scause": int('142',16), + "stval": int('143',16), + "sip": int('144',16), + "satp": int('180',16) + } + for i in range(16): + self.csr_regs["pmpaddr"+str(i)] = int('3B0',16)+i + for i in range(3,32): + self.csr_regs["mhpmcounter"+str(i)] = int('B03',16) + (i-3) + self.csr_regs["mhpmcounter"+str(i)+"h"] = int('B83',16) + (i-3) + self.csr_regs["mhpmevent"+str(i)] = int('323',16) + (i-3) + + def __setitem__ (self,key,value): + + if(isinstance(key, str)): + self.csr[self.csr_regs[key]] = value + else: + self.csr[key] = value + + def __iter__(self): + for entry in self.csr_regs.keys(): + yield (entry,self.csr_regs[entry],self.csr[self.csr_regs[entry]]) + + def __len__(self): + return len(self.csr) + + def __delitem__(self,key): + pass + + def __getitem__ (self,key): + if(isinstance(key, str)): + return self.csr[self.csr_regs[key]] + else: + return self.csr[key] + +class archState: + ''' + Defines the architectural state of the RISC-V device. + ''' + + def __init__ (self, xlen, flen): + ''' + Class constructor + + :param xlen: max XLEN value of the RISC-V device + :param flen: max FLEN value of the RISC-V device + + :type xlen: int + :type flen: int + + Currently defines the integer and floating point register files the + width of which is defined by the xlen and flen parameters. These are + implemented as an array holding the hexadecimal representations of the + values as string. + + The program counter is also defined as an int. + + ''' + + if xlen == 32: + self.x_rf = ['00000000']*32 + else: + self.x_rf = ['0000000000000000']*32 + + if flen == 32: + self.f_rf = ['00000000']*32 + self.fcsr = 0 + else: + self.f_rf = ['0000000000000000']*32 + self.fcsr = 0 + self.pc = 0 + +class statistics: + ''' + Class for holding statistics used for Data propagation report + ''' + + def __init__(self, xlen, flen): + ''' + This class maintains a collection of arrays which are useful in + calculating the following set of statistics: + + - STAT1 : Number of instructions that hit unique coverpoints and update the signature. + - STAT2 : Number of instructions that hit covepoints which are not unique but still update the signature + - STAT3 : Number of instructions that hit a unique coverpoint but do not update signature + - STAT4 : Number of multiple signature updates for the same coverpoint + - STAT5 : Number of times the signature was overwritten + ''' + + + self.stat1 = [] + self.stat2 = [] + self.stat3 = [] + self.stat4 = [] + self.stat5 = [] + self.code_seq = [] + self.ucode_seq = [] + self.covpt = [] + self.ucovpt = [] + self.cov_pt_sig = [] + self.last_meta = [] + +def pretty_print_yaml(yaml): + res = '''''' + for line in ruamel.yaml.round_trip_dump(yaml, indent=5, block_seq_indent=3).splitlines(True): + res += line + return res + +def pretty_print_regfile(regfile): + res = "" + for index in range(0, 32, 4): + print('x'+str(index) + ' : ' + regfile[index] + '\t' +\ + 'x'+str(index+1) + ' : ' + regfile[index+1] + '\t' + \ + 'x'+str(index+2) + ' : ' + regfile[index+2] + '\t' + \ + 'x'+str(index+3) + ' : ' + regfile[index+3] + '\t' ) + print('\n\n') + +def gen_report(cgf, detailed): + ''' + Function to convert a CGF to a string report. A detailed report includes the individual coverpoints and the corresponding values of the same + + :param cgf: an input CGF dictionary + :param detailed: boolean value indicating a detailed report must be generated. + + :type cgf: dict + :type detailed: bool + + :return: string holding the final report + ''' + temp = cgf.copy() + for cov_labels, value in cgf.items(): + if cov_labels != 'datasets': + total_uncovered = 0 + total_categories = 0 + for categories in value: + if categories not in ['cond','config','ignore']: + for coverpoints, coverage in value[categories].items(): + if coverage == 0: + total_uncovered += 1 + total_categories += len(value[categories]) + for categories in value: + if categories not in ['cond','config','ignore']: + uncovered = 0 + for coverpoints, coverage in value[categories].items(): + if coverage == 0: + uncovered += 1 + percentage_covered = str((len(value[categories]) - uncovered)/len(value[categories])) + node_level_str = ' ' + categories + ':\n' + node_level_str += ' coverage: ' + \ + str(len(value[categories]) - uncovered) + \ + '/' + str(len(value[categories])) + temp[cov_labels][categories]['coverage'] = '{0}/{1}'.format(\ + str(len(value[categories]) - uncovered),\ + str(len(value[categories]))) + temp[cov_labels]['total_coverage'] = '{0}/{1}'.format(\ + str(total_categories-total_uncovered),\ + str(total_categories)) + return dict(temp) + +def merge_files(files,i,k): + ''' + Merges files from i to n where n is len(files) or i+k + + Arguments: + + files: List of dictionaries to be merged + i : beginning index to merge files on a given core + k : number of files to be merged + + ''' + + temp = files[i] + n = min(len(files),i+k) + for logs_cov in files[i+1:n]: + for cov_labels, value in logs_cov.items(): + if cov_labels not in temp: + temp[cov_labels] = value + continue + for categories in value: + if categories not in ['cond','config','ignore','total_coverage','coverage']: + if categories not in temp[cov_labels]: + temp[cov_labels][categories] = value[categories] + continue + for coverpoints, coverage in value[categories].items(): + if coverpoints not in temp[cov_labels][categories]: + temp[cov_labels][categories][coverpoints] = coverage + else: + temp[cov_labels][categories][coverpoints] += coverage + return temp + +def merge_fn(files, cgf, p): + + ''' + Each core is assigned ceil(n/k) processes where n is len(files) + ''' + + + pool_work = mp.Pool(processes = p) + while(len(files)>1): + n = len(files) + max_process = math.ceil(n/p) + if(max_process==1): + max_process = 2 + files = pool_work.starmap_async(merge_files,[(files,i,max_process) for i in range(0,n,max_process)]) + files = files.get() + pool_work.close() + pool_work.join() + + return files[0] + + +def merge_coverage(inp_files, cgf, detailed, xlen, p=1): + ''' + This function merges values of multiple CGF files and return a single cgf + file. This can be treated analogous to how coverage files are merged + traditionally. + + :param inp_files: an array of input CGF file names which need to be merged. + :param cgf: a cgf against which coverpoints need to be checked for. + :param detailed: a boolean value indicating if a detailed report needs to be generated + :param xlen: XLEN of the trace + :param p: Number of worker processes (>=1) + + :type inp_files: [str] + :type cgf: dict + :type detailed: bool + :type xlen: int + :type p: int + + :return: a string contain the final report of the merge. + ''' + files = [] + for logs in inp_files: + files.append(utils.load_yaml_file(logs)) + + temp = merge_fn(files,cgf,p) + for cov_labels, value in temp.items(): + for categories in value: + if categories not in ['cond','config','ignore','total_coverage','coverage']: + for coverpoints, coverage in value[categories].items(): + if coverpoints in cgf[cov_labels][categories]: + cgf[cov_labels][categories][coverpoints] += coverage + + return gen_report(cgf, detailed) + +def twos_complement(val,bits): + if (val & (1 << (bits - 1))) != 0: + val = val - (1 << bits) + return val + +def compute_per_line(instr, cgf, xlen, addr_pairs, sig_addrs): + ''' + This function checks if the current instruction under scrutiny matches a + particular coverpoint of interest. If so, it updates the coverpoints and + return the same. + + :param instr: an instructionObject of the single instruction currently parsed + :param cgf: a cgf against which coverpoints need to be checked for. + :param xlen: Max xlen of the trace + :param addr_pairs: pairs of start and end addresses for which the coverage needs to be updated + + :type instr: :class:`instructionObject` + :type cgf: dict + :type xlen: int + :type addr_pairs: (int, int) + ''' + global arch_state + global csr_regfile + global stats + + mnemonic = instr.mnemonic + commitvalue = instr.reg_commit + + # assign default values to operands + rs1 = 0 + rs2 = 0 + rs3 = 0 + rd = 0 + rs1_type = 'x' + rs2_type = 'x' + rs3_type = 'f' + rd_type = 'x' + + csr_addr = 0 + + # create signed/unsigned conversion params + if xlen == 32: + unsgn_sz = '>I' + sgn_sz = '>i' + else: + unsgn_sz = '>Q' + sgn_sz = '>q' + + # if instruction is empty then return + if instr is None: + return cgf + + # check if instruction lies within the valid region of interest + if addr_pairs: + if any([instr.instr_addr >= saddr and instr.instr_addr < eaddr for saddr,eaddr in addr_pairs]): + enable = True + else: + enable = False + else: + enable=True + + # capture the operands and their values from the regfile + if instr.rs1 is not None: + rs1 = instr.rs1[0] + rs1_type = instr.rs1[1] + if instr.rs2 is not None: + rs2 = instr.rs2[0] + rs2_type = instr.rs2[1] + if instr.rs3 is not None: + rs3 = instr.rs3[0] + rs3_type = instr.rs3[1] + + if instr.rd is not None: + rd = instr.rd[0] + is_rd_valid = True + rd_type = instr.rd[1] + else: + is_rd_valid = False + + if instr.imm is not None: + imm_val = instr.imm + if instr.shamt is not None: + imm_val = instr.shamt + + # special value conversion based on signed/unsigned operations + if instr.instr_name in unsgn_rs1: + rs1_val = struct.unpack(unsgn_sz, bytes.fromhex(arch_state.x_rf[rs1]))[0] + elif rs1_type == 'x': + rs1_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.x_rf[rs1]))[0] + if instr.instr_name in ["fmv.w.x"]: + rs1_val = '0x' + (arch_state.x_rf[rs1]).lower() + elif rs1_type == 'f': + rs1_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.f_rf[rs1]))[0] + if instr.instr_name in ["fadd.s","fsub.s","fmul.s","fdiv.s","fsqrt.s","fmadd.s","fmsub.s","fnmadd.s","fnmsub.s","fmax.s","fmin.s","feq.s","flt.s","fle.s","fmv.x.w","fmv.w.x","fcvt.wu.s","fcvt.s.wu","fcvt.w.s","fcvt.s.w","fsgnj.s","fsgnjn.s","fsgnjx.s","fclass.s"]: + rs1_val = '0x' + (arch_state.f_rf[rs1]).lower() + + if instr.instr_name in unsgn_rs2: + rs2_val = struct.unpack(unsgn_sz, bytes.fromhex(arch_state.x_rf[rs2]))[0] + elif rs2_type == 'x': + rs2_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.x_rf[rs2]))[0] + elif rs2_type == 'f': + rs2_val = struct.unpack(sgn_sz, bytes.fromhex(arch_state.f_rf[rs2]))[0] + if instr.instr_name in ["fadd.s","fsub.s","fmul.s","fdiv.s","fmadd.s","fmsub.s","fnmadd.s","fnmsub.s","fmax.s","fmin.s","feq.s","flt.s","fle.s","fsgnj.s","fsgnjn.s","fsgnjx.s"]: + rs2_val = '0x' + (arch_state.f_rf[rs2]).lower() + + if instr.instr_name in ["fmadd.s","fmsub.s","fnmadd.s","fnmsub.s"]: + rs3_val = '0x' + (arch_state.f_rf[rs3]).lower() + + if instr.instr_name in ['csrrwi']: + arch_state.fcsr = instr.zimm + + if instr.instr_name in ["fadd.s","fsub.s","fmul.s","fdiv.s","fsqrt.s","fmadd.s","fmsub.s","fnmadd.s","fnmsub.s","fmax.s","fmin.s","feq.s","flt.s","fle.s","fmv.x.w","fmv.w.x","fcvt.wu.s","fcvt.s.wu","fcvt.w.s","fcvt.s.w","fsgnj.s","fsgnjn.s","fsgnjx.s","fclass.s"]: + rm = instr.rm + if(rm==7 or rm==None): + rm_val = arch_state.fcsr + else: + rm_val = rm + + arch_state.pc = instr.instr_addr + + # the ea_align variable is used by the eval statements of the + # coverpoints for conditional ops and memory ops + if instr.instr_name in ['jal','bge','bgeu','blt','bltu','beq','bne']: + ea_align = (instr.instr_addr+(imm_val<<1)) % 4 + + if instr.instr_name == "jalr": + ea_align = (rs1_val + imm_val) % 4 + + if instr.instr_name in ['sw','sh','sb','lw','lhu','lh','lb','lbu','lwu','flw','fsw']: + ea_align = (rs1_val + imm_val) % 4 + if instr.instr_name in ['ld','sd']: + ea_align = (rs1_val + imm_val) % 8 + + local_dict={} + for i in csr_regfile.csr_regs: + local_dict[i] = int(csr_regfile[i],16) + + local_dict['xlen'] = xlen + + if enable : + for cov_labels,value in cgf.items(): + if cov_labels != 'datasets': + if 'opcode' in value: + if instr.instr_name in value['opcode']: + if stats.code_seq: + logger.error('Found a coverpoint without sign Upd ' + str(stats.code_seq)) + stats.stat3.append('\n'.join(stats.code_seq)) + stats.code_seq = [] + stats.covpt = [] + stats.ucovpt = [] + stats.ucode_seq = [] + + if value['opcode'][instr.instr_name] == 0: + stats.ucovpt.append('opcode : ' + instr.instr_name) + stats.covpt.append('opcode : ' + instr.instr_name) + value['opcode'][instr.instr_name] += 1 + if 'rs1' in value and 'x'+str(rs1) in value['rs1']: + if value['rs1']['x'+str(rs1)] == 0: + stats.ucovpt.append('rs1 : ' + 'x'+str(rs1)) + stats.covpt.append('rs1 : ' + 'x'+str(rs1)) + value['rs1']['x'+str(rs1)] += 1 + if 'rs2' in value and 'x'+str(rs2) in value['rs2']: + if value['rs2']['x'+str(rs2)] == 0: + stats.ucovpt.append('rs2 : ' + 'x'+str(rs2)) + stats.covpt.append('rs2 : ' + 'x'+str(rs2)) + value['rs2']['x'+str(rs2)] += 1 + if 'rd' in value and is_rd_valid and 'x'+str(rd) in value['rd']: + if value['rd']['x'+str(rd)] == 0: + stats.ucovpt.append('rd : ' + 'x'+str(rd)) + stats.covpt.append('rd : ' + 'x'+str(rd)) + value['rd']['x'+str(rd)] += 1 + + if 'rs1' in value and 'f'+str(rs1) in value['rs1']: + if value['rs1']['f'+str(rs1)] == 0: + stats.ucovpt.append('rs1 : ' + 'f'+str(rs1)) + stats.covpt.append('rs1 : ' + 'f'+str(rs1)) + value['rs1']['f'+str(rs1)] += 1 + if 'rs2' in value and 'f'+str(rs2) in value['rs2']: + if value['rs2']['f'+str(rs2)] == 0: + stats.ucovpt.append('rs2 : ' + 'f'+str(rs2)) + stats.covpt.append('rs2 : ' + 'f'+str(rs2)) + value['rs2']['f'+str(rs2)] += 1 + if 'rs3' in value and 'f'+str(rs3) in value['rs3']: + if value['rs3']['f'+str(rs3)] == 0: + stats.ucovpt.append('rs3 : ' + 'f'+str(rs3)) + stats.covpt.append('rs3 : ' + 'f'+str(rs3)) + value['rs3']['f'+str(rs3)] += 1 + if 'rd' in value and is_rd_valid and 'f'+str(rd) in value['rd']: + if value['rd']['f'+str(rd)] == 0: + stats.ucovpt.append('rd : ' + 'f'+str(rd)) + stats.covpt.append('rd : ' + 'f'+str(rd)) + value['rd']['f'+str(rd)] += 1 + + if 'op_comb' in value and len(value['op_comb']) != 0 : + for coverpoints in value['op_comb']: + if eval(coverpoints): + if cgf[cov_labels]['op_comb'][coverpoints] == 0: + stats.ucovpt.append(str(coverpoints)) + stats.covpt.append(str(coverpoints)) + cgf[cov_labels]['op_comb'][coverpoints] += 1 + if 'val_comb' in value and len(value['val_comb']) != 0: + if instr.instr_name in ['fadd.s',"fsub.s","fmul.s","fdiv.s","fmax.s","fmin.s","feq.s","flt.s","fle.s","fsgnj.s","fsgnjn.s","fsgnjx.s"]: + val_key = fmt.extract_fields(32, rs1_val, str(1)) + val_key+= " and " + val_key+= fmt.extract_fields(32, rs2_val, str(2)) + val_key+= " and " + val_key+= 'rm == '+ str(rm_val) + l=[0] + l[0] = val_key + val_key = l + if(val_key[0] in cgf[cov_labels]['val_comb']): + if cgf[cov_labels]['val_comb'][val_key[0]] == 0: + stats.ucovpt.append(str(val_key[0])) + stats.covpt.append(str(val_key[0])) + cgf[cov_labels]['val_comb'][val_key[0]] += 1 + elif instr.instr_name in ["fsqrt.s","fmv.x.w","fmv.w.x","fcvt.wu.s","fcvt.s.wu","fcvt.w.s","fcvt.s.w","fclass.s"]: + val_key = fmt.extract_fields(32, rs1_val, str(1)) + val_key+= " and " + val_key+= 'rm == '+ str(rm_val) + l=[0] + l[0] = val_key + val_key = l + if(val_key[0] in cgf[cov_labels]['val_comb']): + if cgf[cov_labels]['val_comb'][val_key[0]] == 0: + stats.ucovpt.append(str(val_key[0])) + stats.covpt.append(str(val_key[0])) + cgf[cov_labels]['val_comb'][val_key[0]] += 1 + elif instr.instr_name in ["fmadd.s","fmsub.s","fnmadd.s","fnmsub.s"]: + val_key = fmt.extract_fields(32, rs1_val, str(1)) + val_key+= " and " + val_key+= fmt.extract_fields(32, rs2_val, str(2)) + val_key+= " and " + val_key+= fmt.extract_fields(32, rs3_val, str(3)) + val_key+= " and " + val_key+= 'rm == '+ str(rm_val) + l=[0] + l[0] = val_key + val_key = l + if(val_key[0] in cgf[cov_labels]['val_comb']): + if cgf[cov_labels]['val_comb'][val_key[0]] == 0: + stats.ucovpt.append(str(val_key[0])) + stats.covpt.append(str(val_key[0])) + cgf[cov_labels]['val_comb'][val_key[0]] += 1 + else: + for coverpoints in value['val_comb']: + if eval(coverpoints): + if cgf[cov_labels]['val_comb'][coverpoints] == 0: + stats.ucovpt.append(str(coverpoints)) + stats.covpt.append(str(coverpoints)) + cgf[cov_labels]['val_comb'][coverpoints] += 1 + if 'abstract_comb' in value \ + and len(value['abstract_comb']) != 0 : + for coverpoints in value['abstract_comb']: + if eval(coverpoints): + if cgf[cov_labels]['abstract_comb'][coverpoints] == 0: + stats.ucovpt.append(str(coverpoints)) + stats.covpt.append(str(coverpoints)) + cgf[cov_labels]['abstract_comb'][coverpoints] += 1 + + if 'csr_comb' in value and len(value['csr_comb']) != 0: + for coverpoints in value['csr_comb']: + if eval(coverpoints, {"__builtins__":None}, local_dict): + if cgf[cov_labels]['csr_comb'][coverpoints] == 0: + stats.ucovpt.append(str(coverpoints)) + stats.covpt.append(str(coverpoints)) + cgf[cov_labels]['csr_comb'][coverpoints] += 1 + elif 'opcode' not in value: + if 'csr_comb' in value and len(value['csr_comb']) != 0: + for coverpoints in value['csr_comb']: + if eval(coverpoints, {"__builtins__":None}, local_dict): + if cgf[cov_labels]['csr_comb'][coverpoints] == 0: + stats.ucovpt.append(str(coverpoints)) + stats.covpt.append(str(coverpoints)) + cgf[cov_labels]['csr_comb'][coverpoints] += 1 + if stats.covpt: + if mnemonic is not None : + stats.code_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + mnemonic) + else: + stats.code_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + instr.instr_name) + if stats.ucovpt: + if mnemonic is not None : + stats.ucode_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + mnemonic) + else: + stats.ucode_seq.append('[' + str(hex(instr.instr_addr)) + ']:' + instr.instr_name) + + if instr.instr_name in ['sh','sb','sw','sd','c.sw','c.sd','c.swsp','c.sdsp'] and sig_addrs: + store_address = rs1_val + imm_val + store_val = '0x'+arch_state.x_rf[rs2] + for start, end in sig_addrs: + if store_address >= start and store_address <= end: + logger.debug('Signature update : ' + str(hex(store_address))) + stats.stat5.append((store_address, store_val, stats.ucovpt, stats.code_seq)) + stats.cov_pt_sig += stats.covpt + if stats.ucovpt: + stats.stat1.append((store_address, store_val, stats.ucovpt, stats.ucode_seq)) + stats.last_meta = [store_address, store_val, stats.ucovpt, stats.ucode_seq] + stats.ucovpt = [] + elif stats.covpt: + _log = 'Op without unique coverpoint updates Signature\n' + _log += ' -- Code Sequence:\n' + for op in stats.code_seq: + _log += ' ' + op + '\n' + _log += ' -- Signature Address: {0} Data: {1}\n'.format( + str(hex(store_address)), store_val) + _log += ' -- Redundant Coverpoints hit by the op\n' + for c in stats.covpt: + _log += ' - ' + str(c) + '\n' + logger.warn(_log) + stats.stat2.append(_log + '\n\n') + stats.last_meta = [store_address, store_val, stats.covpt, stats.code_seq] + else: + _log = 'Last Coverpoint : ' + str(stats.last_meta[2]) + '\n' + _log += 'Last Code Sequence : \n\t-' + '\n\t-'.join(stats.last_meta[3]) + '\n' + _log +='Current Store : [{0}] : {1} -- Store: [{2}]:{3}\n'.format(\ + str(hex(instr.instr_addr)), mnemonic, + str(hex(store_address)), + store_val) + logger.error(_log) + stats.stat4.append(_log + '\n\n') + stats.covpt = [] + stats.code_seq = [] + stats.ucode_seq = [] + + + + if commitvalue is not None: + if rd_type == 'x': + arch_state.x_rf[int(commitvalue[1])] = str(commitvalue[2][2:]) + elif rd_type == 'f': + arch_state.f_rf[int(commitvalue[1])] = str(commitvalue[2][2:]) + + csr_commit = instr.csr_commit + if csr_commit is not None: + for commits in csr_commit: + if(commits[0]=="CSR"): + csr_regfile[commits[1]] = str(commits[2][2:]) + + + return cgf + +def compute(trace_file, test_name, cgf, parser_name, decoder_name, detailed, xlen, addr_pairs + , dump, cov_labels, sig_addrs, window_size): + '''Compute the Coverage''' + + global arch_state + global csr_regfile + global stats + global cross_cover_queue + + temp = cgf.copy() + if cov_labels: + for groups in cgf: + if groups not in cov_labels: + del temp[groups] + cgf = temp + + if dump is not None: + dump_f = open(dump, 'w') + dump_f.write(ruamel.yaml.round_trip_dump(cgf, indent=5, block_seq_indent=3)) + dump_f.close() + sys.exit(0) + + arch_state = archState(xlen,32) + csr_regfile = csr_registers(xlen) + stats = statistics(xlen, 32) + cross_cover_queue = [] + + ## Get coverpoints from cgf + obj_dict = {} ## (label,coverpoint): object + for cov_labels,value in cgf.items(): + if cov_labels != 'datasets': + if 'cross_comb' in value and len(value['cross_comb'])!=0: + for coverpt in value['cross_comb'].keys(): + if(isinstance(coverpt,str)): + new_obj = cross(cov_labels,coverpt) + obj_dict[(cov_labels,coverpt)] = new_obj + + + parser_pm = pluggy.PluginManager("parser") + parser_pm.add_hookspecs(ParserSpec) + try: + parserfile = importlib.import_module(parser_name) + except ImportError: + logger.error('Parser name invalid!') + raise SystemExit + parserclass = getattr(parserfile, parser_name) + parser_pm.register(parserclass()) + parser = parser_pm.hook + parser.setup(trace=trace_file,arch="rv"+str(xlen)) + + decoder_pm = pluggy.PluginManager("decoder") + decoder_pm.add_hookspecs(DecoderSpec) + try: + instructionObjectfile = importlib.import_module(decoder_name) + except ImportError: + logger.error('Decoder name invalid!') + raise SystemExit + decoderclass = getattr(instructionObjectfile, "disassembler") + decoder_pm.register(decoderclass()) + decoder = decoder_pm.hook + decoder.setup(arch="rv"+str(xlen)) + + iterator = iter(parser.__iter__()[0]) + rcgf = cgf + for instrObj_temp in iterator: + instr = instrObj_temp.instr + if instr is None: + continue + instrObj = (decoder.decode(instrObj_temp = instrObj_temp))[0] + logger.debug(instrObj) + cross_cover_queue.append(instrObj) + if(len(cross_cover_queue)>=window_size): + for (label,coverpt) in obj_dict.keys(): + obj_dict[(label,coverpt)].process(cross_cover_queue, window_size,addr_pairs) + cross_cover_queue.pop(0) + rcgf = compute_per_line(instrObj, rcgf, xlen, + addr_pairs, sig_addrs) + + ## Check for cross coverage for end instructions + ## All metric is stored in objects of obj_dict + while(len(cross_cover_queue)>1): + for label,coverpt in obj_dict.keys(): + obj_dict[(label,coverpt)].process(cross_cover_queue, window_size,addr_pairs) + cross_cover_queue.pop(0) + + for label,coverpt in obj_dict.keys(): + metric = obj_dict[(label,coverpt)].get_metric() + if(metric!=0): + rcgf[label]['cross_comb'][coverpt] = metric + + rpt_str = gen_report(rcgf, detailed) + logger.info('Writing out updated cgf : ' + test_name + '.cgf') + dump_file = open(test_name+'.cgf', 'w') + dump_file.write(ruamel.yaml.round_trip_dump(rcgf, indent=5, block_seq_indent=3)) + dump_file.close() + + if sig_addrs: + logger.info('Creating Data Propagation Report : ' + test_name + '.md') + writer = pytablewriter.MarkdownTableWriter() + writer.headers = ["s.no","signature", "coverpoints", "code"] + for cov_labels, value in cgf.items(): + if cov_labels != 'datasets': + # rpt_str += cov_labels + ':\n' + total_uncovered = 0 + total_categories = 0 + for categories in value: + if categories not in ['cond','config','ignore', 'total_coverage', 'coverage']: + for coverpoints, coverage in value[categories].items(): + if coverage == 0: + total_uncovered += 1 + total_categories += len(value[categories]) + + addr_pairs_hex = [] + for x in addr_pairs: + _x = (hex(x[0]), hex(x[1])) + addr_pairs_hex.append(_x) + sig_addrs_hex = [] + for x in sig_addrs: + if xlen == 64: + _x = (hex(x[0]), hex(x[1]), str(int((x[1]-x[0])/8)) + ' dwords') + else: + _x = (hex(x[0]), hex(x[1]), str(int((x[1]-x[0])/4)) + ' words') + sig_addrs_hex.append(_x) + + cov_set = set() + count = 1 + stat5_log = [] + for addr,val,cover,code in stats.stat1: + sig = ('[{0}]
{1}'.format(str(hex(addr)), str(val))) + cov = '' + for c in cover: + cov += '- ' + str(c) + '
\n' + cov_set.add(c) + cod = '' + for i in code: + cod += str(i) + '
\n' + + row = [count, sig, cov, cod] + writer.value_matrix.append(row) + count += 1 + f =open(test_name+'.md','w') + if xlen == 64: + sig_count = 2*len(stats.stat5) + else: + sig_count = len(stats.stat5) + + stat2_log = '' + for _l in stats.stat2: + stat2_log += _l + '\n\n' + + stat4_log = '' + for _l in stats.stat4: + stat4_log += _l + '\n\n' + + stat3_log = '' + for _l in stats.stat3: + stat3_log += _l + '\n\n' + + stat5_log = '' + sig_set = set() + overwrites = 0 + for addr, val, cover, code in stats.stat5: + if addr in sig_set: + stat5_log += ('[{0}]
{1}'.format(str(hex(addr)), str(val))) + stat5_log += code + '\n\n' + overwrites += 1 + sig_set.add(addr) + logger.error('Found overwrite in Signature at Addr : ' + + str(addr)) + + f.write(dpr_template.format(str(xlen), + str(addr_pairs_hex), + str(sig_addrs_hex), + str(cov_labels), + test_name, + total_categories, + len(stats.stat5), + len(set(stats.cov_pt_sig)), + len(stats.stat1), + len(stats.stat2), + len(stats.stat3), + len(stats.stat4), + len(stat5_log), + stat2_log, + stat3_log, + stat4_log, + stat5_log)) + f.write(writer.dumps()) + f.close() + + return rpt_str + diff --git a/riscv_isac/isac.py b/riscv_isac/isac.py index f5a76f3..b87fd7b 100644 --- a/riscv_isac/isac.py +++ b/riscv_isac/isac.py @@ -5,7 +5,7 @@ import riscv_isac.coverage as cov from elftools.elf.elffile import ELFFile -def isac(output_file,elf ,trace_file, cgf, parser_name, decoder_name, parser_path, decoder_path, detailed, test_labels, +def isac(output_file,elf ,trace_file, window_size, cgf, parser_name, decoder_name, parser_path, decoder_path, detailed, test_labels, sig_labels, dump, cov_labels, xlen, logging=False): test_addr = [] sig_addr = [] @@ -38,7 +38,7 @@ def isac(output_file,elf ,trace_file, cgf, parser_name, decoder_name, parser_pat sig_addr.append((start_address,end_address)) else: test_name = trace_file.rsplit(',',1)[0] - rpt = cov.compute(trace_file, test_name, cgf, parser_name, decoder_name, detailed, xlen, test_addr, dump, cov_labels, sig_addr) + rpt = cov.compute(trace_file, test_name, cgf, parser_name, decoder_name, detailed, xlen, test_addr, dump, cov_labels, sig_addr, window_size) if output_file is not None and logging: logger.info('Coverage Report:') logger.info('\n\n' + rpt) diff --git a/riscv_isac/main.py b/riscv_isac/main.py index a929801..74bc205 100644 --- a/riscv_isac/main.py +++ b/riscv_isac/main.py @@ -27,6 +27,12 @@ def cli(verbose): help="Instruction trace file to be analyzed" ) +@click.option( + '--window-size', + type= int, + default = 32, + help="Maximum length of instructions to be evaluated for checking hazards" + ) @click.option( '--cgf-file','-c',multiple=True, type=click.Path(resolve_path=True,readable=True,exists=True), @@ -99,9 +105,9 @@ def cli(verbose): help = "Coverage labels to consider for this run." ) @click.option('--xlen','-x',type=click.Choice(['32','64']),default='32',help="XLEN value for the ISA.") -def coverage(elf,trace_file,cgf_file,detailed,parser_name, decoder_name, parser_path, decoder_path,output_file, test_label, +def coverage(elf,trace_file, window_size, cgf_file, detailed,parser_name, decoder_name, parser_path, decoder_path,output_file, test_label, sig_label, dump,cov_label, xlen): - isac(output_file,elf,trace_file, expand_cgf(cgf_file,int(xlen)), parser_name, decoder_name, parser_path, decoder_path, detailed, test_label, + isac(output_file,elf,trace_file, window_size, expand_cgf(cgf_file,int(xlen)), parser_name, decoder_name, parser_path, decoder_path, detailed, test_label, sig_label, dump, cov_label, int(xlen)) diff --git a/riscv_isac/plugins/internaldecoder.py b/riscv_isac/plugins/internaldecoder.py index 16487b9..fb9d793 100644 --- a/riscv_isac/plugins/internaldecoder.py +++ b/riscv_isac/plugins/internaldecoder.py @@ -591,7 +591,7 @@ def arith_ops(self, instrObj): instrObj.instr_name = 'and' return instrObj - + def fence_ops(self, instrObj): instr = instrObj.instr funct3 = (instr & self.FUNCT3_MASK) >> 12 @@ -690,7 +690,7 @@ def rv64i_arithi_ops(self, instrObj): instrObj.instr_name = 'sraiw' return instrObj - + def rv64m_arithm_ops(self, instrObj): instr = instrObj.instr funct3 = (instr & self.FUNCT3_MASK) >> 12 @@ -773,7 +773,7 @@ def rv64i_arith_ops(self, instrObj): instrObj.rd = rd return instrObj - + rv32a_instr_names = { 0b00010: 'lr.w', 0b00011: 'sc.w', @@ -1563,7 +1563,7 @@ def quad2(self, instrObj): instrObj.rs1 = (2 , 'x') return instrObj - + def parseCompressedInstruction(self, instrObj_temp): ''' Parse a compressed instruction diff --git a/setup.cfg b/setup.cfg index 747ba1b..e57daf2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.7.3 +current_version = 0.8.0 commit = True tag = True diff --git a/setup.py b/setup.py index 439c960..ff2e3b7 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def read_requires(): setup( name='riscv_isac', - version='0.7.3', + version='0.8.0', description="RISC-V ISAC", long_description=readme + '\n\n', classifiers=[