From 39a3931af7a7d8cf06baac17b5277c57894d5795 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Mon, 31 Oct 2022 18:52:32 +0530 Subject: [PATCH 01/13] add macro for testing a csr field with the given mask value --- riscv_ctg/env/arch_test.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/riscv_ctg/env/arch_test.h b/riscv_ctg/env/arch_test.h index 993e7679..8a34bc63 100644 --- a/riscv_ctg/env/arch_test.h +++ b/riscv_ctg/env/arch_test.h @@ -942,6 +942,18 @@ RVTEST_SIGUPD_F(swreg,destreg,flagreg) csrr DEST_REG,ADDRESS;\ RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) +#define TEST_CSR_FIELD_W_MASK(ADDRESS,TEMP_REG1,TEMP_REG2,MASK_VAL,VAL,DEST_REG,OFFSET,BASE_REG) \ + LI(TEMP_REG1,VAL);\ + LI(TEMP_REG2,MASK_VAL);\ + and TEMP_REG1,TEMP_REG1,TEMP_REG2;\ + csrr DEST_REG,ADDRESS;\ + not TEMP_REG2,TEMP_REG2;\ + and DEST_REG,DEST_REG,TEMP_REG2;\ + or TEMP_REG1,TEMP_REG1,DEST_REG;\ + csrw ADDRESS,TEMP_REG1;\ + csrr DEST_REG,ADDRESS;\ + RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) + #define TEST_CASE(testreg, destreg, correctval, swreg, offset, code... ) \ code; \ From 8dea1d2437a5de9b2f75db67cb808cf709079c43 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Mon, 31 Oct 2022 23:26:51 +0530 Subject: [PATCH 02/13] create a new class to aid in test generation for the csr_comb node --- riscv_ctg/csr_comb.py | 40 ++++++++++++++ riscv_ctg/ctg.py | 126 ++++++++++++++++++++++-------------------- 2 files changed, 106 insertions(+), 60 deletions(-) create mode 100644 riscv_ctg/csr_comb.py diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py new file mode 100644 index 00000000..3565425d --- /dev/null +++ b/riscv_ctg/csr_comb.py @@ -0,0 +1,40 @@ +# See LICENSE.incore for details +import re +from riscv_ctg.log import logger + +CSR_REGS = ['mvendorid', 'marchid', 'mimpid', 'mhartid', 'mstatus', 'misa', 'medeleg', 'mideleg', 'mie', 'mtvec', 'mcounteren', 'mscratch', 'mepc', 'mcause', 'mtval', 'mip', 'pmpcfg0', 'pmpcfg1', 'pmpcfg2', 'pmpcfg3', 'mcycle', 'minstret', 'mcycleh', 'minstreth', 'mcountinhibit', 'tselect', 'tdata1', 'tdata2', 'tdata3', 'dcsr', 'dpc', 'dscratch0', 'dscratch1', 'sstatus', 'sedeleg', 'sideleg', 'sie', 'stvec', 'scounteren', 'sscratch', 'sepc', 'scause', 'stval', 'sip', 'satp', 'vxsat', 'fflags', 'frm', 'fcsr'] + +csr_comb_covpt_regex_string = f'({"|".join(CSR_REGS)})' + r' *& *([^ ].*)== *([^ ].*)' +csr_comb_covpt_regex = re.compile(csr_comb_covpt_regex_string) + +class GeneratorCSRComb(): + ''' + A class to generate RISC-V assembly tests for CSR-combination coverpoints. + ''' + + def __init__(self, mxlen): + self.mxlen = mxlen + + def csr_comb(self, cgf_node): + logger.debug('Generating tests for csr_comb') + if 'csr_comb' in cgf_node: + csr_comb = set(cgf_node['csr_comb']) + else: + return + + # This function extracts the csr register, the field mask and the field value from the coverpoint + # The coverpoint is assumed of the format: 'csr_reg & mask == val' + # csr_reg must be a valid csr register; mask and val are allowed to be valid python expressions + def get_csr_reg_field_mask_and_val(coverpoint): + regex_match = csr_comb_covpt_regex.match(coverpoint.strip()) + if regex_match is None: + return None, None, None + csr_reg, mask, val = regex_match.groups() + return csr_reg, mask, val + + for covpt in csr_comb: + csr, mask, val = get_csr_reg_field_mask_and_val(covpt) + if csr is None: + logger.error(f'Invalid csr_comb coverpoint: {covpt}') + print(csr, mask, val) + diff --git a/riscv_ctg/ctg.py b/riscv_ctg/ctg.py index 500a6d79..0cc33e8b 100644 --- a/riscv_ctg/ctg.py +++ b/riscv_ctg/ctg.py @@ -12,85 +12,91 @@ from riscv_isac.cgf_normalize import expand_cgf from riscv_ctg.generator import Generator from riscv_ctg.cross_comb import cross +from riscv_ctg.csr_comb import GeneratorCSRComb from math import * from riscv_ctg.__init__ import __version__ def create_test(usage_str, node,label,base_isa,max_inst, op_template, randomize, out_dir, xlen, flen): iflen = 0 - if 'mnemonics' not in node: - logger.warning("mnemonics node not found in covergroup: " + str(label)) + if 'mnemonics' not in node and 'csr_comb' not in node: + logger.warning("Neither mnemonics nor csr_comb node not found in covergroup: " + str(label)) return if 'ignore' in node: logger.info("Ignoring :" + str(label)) if node['ignore']: return - # Function to encompass checks and test generation - def gen_test(op_node, opcode): - iflen = 0 - if xlen not in op_node['xlen']: - logger.warning("Skipping {0} since its not supported in current XLEN:".format(opcode)) - return - if 'flen' in op_node: - if flen not in op_node['flen']: - logger.warning("Skipping {0} since its not supported in current FLEN({1}):".format(\ - opcode, flen)) + if 'mnemonics' in node: + # Function to encompass checks and test generation + def gen_test(op_node, opcode): + iflen = 0 + if xlen not in op_node['xlen']: + logger.warning("Skipping {0} since its not supported in current XLEN:".format(opcode)) return - iflen = min(op_node['flen']) - fprefix = os.path.join(out_dir,str(label)) - logger.info('Generating Test for :' + str(label) +"-" + opcode) - formattype = op_node['formattype'] - gen = Generator(formattype,op_node,opcode,randomize,xlen,flen,iflen,base_isa) - op_comb = gen.opcomb(node) - val_comb = gen.valcomb(node) - instr_dict = gen.correct_val( - gen.valreg( - gen.testreg( - gen.swreg( - gen.gen_inst(op_comb, val_comb, node))))) - logger.info("Writing tests for :"+str(label)) - my_dict = gen.reformat_instr(instr_dict) - gen.write_test(fprefix,node,label,my_dict, op_node, usage_str, max_inst) + if 'flen' in op_node: + if flen not in op_node['flen']: + logger.warning("Skipping {0} since its not supported in current FLEN({1}):".format(\ + opcode, flen)) + return + iflen = min(op_node['flen']) + fprefix = os.path.join(out_dir,str(label)) + logger.info('Generating Test for :' + str(label) +"-" + opcode) + formattype = op_node['formattype'] + gen = Generator(formattype,op_node,opcode,randomize,xlen,flen,iflen,base_isa) + op_comb = gen.opcomb(node) + val_comb = gen.valcomb(node) + instr_dict = gen.correct_val( + gen.valreg( + gen.testreg( + gen.swreg( + gen.gen_inst(op_comb, val_comb, node))))) + logger.info("Writing tests for :"+str(label)) + my_dict = gen.reformat_instr(instr_dict) + gen.write_test(fprefix,node,label,my_dict, op_node, usage_str, max_inst) - # If base_op defined in covergroup, extract corresponding template - # else go through the instructions defined in mnemonics label - op_node = None - if 'base_op' in node: - # Extract pseudo and base instructions - base_op = node['base_op'] - pseudop = list(node['mnemonics'].keys())[0] - if base_op in op_template and pseudop in op_template: - op_node = copy.deepcopy(op_template[base_op]) - pseudo_template = op_template[pseudop] + # If base_op defined in covergroup, extract corresponding template + # else go through the instructions defined in mnemonics label + op_node = None + if 'base_op' in node: + # Extract pseudo and base instructions + base_op = node['base_op'] + pseudop = list(node['mnemonics'].keys())[0] + if base_op in op_template and pseudop in op_template: + op_node = copy.deepcopy(op_template[base_op]) + pseudo_template = op_template[pseudop] - # Ovewrite/add nodes from pseudoinstruction template in base instruction template - for key, val in pseudo_template.items(): - op_node[key] = val + # Ovewrite/add nodes from pseudoinstruction template in base instruction template + for key, val in pseudo_template.items(): + op_node[key] = val - # Generate tests - gen_test(op_node, pseudop) - else: - for opcode in node['mnemonics']: - if opcode in op_template: - op_node = op_template[opcode] # Generate tests - gen_test(op_node, opcode) - else: - logger.warning(str(opcode) + " not found in template file. Skipping") - return + gen_test(op_node, pseudop) + else: + for opcode in node['mnemonics']: + if opcode in op_template: + op_node = op_template[opcode] + # Generate tests + gen_test(op_node, opcode) + else: + logger.warning(str(opcode) + " not found in template file. Skipping") + return - if 'cross_comb' in node: - fprefix = os.path.join(out_dir,str(label)) - cross_obj = cross(base_isa, xlen, randomize, label) - cross_instr_dict = cross_obj.cross_comb(node) - logger.info('Writing cross-comb test') - cross_obj.write_test(fprefix, node, usage_str, label, cross_instr_dict) + if 'cross_comb' in node: + fprefix = os.path.join(out_dir,str(label)) + cross_obj = cross(base_isa, xlen, randomize, label) + cross_instr_dict = cross_obj.cross_comb(node) + logger.info('Writing cross-comb test') + cross_obj.write_test(fprefix, node, usage_str, label, cross_instr_dict) - # Return if there is no corresponding template + if op_node is None: + # Return if there is no corresponding template + logger.warning("Skipping :" + str(opcode)) + return - if op_node is None: - logger.warning("Skipping :" + str(opcode)) - return + if 'csr_comb' in node: + csr_comb_gen = GeneratorCSRComb(xlen) + csr_comb_instr_dict = csr_comb_gen.csr_comb(node) + logger.info('Writing tests for csr_comb') def ctg(verbose, out, random ,xlen_arg,flen_arg, cgf_file,num_procs,base_isa, max_inst): logger.level(verbose) From 004bcc975a4ce8eb2455951f32fa35fbfb76662f Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Wed, 2 Nov 2022 23:08:07 +0530 Subject: [PATCH 03/13] restore the original value of the csr register after writing to it in the TEST_CSR_FIELD_W_MASK macro --- riscv_ctg/env/arch_test.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/riscv_ctg/env/arch_test.h b/riscv_ctg/env/arch_test.h index 8a34bc63..19f865b6 100644 --- a/riscv_ctg/env/arch_test.h +++ b/riscv_ctg/env/arch_test.h @@ -950,8 +950,9 @@ RVTEST_SIGUPD_F(swreg,destreg,flagreg) not TEMP_REG2,TEMP_REG2;\ and DEST_REG,DEST_REG,TEMP_REG2;\ or TEMP_REG1,TEMP_REG1,DEST_REG;\ - csrw ADDRESS,TEMP_REG1;\ + csrrw TEMP_REG2,ADDRESS,TEMP_REG1;\ csrr DEST_REG,ADDRESS;\ + csrw ADDRESS,TEMP_REG2;\ RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) From 7b8bd4928c171ca3691149d3dc6674a68ae10c2b Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Thu, 3 Nov 2022 00:17:26 +0530 Subject: [PATCH 04/13] generate and write tests for csr_comb --- riscv_ctg/constants.py | 32 ++++++++++++++++++++++ riscv_ctg/csr_comb.py | 61 +++++++++++++++++++++++++++++++++++++----- riscv_ctg/ctg.py | 4 ++- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/riscv_ctg/constants.py b/riscv_ctg/constants.py index dfbe8c60..6f25af4f 100644 --- a/riscv_ctg/constants.py +++ b/riscv_ctg/constants.py @@ -221,6 +221,10 @@ def gen_bitmanip_dataset(bit_width,sign=True): // This assembly file is used for the test of cross-combination coverpoint described in $label covergroup. ''' +csr_comb_comment_template = ''' +// This assembly file is used for the test of CSR-combination coverpoint described in $label covergroup. +''' + test_template = Template(copyright_string + comment_template+''' #include "model_test.h" #include "arch_test.h" @@ -277,6 +281,30 @@ def gen_bitmanip_dataset(bit_width,sign=True): RVMODEL_DATA_END ''') +csr_comb_test_template = Template(copyright_string + csr_comb_comment_template + ''' +#include "model_test.h" +#include "arch_test.h" +RVTEST_ISA("$isa") + +.section .text.init +.globl rvtest_entry_point +rvtest_entry_point: +RVMODEL_BOOT +RVTEST_CODE_BEGIN +$test + +RVTEST_CODE_END +RVMODEL_HALT + +RVTEST_DATA_BEGIN +$data +RVTEST_DATA_END + +RVMODEL_DATA_BEGIN +$sig +RVMODEL_DATA_END +''') + case_template = Template(''' RVTEST_CASE($num,"//$cond;def TEST_CASE_1=True;",$cov_label) ''') @@ -293,3 +321,7 @@ def gen_bitmanip_dataset(bit_width,sign=True): .fill $n*$sz,4,0xdeadbeef ''') +csr_reg_write_test_template = Template(''' +// $csr_reg & $mask == $val +TEST_CSR_FIELD_W_MASK($csr_reg, $temp_reg1, $temp_reg2, $mask, $val, $dest_reg, $offset, $base_reg) +''') diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index 3565425d..ab0e5591 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -1,6 +1,8 @@ # See LICENSE.incore for details import re + from riscv_ctg.log import logger +from riscv_ctg.constants import * CSR_REGS = ['mvendorid', 'marchid', 'mimpid', 'mhartid', 'mstatus', 'misa', 'medeleg', 'mideleg', 'mie', 'mtvec', 'mcounteren', 'mscratch', 'mepc', 'mcause', 'mtval', 'mip', 'pmpcfg0', 'pmpcfg1', 'pmpcfg2', 'pmpcfg3', 'mcycle', 'minstret', 'mcycleh', 'minstreth', 'mcountinhibit', 'tselect', 'tdata1', 'tdata2', 'tdata3', 'dcsr', 'dpc', 'dscratch0', 'dscratch1', 'sstatus', 'sedeleg', 'sideleg', 'sie', 'stvec', 'scounteren', 'sscratch', 'sepc', 'scause', 'stval', 'sip', 'satp', 'vxsat', 'fflags', 'frm', 'fcsr'] @@ -12,8 +14,10 @@ class GeneratorCSRComb(): A class to generate RISC-V assembly tests for CSR-combination coverpoints. ''' - def __init__(self, mxlen): - self.mxlen = mxlen + def __init__(self, base_isa, xlen, randomize): + self.base_isa = base_isa + self.xlen = xlen + self.randomize = randomize def csr_comb(self, cgf_node): logger.debug('Generating tests for csr_comb') @@ -29,12 +33,57 @@ def get_csr_reg_field_mask_and_val(coverpoint): regex_match = csr_comb_covpt_regex.match(coverpoint.strip()) if regex_match is None: return None, None, None - csr_reg, mask, val = regex_match.groups() + csr_reg, mask_expr, val_expr = regex_match.groups() + mask = eval(mask_expr) + val = eval(val_expr) return csr_reg, mask, val + temp_regs = ['x28', 'x29'] # t0 and t1 + dest_reg = 'x23' + + instr_dict = [] + offset = 0 for covpt in csr_comb: - csr, mask, val = get_csr_reg_field_mask_and_val(covpt) - if csr is None: + csr_reg, mask, val = get_csr_reg_field_mask_and_val(covpt) + if csr_reg is None: logger.error(f'Invalid csr_comb coverpoint: {covpt}') - print(csr, mask, val) + continue + instr_dict.append({ + 'csr_reg': csr_reg, 'mask': hex(mask), 'val': hex(val), 'dest_reg': dest_reg, + 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1], 'offset': offset + }) + offset += 4 + + return instr_dict + + def write_test(self, fprefix, cgf_node, usage_str, cov_label, instr_dict): + base_reg = 'x8' + + code = [""] + data = [".align 4","rvtest_data:",".word 0xbabecafe", \ + ".word 0xabecafeb", ".word 0xbecafeba", ".word 0xecafebab"] + sig = [""] + + sig_label = f"signature_{base_reg}_0" + sig.append(signode_template.safe_substitute(label = sig_label, n = len(instr_dict), sz = 'XLEN/32')) + code.append(f"RVTEST_SIGBASE({base_reg}, {sig_label})\n") + + for i, instr in enumerate(instr_dict): + code.extend([ + f"\ninst_{i}:", + csr_reg_write_test_template.safe_substitute({ + 'base_reg': base_reg, **instr + }) + ]) + + case_str = ''.join([case_template.safe_substitute(xlen = self.xlen, num = i, cov_label = cov_label) for i, cond in enumerate(cgf_node.get('config', []))]) + test_str = part_template.safe_substitute(case_str = case_str, code = '\n'.join(code)) + with open(fprefix + '_csr-comb.S', 'w') as fp: + fp.write(usage_str + csr_comb_test_template.safe_substitute( + isa = self.base_isa.upper(), # how to get the extensions? + test = test_str, + data = '\n'.join(data), + sig = '\n'.join(sig), + label = cov_label + )) diff --git a/riscv_ctg/ctg.py b/riscv_ctg/ctg.py index 0cc33e8b..9d7c0fa8 100644 --- a/riscv_ctg/ctg.py +++ b/riscv_ctg/ctg.py @@ -94,9 +94,11 @@ def gen_test(op_node, opcode): return if 'csr_comb' in node: - csr_comb_gen = GeneratorCSRComb(xlen) + fprefix = os.path.join(out_dir,str(label)) + csr_comb_gen = GeneratorCSRComb(base_isa, xlen, randomize) csr_comb_instr_dict = csr_comb_gen.csr_comb(node) logger.info('Writing tests for csr_comb') + csr_comb_gen.write_test(fprefix, node, usage_str, label, csr_comb_instr_dict) def ctg(verbose, out, random ,xlen_arg,flen_arg, cgf_file,num_procs,base_isa, max_inst): logger.level(verbose) From 9345cabf62ab64ff6290b8f9211277c60003aca8 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Mon, 28 Nov 2022 03:59:11 +0530 Subject: [PATCH 05/13] add functionality for parsing csr_comb coverpoints with multiple conditions combined with and/or --- riscv_ctg/csr_comb.py | 188 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index ab0e5591..b04a4844 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -4,11 +4,199 @@ from riscv_ctg.log import logger from riscv_ctg.constants import * +import tokenize as tkn +from io import BytesIO + +OPS = ['not', 'and', 'or'] +OP_PRIORITY = { + 'not': -1, + 'and': -2, + 'or' : -3, +} + CSR_REGS = ['mvendorid', 'marchid', 'mimpid', 'mhartid', 'mstatus', 'misa', 'medeleg', 'mideleg', 'mie', 'mtvec', 'mcounteren', 'mscratch', 'mepc', 'mcause', 'mtval', 'mip', 'pmpcfg0', 'pmpcfg1', 'pmpcfg2', 'pmpcfg3', 'mcycle', 'minstret', 'mcycleh', 'minstreth', 'mcountinhibit', 'tselect', 'tdata1', 'tdata2', 'tdata3', 'dcsr', 'dpc', 'dscratch0', 'dscratch1', 'sstatus', 'sedeleg', 'sideleg', 'sie', 'stvec', 'scounteren', 'sscratch', 'sepc', 'scause', 'stval', 'sip', 'satp', 'vxsat', 'fflags', 'frm', 'fcsr'] csr_comb_covpt_regex_string = f'({"|".join(CSR_REGS)})' + r' *& *([^ ].*)== *([^ ].*)' csr_comb_covpt_regex = re.compile(csr_comb_covpt_regex_string) +def tokenize(s): + result = [] + g = tkn.tokenize(BytesIO(s.encode('utf-8')).readline) + for tok_num, tok_val, _, _, _ in g: + if tok_num in [tkn.ENCODING, tkn.NEWLINE, tkn.ENDMARKER]: + continue + result.append((tok_num, tok_val)) + return result + +def untokenize(tokens): + return tkn.untokenize(tokens) + +# a dummy class acting as an interface for boolean expressions +class BooleanExpression: + def SAT(self): + # returns the complete list of solutions for this expression's satisfiability + # a single solution is a tuple of two lists: + # - the literals in the first list must evaluate to true + # - the literals in the second list must evaluate to false + raise Exception("not implemented") + + def __str__(self): + raise Exception("not implemented") + +class NotExpression(BooleanExpression): + def __init__(self, operand): + self.operand = operand + + def SAT(self): + return [(operand_f, operand_t) for operand_t, operand_f in self.operand.SAT()] + + def __str__(self): + return f'not ({str(self.operand)})' + +class AndExpression(BooleanExpression): + def __init__(self, lhs, rhs): + self.lhs = lhs + self.rhs = rhs + + def SAT(self): + return [(lhs_t + rhs_t, lhs_f + rhs_f) for lhs_t, lhs_f in self.lhs.SAT() for rhs_t, rhs_f in self.rhs.SAT()] + + def __str__(self): + return f'({str(self.lhs)}) and ({str(self.rhs)})' + +class OrExpression(BooleanExpression): + def __init__(self, lhs, rhs): + self.lhs = lhs + self.rhs = rhs + + def SAT(self): + lhs_SAT = self.lhs.SAT() + rhs_SAT = self.rhs.SAT() + + sols = [] + for lhs_t, lhs_f in lhs_SAT: + for rhs_t, rhs_f in rhs_SAT: + sols.extend([ + (lhs_t + rhs_f, lhs_f + rhs_t), + (lhs_f + rhs_t, lhs_t + rhs_f), + (lhs_t + rhs_t, lhs_f + rhs_f), + ]) + + return sols + + def __str__(self): + return f'({str(self.lhs)}) or ({str(self.rhs)})' + +class LiteralExpression(BooleanExpression): + def __init__(self, val): + self.val = val + + def SAT(self): + return [([self.val], [])] + + def __str__(self): + return str(self.val) + +def parse_csr_covpt(covpt): + toks = tokenize(covpt) + + bracket_depth = 0 + clause_depths = [] + for tok_num, tok_val in toks: + if tok_val == '(': + bracket_depth += 1 + elif tok_val == ')': + bracket_depth -= 1 + elif tok_val == '==': + clause_depths.append(bracket_depth) + + bracket_depth = 0 + operator_stack = [] + clause_stack = [] + clause_index = 0 + current_clause = [] + current_clause_depth = clause_depths[clause_index] + for tok_num, tok_val in toks: + if tok_val == '(': + bracket_depth += 1 + + if bracket_depth > current_clause_depth: + current_clause.append((tok_num, tok_val)) + else: + operator_stack.append('(') + elif tok_val == ')': + bracket_depth -= 1 + + if current_clause: + if bracket_depth < current_clause_depth: + clause_stack.append(LiteralExpression(untokenize(current_clause))) + while operator_stack[-1] != '(': + op = operator_stack.pop() + if op == 'not': + operand = clause_stack.pop() + clause_stack.append(NotExpression(operand)) + else: + rhs = clause_stack.pop() + lhs = clause_stack.pop() + clause_stack.append(AndExpression(lhs, rhs) if op == 'and' else OrExpression(lhs, rhs)) + operator_stack.pop() + + current_clause = [] + clause_index += 1 + if (clause_index >= len(clause_depths)): + break + current_clause_depth = clause_depths[clause_index] + else: + current_clause.append((tok_num, tok_val)) + else: + while operator_stack[-1] != '(': + op = operator_stack.pop() + if op == 'not': + operand = clause_stack.pop() + clause_stack.append(NotExpression(operand)) + else: + rhs = clause_stack.pop() + lhs = clause_stack.pop() + clause_stack.append(AndExpression(lhs, rhs) if op == 'and' else OrExpression(lhs, rhs)) + operator_stack.pop() + elif tok_val in OPS: + if current_clause: + clause_stack.append(LiteralExpression(untokenize(current_clause))) + current_clause = [] + clause_index += 1 + current_clause_depth = clause_depths[clause_index] + + # prioritize not over and over or + while len(operator_stack) > 0 and operator_stack[-1] in OPS and OP_PRIORITY[operator_stack[-1]] > OP_PRIORITY[tok_val]: + op = operator_stack.pop() + if op == 'not': + operand = clause_stack.pop() + clause_stack.append(NotExpression(operand)) + else: + rhs = clause_stack.pop() + lhs = clause_stack.pop() + clause_stack.append(AndExpression(lhs, rhs) if op == 'and' else OrExpression(lhs, rhs)) + + operator_stack.append(tok_val) + else: + current_clause.append((tok_num, tok_val)) + + if current_clause: + clause_stack.append(LiteralExpression(untokenize(current_clause))) + + while len(operator_stack) > 0: + op = operator_stack.pop() + if op == 'not': + operand = clause_stack.pop() + clause_stack.append(NotExpression(operand)) + else: + rhs = clause_stack.pop() + lhs = clause_stack.pop() + clause_stack.append(AndExpression(lhs, rhs) if op == 'and' else OrExpression(lhs, rhs)) + + bool_expr = clause_stack.pop() + return bool_expr + class GeneratorCSRComb(): ''' A class to generate RISC-V assembly tests for CSR-combination coverpoints. From 65db465b933878f3442c0637b89b621241011379 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Thu, 1 Dec 2022 22:45:19 +0530 Subject: [PATCH 06/13] add support for generating tests for csr_comb coverpoints containing multiple conditions combined using and/or --- riscv_ctg/constants.py | 13 ++++- riscv_ctg/csr_comb.py | 117 ++++++++++++++++++++++++++++---------- riscv_ctg/env/arch_test.h | 17 ++++++ 3 files changed, 115 insertions(+), 32 deletions(-) diff --git a/riscv_ctg/constants.py b/riscv_ctg/constants.py index 6f25af4f..3f3f8017 100644 --- a/riscv_ctg/constants.py +++ b/riscv_ctg/constants.py @@ -321,7 +321,14 @@ def gen_bitmanip_dataset(bit_width,sign=True): .fill $n*$sz,4,0xdeadbeef ''') -csr_reg_write_test_template = Template(''' -// $csr_reg & $mask == $val -TEST_CSR_FIELD_W_MASK($csr_reg, $temp_reg1, $temp_reg2, $mask, $val, $dest_reg, $offset, $base_reg) +csr_reg_write_to_field_template = Template(''' +WRITE_TO_CSR_FIELD_W_MASK($csr_reg, $restore_reg, $temp_reg1, $temp_reg2, $mask, $val) +''') + +csr_reg_read_and_sig_upd_template = Template(''' +READ_CSR_REG_AND_UPD_SIG($csr_reg, $dest_reg, $offset, $base_reg) +''') + +csr_reg_restore_template = Template(''' +RESTORE_CSR_REG($csr_reg, $restore_reg) ''') diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index b04a4844..b31e21f2 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -1,5 +1,6 @@ # See LICENSE.incore for details import re +import functools from riscv_ctg.log import logger from riscv_ctg.constants import * @@ -97,6 +98,9 @@ def SAT(self): def __str__(self): return str(self.val) +# This function parses coverpoints for the CSR-combination node +# The coverpoints are assumed of the form: multiple condition clauses combined with and's and or's +# A coverpoint condition clause is assumed of the form: 'csr_reg & mask == val' def parse_csr_covpt(covpt): toks = tokenize(covpt) @@ -197,6 +201,18 @@ def parse_csr_covpt(covpt): bool_expr = clause_stack.pop() return bool_expr +# This function extracts the csr register, the field mask and the field value from the coverpoint clause +# The coverpoint clause is assumed of the format: 'csr_reg & mask == val' +# csr_reg must be a valid csr register; mask and val are allowed to be valid python expressions +def get_csr_reg_field_mask_and_val(clause): + regex_match = csr_comb_covpt_regex.match(clause.strip()) + if regex_match is None: + return None, None, None + csr_reg, mask_expr, val_expr = regex_match.groups() + mask = eval(mask_expr) + val = eval(val_expr) + return csr_reg, mask, val + class GeneratorCSRComb(): ''' A class to generate RISC-V assembly tests for CSR-combination coverpoints. @@ -214,38 +230,62 @@ def csr_comb(self, cgf_node): else: return - # This function extracts the csr register, the field mask and the field value from the coverpoint - # The coverpoint is assumed of the format: 'csr_reg & mask == val' - # csr_reg must be a valid csr register; mask and val are allowed to be valid python expressions - def get_csr_reg_field_mask_and_val(coverpoint): - regex_match = csr_comb_covpt_regex.match(coverpoint.strip()) - if regex_match is None: - return None, None, None - csr_reg, mask_expr, val_expr = regex_match.groups() - mask = eval(mask_expr) - val = eval(val_expr) - return csr_reg, mask, val - - temp_regs = ['x28', 'x29'] # t0 and t1 - dest_reg = 'x23' + temp_regs = ['x30', 'x31'] + dest_reg = 'x29' instr_dict = [] offset = 0 + for covpt in csr_comb: - csr_reg, mask, val = get_csr_reg_field_mask_and_val(covpt) - if csr_reg is None: + try: + bool_expr = parse_csr_covpt(covpt) + sols = bool_expr.SAT() + except: logger.error(f'Invalid csr_comb coverpoint: {covpt}') continue - instr_dict.append({ - 'csr_reg': csr_reg, 'mask': hex(mask), 'val': hex(val), 'dest_reg': dest_reg, - 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1], 'offset': offset - }) - offset += 4 + + for sol, _ in sols: + reg_mask_val_arr = [] + for clause in sol: + csr_reg, mask, val = get_csr_reg_field_mask_and_val(clause) + if csr_reg is None: + logger.error(f'Skipping invalid csr_comb coverpoint condition clause: {clause}') + continue + reg_mask_val_arr.append((csr_reg, mask, val)) + + reg_mask_val_arr.sort(key=functools.cmp_to_key(lambda x, y: 1)) + + instr_dict_csr_writes = [] + instr_dict_csr_restores = [] + uniq_csr_regs = [] + restore_reg = 1 + for csr_reg, mask, val in reg_mask_val_arr: + instr_dict_csr_writes.append({ + 'csr_reg': csr_reg, 'mask': hex(mask), 'val': hex(val), 'restore_reg': f'x{restore_reg}', + 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1] + }) + instr_dict_csr_restores.append({ + 'csr_reg': csr_reg, 'restore_reg': f'x{restore_reg}' + }) + restore_reg += 1 + if csr_reg not in uniq_csr_regs: + uniq_csr_regs.append(csr_reg) + + instr_dict_csr_restores.reverse() + + instr_dict_csr_read_and_sig_upds = [] + for csr_reg in uniq_csr_regs: + instr_dict_csr_read_and_sig_upds.append({ + 'csr_reg': csr_reg, 'dest_reg': dest_reg, 'offset': offset + }) + offset += 4 + + instr_dict.append((instr_dict_csr_writes, instr_dict_csr_read_and_sig_upds, instr_dict_csr_restores)) return instr_dict def write_test(self, fprefix, cgf_node, usage_str, cov_label, instr_dict): - base_reg = 'x8' + base_reg = 'x28' code = [""] data = [".align 4","rvtest_data:",".word 0xbabecafe", \ @@ -253,16 +293,35 @@ def write_test(self, fprefix, cgf_node, usage_str, cov_label, instr_dict): sig = [""] sig_label = f"signature_{base_reg}_0" - sig.append(signode_template.safe_substitute(label = sig_label, n = len(instr_dict), sz = 'XLEN/32')) + sig.append(signode_template.safe_substitute(label = sig_label, n = len(instr_dict), sz = '(XLEN/32)')) code.append(f"RVTEST_SIGBASE({base_reg}, {sig_label})\n") for i, instr in enumerate(instr_dict): - code.extend([ - f"\ninst_{i}:", - csr_reg_write_test_template.safe_substitute({ - 'base_reg': base_reg, **instr - }) - ]) + csr_writes, csr_read_sig_upds, csr_restores = instr + + for j, csr_write in enumerate(csr_writes): + code.extend([ + f"\ninst_{i}_csr_write_{j}:", + csr_reg_write_to_field_template.safe_substitute({ + 'base_reg': base_reg, **csr_write + }) + ]) + + for j, csr_read_sig_upd in enumerate(csr_read_sig_upds): + code.extend([ + f"\ninst_{i}_csr_read_sig_upd_{j}:", + csr_reg_read_and_sig_upd_template.safe_substitute({ + 'base_reg': base_reg, **csr_read_sig_upd + }) + ]) + + for j, csr_restore in enumerate(csr_restores): + code.extend([ + f"\ninst_{i}_csr_restore_{j}:", + csr_reg_restore_template.safe_substitute({ + 'base_reg': base_reg, **csr_restore + }) + ]) case_str = ''.join([case_template.safe_substitute(xlen = self.xlen, num = i, cov_label = cov_label) for i, cond in enumerate(cgf_node.get('config', []))]) test_str = part_template.safe_substitute(case_str = case_str, code = '\n'.join(code)) diff --git a/riscv_ctg/env/arch_test.h b/riscv_ctg/env/arch_test.h index 19f865b6..c3ada43d 100644 --- a/riscv_ctg/env/arch_test.h +++ b/riscv_ctg/env/arch_test.h @@ -955,6 +955,23 @@ RVTEST_SIGUPD_F(swreg,destreg,flagreg) csrw ADDRESS,TEMP_REG2;\ RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) +#define WRITE_TO_CSR_FIELD_W_MASK(ADDRESS,RESTORE_REG,TEMP_REG1,TEMP_REG2,MASK_VAL,VAL) \ + LI(TEMP_REG1,VAL);\ + LI(TEMP_REG2,MASK_VAL);\ + and TEMP_REG1,TEMP_REG1,TEMP_REG2;\ + csrr RESTORE_REG,ADDRESS;\ + not TEMP_REG2,TEMP_REG2;\ + and RESTORE_REG,RESTORE_REG,TEMP_REG2;\ + or TEMP_REG1,TEMP_REG1,RESTORE_REG;\ + csrrw RESTORE_REG,ADDRESS,TEMP_REG1;\ + +#define READ_CSR_REG_AND_UPD_SIG(ADDRESS,DEST_REG,OFFSET,BASE_REG) \ + csrr DEST_REG,ADDRESS;\ + RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) + +#define RESTORE_CSR_REG(ADDRESS,RESTORE_REG) \ + csrw ADDRESS,RESTORE_REG; + #define TEST_CASE(testreg, destreg, correctval, swreg, offset, code... ) \ code; \ From 0fecccdfebe45af3cda307cbce8f51eed4862471 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Fri, 2 Dec 2022 01:09:39 +0530 Subject: [PATCH 07/13] add support for bitshifts in csr_comb coverpoint condition clauses --- riscv_ctg/csr_comb.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index b31e21f2..b13f9c15 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -18,7 +18,9 @@ CSR_REGS = ['mvendorid', 'marchid', 'mimpid', 'mhartid', 'mstatus', 'misa', 'medeleg', 'mideleg', 'mie', 'mtvec', 'mcounteren', 'mscratch', 'mepc', 'mcause', 'mtval', 'mip', 'pmpcfg0', 'pmpcfg1', 'pmpcfg2', 'pmpcfg3', 'mcycle', 'minstret', 'mcycleh', 'minstreth', 'mcountinhibit', 'tselect', 'tdata1', 'tdata2', 'tdata3', 'dcsr', 'dpc', 'dscratch0', 'dscratch1', 'sstatus', 'sedeleg', 'sideleg', 'sie', 'stvec', 'scounteren', 'sscratch', 'sepc', 'scause', 'stval', 'sip', 'satp', 'vxsat', 'fflags', 'frm', 'fcsr'] csr_comb_covpt_regex_string = f'({"|".join(CSR_REGS)})' + r' *& *([^ ].*)== *([^ ].*)' +csr_comb_covpt_regex_w_bitshift_string = r'\( *' + f'({"|".join(CSR_REGS)})' + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)' csr_comb_covpt_regex = re.compile(csr_comb_covpt_regex_string) +csr_comb_covpt_regex_w_bitshift = re.compile(csr_comb_covpt_regex_w_bitshift_string) def tokenize(s): result = [] @@ -100,7 +102,7 @@ def __str__(self): # This function parses coverpoints for the CSR-combination node # The coverpoints are assumed of the form: multiple condition clauses combined with and's and or's -# A coverpoint condition clause is assumed of the form: 'csr_reg & mask == val' +# A coverpoint condition clause is assumed of the form: 'csr_reg & mask == val' or '(csr_reg >> shift) & mask == val' def parse_csr_covpt(covpt): toks = tokenize(covpt) @@ -202,16 +204,31 @@ def parse_csr_covpt(covpt): return bool_expr # This function extracts the csr register, the field mask and the field value from the coverpoint clause -# The coverpoint clause is assumed of the format: 'csr_reg & mask == val' +# The coverpoint clause is assumed of the format: 'csr_reg & mask == val' or '(csr_reg >> shift) & mask == val' # csr_reg must be a valid csr register; mask and val are allowed to be valid python expressions -def get_csr_reg_field_mask_and_val(clause): - regex_match = csr_comb_covpt_regex.match(clause.strip()) - if regex_match is None: - return None, None, None - csr_reg, mask_expr, val_expr = regex_match.groups() - mask = eval(mask_expr) - val = eval(val_expr) - return csr_reg, mask, val +def get_csr_reg_field_mask_and_val(clause, instr_dict={}): + clause = clause.strip() + regex_w_shift_match = csr_comb_covpt_regex_w_bitshift.match(clause) + if regex_w_shift_match is None: + regex_match = csr_comb_covpt_regex.match(clause) + if regex_match is None: + return None, None, None + csr_reg, mask_expr, val_expr = regex_match.groups() + mask = eval(mask_expr, {}, instr_dict) + val = eval(val_expr, {}, instr_dict) + return csr_reg, mask, val + else: + csr_reg, shift_op, shift_expr, mask_expr, val_expr = regex_w_shift_match.groups() + shift = eval(shift_expr, {}, instr_dict) + mask = eval(mask_expr, {}, instr_dict) + val = eval(val_expr, {}, instr_dict) + if shift_op == '>>': + mask = mask << shift + val = val << shift + else: + mask = mask >> shift + val = val >> shift + return csr_reg, mask, val class GeneratorCSRComb(): ''' @@ -247,7 +264,7 @@ def csr_comb(self, cgf_node): for sol, _ in sols: reg_mask_val_arr = [] for clause in sol: - csr_reg, mask, val = get_csr_reg_field_mask_and_val(clause) + csr_reg, mask, val = get_csr_reg_field_mask_and_val(clause, {'xlen': self.xlen}) if csr_reg is None: logger.error(f'Skipping invalid csr_comb coverpoint condition clause: {clause}') continue From df14a48ba7f12a121d991e92ec3c22ab2f3f23d0 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Fri, 2 Dec 2022 01:13:50 +0530 Subject: [PATCH 08/13] remove unused macro TEST_CSR_FIELD_W_MASK in arch_test.h --- riscv_ctg/env/arch_test.h | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/riscv_ctg/env/arch_test.h b/riscv_ctg/env/arch_test.h index c3ada43d..98f3f9ea 100644 --- a/riscv_ctg/env/arch_test.h +++ b/riscv_ctg/env/arch_test.h @@ -942,19 +942,6 @@ RVTEST_SIGUPD_F(swreg,destreg,flagreg) csrr DEST_REG,ADDRESS;\ RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) -#define TEST_CSR_FIELD_W_MASK(ADDRESS,TEMP_REG1,TEMP_REG2,MASK_VAL,VAL,DEST_REG,OFFSET,BASE_REG) \ - LI(TEMP_REG1,VAL);\ - LI(TEMP_REG2,MASK_VAL);\ - and TEMP_REG1,TEMP_REG1,TEMP_REG2;\ - csrr DEST_REG,ADDRESS;\ - not TEMP_REG2,TEMP_REG2;\ - and DEST_REG,DEST_REG,TEMP_REG2;\ - or TEMP_REG1,TEMP_REG1,DEST_REG;\ - csrrw TEMP_REG2,ADDRESS,TEMP_REG1;\ - csrr DEST_REG,ADDRESS;\ - csrw ADDRESS,TEMP_REG2;\ - RVTEST_SIGUPD(BASE_REG,DEST_REG,OFFSET) - #define WRITE_TO_CSR_FIELD_W_MASK(ADDRESS,RESTORE_REG,TEMP_REG1,TEMP_REG2,MASK_VAL,VAL) \ LI(TEMP_REG1,VAL);\ LI(TEMP_REG2,MASK_VAL);\ From 355ad7bd09ee6baaa783cf2c4fcf5660aacb7cbf Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Sat, 10 Dec 2022 00:00:18 +0530 Subject: [PATCH 09/13] add regex expressions for matching csr_comb coverpoints with the modifiers old and write --- riscv_ctg/csr_comb.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index b13f9c15..1648b279 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -16,11 +16,17 @@ } CSR_REGS = ['mvendorid', 'marchid', 'mimpid', 'mhartid', 'mstatus', 'misa', 'medeleg', 'mideleg', 'mie', 'mtvec', 'mcounteren', 'mscratch', 'mepc', 'mcause', 'mtval', 'mip', 'pmpcfg0', 'pmpcfg1', 'pmpcfg2', 'pmpcfg3', 'mcycle', 'minstret', 'mcycleh', 'minstreth', 'mcountinhibit', 'tselect', 'tdata1', 'tdata2', 'tdata3', 'dcsr', 'dpc', 'dscratch0', 'dscratch1', 'sstatus', 'sedeleg', 'sideleg', 'sie', 'stvec', 'scounteren', 'sscratch', 'sepc', 'scause', 'stval', 'sip', 'satp', 'vxsat', 'fflags', 'frm', 'fcsr'] +csr_regs_capture_group = f'({"|".join(CSR_REGS)})' +csr_regs_with_modifiers_capture_group = r'(write|old) *\( *' + csr_regs_capture_group + r' *\)' -csr_comb_covpt_regex_string = f'({"|".join(CSR_REGS)})' + r' *& *([^ ].*)== *([^ ].*)' -csr_comb_covpt_regex_w_bitshift_string = r'\( *' + f'({"|".join(CSR_REGS)})' + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)' -csr_comb_covpt_regex = re.compile(csr_comb_covpt_regex_string) -csr_comb_covpt_regex_w_bitshift = re.compile(csr_comb_covpt_regex_w_bitshift_string) +csr_comb_covpt_regex_strings = [ + csr_regs_capture_group + r' *& *([^ ].*)== *([^ ].*)' # regular + r'\( *' + csr_regs_capture_group + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)' # with bitshifts only + csr_regs_with_modifiers_capture_group + r' *& *([^ ].*)== *([^ ].*)' # with modifiers only + r'\( *' + csr_regs_with_modifiers_capture_group + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)' # with bitshifts and modifiers +] + +csr_comb_covpt_regexs = [re.compile(regex_string) for regex_string in csr_comb_covpt_regex_strings] def tokenize(s): result = [] From ec3bb173ec80ae3fe795e7d79e34f84db77eea3a Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Sat, 10 Dec 2022 01:55:51 +0530 Subject: [PATCH 10/13] add support for the modifiers old and write --- riscv_ctg/csr_comb.py | 149 ++++++++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 42 deletions(-) diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index 1648b279..74f78878 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -17,16 +17,16 @@ CSR_REGS = ['mvendorid', 'marchid', 'mimpid', 'mhartid', 'mstatus', 'misa', 'medeleg', 'mideleg', 'mie', 'mtvec', 'mcounteren', 'mscratch', 'mepc', 'mcause', 'mtval', 'mip', 'pmpcfg0', 'pmpcfg1', 'pmpcfg2', 'pmpcfg3', 'mcycle', 'minstret', 'mcycleh', 'minstreth', 'mcountinhibit', 'tselect', 'tdata1', 'tdata2', 'tdata3', 'dcsr', 'dpc', 'dscratch0', 'dscratch1', 'sstatus', 'sedeleg', 'sideleg', 'sie', 'stvec', 'scounteren', 'sscratch', 'sepc', 'scause', 'stval', 'sip', 'satp', 'vxsat', 'fflags', 'frm', 'fcsr'] csr_regs_capture_group = f'({"|".join(CSR_REGS)})' -csr_regs_with_modifiers_capture_group = r'(write|old) *\( *' + csr_regs_capture_group + r' *\)' +csr_regs_with_modifiers_capture_group = r'(write|old) *\( *"' + csr_regs_capture_group + r'" *\)' csr_comb_covpt_regex_strings = [ - csr_regs_capture_group + r' *& *([^ ].*)== *([^ ].*)' # regular - r'\( *' + csr_regs_capture_group + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)' # with bitshifts only - csr_regs_with_modifiers_capture_group + r' *& *([^ ].*)== *([^ ].*)' # with modifiers only - r'\( *' + csr_regs_with_modifiers_capture_group + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)' # with bitshifts and modifiers + csr_regs_capture_group + r' *& *([^ ].*)== *([^ ].*)', # regular + r'\( *' + csr_regs_capture_group + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)', # with bitshifts only + csr_regs_with_modifiers_capture_group + r' *& *([^ ].*)== *([^ ].*)', # with modifiers only + r'\( *' + csr_regs_with_modifiers_capture_group + r' *(>>|<<) *([^ ].*)\) *& *([^ ].*)== *([^ ].*)', # with bitshifts and modifiers ] -csr_comb_covpt_regexs = [re.compile(regex_string) for regex_string in csr_comb_covpt_regex_strings] +csr_comb_covpt_regexes = [re.compile(regex_string) for regex_string in csr_comb_covpt_regex_strings] def tokenize(s): result = [] @@ -209,32 +209,50 @@ def parse_csr_covpt(covpt): bool_expr = clause_stack.pop() return bool_expr -# This function extracts the csr register, the field mask and the field value from the coverpoint clause +# This function extracts the csr register, the field mask, the field value and the csr register modifier (if present) from the coverpoint clause # The coverpoint clause is assumed of the format: 'csr_reg & mask == val' or '(csr_reg >> shift) & mask == val' +# Modifiers `old()` and `write()` are also allowed on the `csr_reg`s. Example: `old(csr_reg) & mask == val` # csr_reg must be a valid csr register; mask and val are allowed to be valid python expressions -def get_csr_reg_field_mask_and_val(clause, instr_dict={}): +def get_csr_mask_val_modifier(clause, instr_dict={}): clause = clause.strip() - regex_w_shift_match = csr_comb_covpt_regex_w_bitshift.match(clause) - if regex_w_shift_match is None: - regex_match = csr_comb_covpt_regex.match(clause) - if regex_match is None: - return None, None, None - csr_reg, mask_expr, val_expr = regex_match.groups() - mask = eval(mask_expr, {}, instr_dict) - val = eval(val_expr, {}, instr_dict) - return csr_reg, mask, val - else: - csr_reg, shift_op, shift_expr, mask_expr, val_expr = regex_w_shift_match.groups() - shift = eval(shift_expr, {}, instr_dict) - mask = eval(mask_expr, {}, instr_dict) - val = eval(val_expr, {}, instr_dict) - if shift_op == '>>': - mask = mask << shift - val = val << shift - else: - mask = mask >> shift - val = val >> shift - return csr_reg, mask, val + for i, regex in enumerate(csr_comb_covpt_regexes): + regex_match = regex.match(clause) + if regex_match is not None: + if i == 0: # regular covpt + csr_reg, mask_expr, val_expr = regex_match.groups() + mask = eval(mask_expr, {}, instr_dict) + val = eval(val_expr, {}, instr_dict) + return csr_reg, mask, val, None + elif i == 1: # with bitshifts only + csr_reg, shift_op, shift_expr, mask_expr, val_expr = regex_match.groups() + shift = eval(shift_expr, {}, instr_dict) + mask = eval(mask_expr, {}, instr_dict) + val = eval(val_expr, {}, instr_dict) + if shift_op == '>>': + mask = mask << shift + val = val << shift + else: + mask = mask >> shift + val = val >> shift + return csr_reg, mask, val, None + elif i == 2: # with modifiers only + mod, csr_reg, mask_expr, val_expr = regex_match.groups() + mask = eval(mask_expr, {}, instr_dict) + val = eval(val_expr, {}, instr_dict) + return csr_reg, mask, val, mod + elif i == 3: # with both modifiers and bitshifts + mod, csr_reg, shift_op, shift_expr, mask_expr, val_expr = regex_match.groups() + shift = eval(shift_expr, {}, instr_dict) + mask = eval(mask_expr, {}, instr_dict) + val = eval(val_expr, {}, instr_dict) + if shift_op == '>>': + mask = mask << shift + val = val << shift + else: + mask = mask >> shift + val = val >> shift + return csr_reg, mask, val, mod + return None, None, None, None class GeneratorCSRComb(): ''' @@ -268,29 +286,76 @@ def csr_comb(self, cgf_node): continue for sol, _ in sols: - reg_mask_val_arr = [] + reg_mask_val_mod_dict = {} # maps a csr_reg to the 6-tuple [mask, val, write_mask, write_val, old_mask, old_val] + reg_with_mod = None for clause in sol: - csr_reg, mask, val = get_csr_reg_field_mask_and_val(clause, {'xlen': self.xlen}) + csr_reg, mask, val, mod = get_csr_mask_val_modifier(clause, {'xlen': self.xlen}) + if csr_reg is None: logger.error(f'Skipping invalid csr_comb coverpoint condition clause: {clause}') continue - reg_mask_val_arr.append((csr_reg, mask, val)) + if mod is not None: + if reg_with_mod is None: reg_with_mod = csr_reg + elif reg_with_mod != csr_reg: + logger.error(f'Skipping invalid csr_comb solution with modifiers on more than one registers for the coverpoint: {covpt}') + continue + + if not csr_reg in reg_mask_val_mod_dict: + if mod == 'old': + reg_mask_val_mod_dict[csr_reg] = [0, 0, 0, 0, mask, val] + elif mod == 'write': + reg_mask_val_mod_dict[csr_reg] = [0, 0, mask, val, 0, 0] + else: + reg_mask_val_mod_dict[csr_reg] = [mask, val, 0, 0, 0, 0] + else: + if mod == 'old': + reg_mask_val_mod_dict[csr_reg][4] |= mask + reg_mask_val_mod_dict[csr_reg][5] |= val + elif mod == 'write': + reg_mask_val_mod_dict[csr_reg][2] |= mask + reg_mask_val_mod_dict[csr_reg][3] |= val + else: + reg_mask_val_mod_dict[csr_reg][0] |= mask + reg_mask_val_mod_dict[csr_reg][1] |= val - reg_mask_val_arr.sort(key=functools.cmp_to_key(lambda x, y: 1)) + reg_mask_val_arr = list(reg_mask_val_mod_dict.items()) + reg_mask_val_arr.sort(key=functools.cmp_to_key(lambda x, y: 1 if x[0] == reg_with_mod else -1)) # put the register with modifier at the end instr_dict_csr_writes = [] instr_dict_csr_restores = [] uniq_csr_regs = [] restore_reg = 1 - for csr_reg, mask, val in reg_mask_val_arr: - instr_dict_csr_writes.append({ - 'csr_reg': csr_reg, 'mask': hex(mask), 'val': hex(val), 'restore_reg': f'x{restore_reg}', - 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1] - }) - instr_dict_csr_restores.append({ - 'csr_reg': csr_reg, 'restore_reg': f'x{restore_reg}' - }) - restore_reg += 1 + for csr_reg, mask_val in reg_mask_val_arr: + mask, val, write_mask, write_val, old_mask, old_val = mask_val + if old_mask != 0: + instr_dict_csr_writes.append({ + 'csr_reg': csr_reg, 'mask': hex(old_mask), 'val': hex(old_val), 'restore_reg': f'x{restore_reg}', + 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1] + }) + instr_dict_csr_restores.append({ + 'csr_reg': csr_reg, 'restore_reg': f'x{restore_reg}' + }) + restore_reg += 1 + + if write_mask != 0: + instr_dict_csr_writes.append({ + 'csr_reg': csr_reg, 'mask': hex(write_mask), 'val': hex(write_val), 'restore_reg': f'x{restore_reg}', + 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1] + }) + instr_dict_csr_restores.append({ + 'csr_reg': csr_reg, 'restore_reg': f'x{restore_reg}' + }) + restore_reg += 1 + elif mask != 0: + instr_dict_csr_writes.append({ + 'csr_reg': csr_reg, 'mask': hex(mask), 'val': hex(val), 'restore_reg': f'x{restore_reg}', + 'temp_reg1': temp_regs[0], 'temp_reg2': temp_regs[1] + }) + instr_dict_csr_restores.append({ + 'csr_reg': csr_reg, 'restore_reg': f'x{restore_reg}' + }) + restore_reg += 1 + if csr_reg not in uniq_csr_regs: uniq_csr_regs.append(csr_reg) From 3a60fe69a92454f0c307cd6e41f74ccbb054e60e Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Sun, 11 Dec 2022 17:19:58 +0530 Subject: [PATCH 11/13] correct the get_csr_mask_val_modifier function description comment --- riscv_ctg/csr_comb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/riscv_ctg/csr_comb.py b/riscv_ctg/csr_comb.py index 74f78878..2e83b3b6 100644 --- a/riscv_ctg/csr_comb.py +++ b/riscv_ctg/csr_comb.py @@ -211,7 +211,7 @@ def parse_csr_covpt(covpt): # This function extracts the csr register, the field mask, the field value and the csr register modifier (if present) from the coverpoint clause # The coverpoint clause is assumed of the format: 'csr_reg & mask == val' or '(csr_reg >> shift) & mask == val' -# Modifiers `old()` and `write()` are also allowed on the `csr_reg`s. Example: `old(csr_reg) & mask == val` +# Modifiers `old()` and `write()` are also allowed on the `csr_reg`s. Example: `old("csr_reg") & mask == val` # csr_reg must be a valid csr register; mask and val are allowed to be valid python expressions def get_csr_mask_val_modifier(clause, instr_dict={}): clause = clause.strip() From 086cc617a48d686b80839910a93f5064e725d08f Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Sun, 11 Dec 2022 17:33:25 +0530 Subject: [PATCH 12/13] =?UTF-8?q?Bump=20version:=200.10.3=20=E2=86=92=200.?= =?UTF-8?q?11.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ riscv_ctg/__init__.py | 2 +- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c01fe4..d4eb411e 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.11.0] - 2022-12-11 +- Added support for csr_comb test generation + ## [0.10.3] - 2022-11-22 - Fixed canary definition diff --git a/riscv_ctg/__init__.py b/riscv_ctg/__init__.py index 464d62bb..0162935f 100644 --- a/riscv_ctg/__init__.py +++ b/riscv_ctg/__init__.py @@ -4,5 +4,5 @@ __author__ = """InCore Semiconductors Pvt Ltd""" __email__ = 'incorebot@gmail.com' -__version__ = '0.10.3' +__version__ = '0.11.0' diff --git a/setup.cfg b/setup.cfg index 773bec36..9e839e82 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.3 +current_version = 0.11.0 commit = True tag = True diff --git a/setup.py b/setup.py index ba590a8a..a90fdd23 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ def read_requires(): setup( name='riscv_ctg', - version='0.10.3', + version='0.11.0', description="RISC-V CTG", long_description=readme + '\n\n', classifiers=[ From 8622947614ddf528226d5b33ef80d490538ef1a1 Mon Sep 17 00:00:00 2001 From: Priyansh Rathi Date: Tue, 13 Dec 2022 00:29:10 +0530 Subject: [PATCH 13/13] add documentation for csr_comb test generation --- docs/source/csr_comb.rst | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/source/csr_comb.rst diff --git a/docs/source/csr_comb.rst b/docs/source/csr_comb.rst new file mode 100644 index 00000000..f9f596de --- /dev/null +++ b/docs/source/csr_comb.rst @@ -0,0 +1,26 @@ +************************************************* +Test Generation using CSR Combination Coverpoints +************************************************* + +CSR Combination Coverpoints can help checking for some basic compliance with the privileged +part of the RISC-V spec by specifying conditions on the CSR values. +The coverpoint node associated with the test generation is ``csr_comb`` defined `here `_. + +Currently, the test generation is only possible for the coverpoints that test for the values of subfields in CSRs. +Thus, the only supported coverpoints are the form ``csr_reg & mask == val``, where: + +* ``mask`` and ``val`` are allowed to be any valid python expressions. +* ``csr_reg`` is allowed to be operated by a bit shift operator, i.e., ``(csr_reg >> shift) & mask == val`` is allowed where ``shift`` is a valid python expression. +* functions ``old("csr_name")`` and ``write("csr_name")`` are allowed to be used in the place of ``csr_reg`` to access the old value and write value of a CSR respectively. +* combination of multiple conditions with ``and`` and ``or`` is allowed. + +Example +------- + + **Coverpoint Definition** + + An example CSR combination coverpoint is given below: :: + + misa: + csr_comb: + 'old("misa") & 0x4 == 0 and (write("misa") >> 12) & 1 == 0 and misa & 0x1000 == 0x1000': 0 \ No newline at end of file