| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| from typing import Dict, Optional, Sequence | |
| import libcst as cst | |
| from libcst._nodes.internal import CodegenState, visit_required | |
| def make_aref(name: str, idx: cst.BaseExpression) -> cst.Subscript: | |
| sub_elt = cst.SubscriptElement(slice=cst.Index(value=idx)) | |
| return cst.Subscript(value=cst.Name(name), slice=[sub_elt]) | |
| class NBAssignTarget(cst.AssignTarget): | |
| '''The target of a (delayed) state update''' | |
| def _codegen_impl(self, state: CodegenState) -> None: | |
| with state.record_syntactic_position(self): | |
| self.target._codegen(state) | |
| self.whitespace_before_equal._codegen(state) | |
| # U+21D0 is "Leftwards Double Arrow" (a nice unicode rendering of | |
| # SystemVerilog's "<=" which doesn't collide with less-than-or-equal. | |
| state.add_token("\u21d0") | |
| self.whitespace_after_equal._codegen(state) | |
| class NBAssign(cst.BaseSmallStatement): | |
| '''An assignment statement that models a (delayed) state update''' | |
| def __init__(self, target: NBAssignTarget, value: cst.BaseExpression): | |
| super().__init__() | |
| self.target = target | |
| self.value = value | |
| def _visit_and_replace_children(self, | |
| visitor: cst.CSTVisitorT) -> "NBAssign": | |
| target = visit_required(self, "target", self.target, visitor) | |
| value = visit_required(self, "value", self.value, visitor) | |
| return NBAssign(target=target, value=value) | |
| def _codegen_impl(self, | |
| state: CodegenState, | |
| default_semicolon: bool = False) -> None: | |
| with state.record_syntactic_position(self): | |
| self.target._codegen(state) | |
| self.value._codegen(state) | |
| @staticmethod | |
| def make(lhs: cst.BaseAssignTargetExpression, | |
| rhs: cst.BaseExpression) -> 'NBAssign': | |
| return NBAssign(target=NBAssignTarget(target=lhs), | |
| value=rhs) | |
| class ImplTransformer(cst.CSTTransformer): | |
| '''An AST visitor used to extract documentation from the ISS''' | |
| def __init__(self) -> None: | |
| self.impls = {} # type: Dict[str, Sequence[cst.BaseStatement]] | |
| self.cur_class = None # type: Optional[str] | |
| def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]: | |
| assert self.cur_class is None | |
| self.cur_class = node.name.value | |
| return None | |
| def leave_ClassDef(self, | |
| orig: cst.ClassDef, | |
| updated: cst.ClassDef) -> cst.BaseStatement: | |
| self.cur_class = None | |
| return updated | |
| def leave_Attribute(self, | |
| orig: cst.Attribute, | |
| updated: cst.Attribute) -> cst.BaseExpression: | |
| if isinstance(updated.value, cst.Name): | |
| stem = updated.value.value | |
| # Strip out "self." references. In the ISS code, a field in the | |
| # instruction appears as self.field_name. In the documentation, we | |
| # can treat all the instruction fields as being in scope. | |
| if stem == 'self': | |
| return updated.attr | |
| # Replace state.dmem with DMEM. This is an object in the ISS code, | |
| # so you see things like state.dmem.load_u32(...). We keep the | |
| # "object-orientated" style (so DMEM.load_u32(...)) because we need | |
| # to distinguish between 32-bit and 256-bit loads. | |
| if stem == 'state' and updated.attr.value == 'dmem': | |
| return cst.Name(value='DMEM') | |
| if isinstance(updated.value, cst.Attribute): | |
| # This attribute looks like A.B.C where B, C are names and A may be | |
| # a further attribute or it might be a name. | |
| attr_a = updated.value.value | |
| attr_b = updated.value.attr.value | |
| attr_c = updated.attr.value | |
| if isinstance(attr_a, cst.Name): | |
| stem = attr_a.value | |
| # Replace state.csrs.flags with FLAGs: the flag groups are | |
| # stored in the CSRs in the ISS and the implementation, but | |
| # logically exist somewhat separately, so we want named | |
| # reads/writes from them to look different. | |
| if (stem, attr_b, attr_c) == ('state', 'csrs', 'flags'): | |
| return cst.Name(value='FLAGs') | |
| return updated | |
| def leave_FunctionDef(self, | |
| orig: cst.FunctionDef, | |
| updated: cst.FunctionDef) -> cst.BaseStatement: | |
| if ((self.cur_class is None or | |
| updated.name.value != 'execute' or | |
| self.cur_class in self.impls)): | |
| return updated | |
| # The body of a function definition is always an IndentedBlock. Strip | |
| # that out to get at the statements inside. | |
| assert isinstance(updated.body, cst.IndentedBlock) | |
| self.impls[self.cur_class] = updated.body.body | |
| return updated | |
| @staticmethod | |
| def match_get_reg(call: cst.BaseExpression) -> Optional[cst.Subscript]: | |
| '''Extract a RegRef from state.gprs.get_reg(foo) | |
| Returns None if this isn't a match. | |
| ''' | |
| if not isinstance(call, cst.Call): | |
| return None | |
| # We expect a single argument (which we take as the index) | |
| if len(call.args) != 1: | |
| return None | |
| getreg_idx = call.args[0].value | |
| # All we need to do still is check that call.func is an | |
| # attribute representing state.gprs.get_reg or state.wdrs.get_reg. | |
| if ((not isinstance(call.func, cst.Attribute) or | |
| call.func.attr.value != 'get_reg')): | |
| return None | |
| state_dot_reg = call.func.value | |
| if not isinstance(state_dot_reg, cst.Attribute): | |
| return None | |
| # Finally, state_dot_reg should be either state.gprs or state.wdrs | |
| if not (isinstance(state_dot_reg.value, cst.Name) and | |
| state_dot_reg.value.value == 'state'): | |
| return None | |
| regfile_name = state_dot_reg.attr.value | |
| if regfile_name == 'gprs': | |
| regfile_uname = 'GPRs' | |
| elif regfile_name == 'wdrs': | |
| regfile_uname = 'WDRs' | |
| else: | |
| return None | |
| return make_aref(regfile_uname, getreg_idx) | |
| @staticmethod | |
| def _spot_reg_read(node: cst.Call) -> Optional[cst.BaseExpression]: | |
| # Detect | |
| # | |
| # state.gprs.get_reg(FOO).read_unsigned() | |
| # state.gprs.get_reg(FOO).read_signed() | |
| # | |
| # and replace with the expressions | |
| # | |
| # GPRs[FOO] | |
| # from_2s_complement(GPRs[FOO]) | |
| # | |
| # respectively. | |
| # In either case, we expect node.func to be some long attribute | |
| # (representing state.gprs.get_reg(FOO).read_X). For unsigned or | |
| # signed, we can check that it is indeed an Attribute and that | |
| # node.args is empty (neither function takes arguments). | |
| if node.args or not isinstance(node.func, cst.Attribute): | |
| return None | |
| # Now, check whether we're calling one of the functions we're | |
| # interested in. | |
| if node.func.attr.value == 'read_signed': | |
| signed = True | |
| elif node.func.attr.value == 'read_unsigned': | |
| signed = False | |
| else: | |
| return None | |
| # Check that node.func.value really does represent something of the | |
| # form "state.gprs.get_reg(FOO)". | |
| ret = ImplTransformer.match_get_reg(node.func.value) | |
| if ret is None: | |
| return None | |
| if signed: | |
| # If this is a call to read_signed, we want to wrap the returned | |
| # value in a call to a fake sign decode function. | |
| return cst.Call(func=cst.Name('from_2s_complement'), | |
| args=[cst.Arg(value=ret)]) | |
| else: | |
| return ret | |
| @staticmethod | |
| def _spot_csr_read(node: cst.Call) -> Optional[cst.BaseExpression]: | |
| # Detect | |
| # | |
| # state.read_csr(FOO) | |
| # | |
| # and replace it with the expression | |
| # | |
| # CSRs[FOO] | |
| # Check we have exactly one argument | |
| if len(node.args) != 1: | |
| return None | |
| # Check this is state.read_csr | |
| if not (isinstance(node.func, cst.Attribute) and | |
| isinstance(node.func.value, cst.Name) and | |
| node.func.value.value == 'state' and | |
| node.func.attr.value == 'read_csr'): | |
| return None | |
| return make_aref('CSRs', node.args[0].value) | |
| @staticmethod | |
| def _spot_wsr_read_idx(node: cst.Call) -> Optional[cst.BaseExpression]: | |
| # Detect | |
| # | |
| # state.wsrs.read_at_idx(FOO) | |
| # | |
| # and replace it with the expression | |
| # | |
| # WSRs[FOO] | |
| # Check we have exactly one argument | |
| if len(node.args) != 1: | |
| return None | |
| # Check this is state.wsrs.read_at_idx | |
| if not (isinstance(node.func, cst.Attribute) and | |
| isinstance(node.func.value, cst.Attribute) and | |
| isinstance(node.func.value.value, cst.Name) and | |
| node.func.value.value.value == 'state' and | |
| node.func.value.attr.value == 'wsrs' and | |
| node.func.attr.value == 'read_at_idx'): | |
| return None | |
| return make_aref('WSRs', node.args[0].value) | |
| @staticmethod | |
| def _spot_wsr_read_name(node: cst.Call) -> Optional[cst.BaseExpression]: | |
| # Detect | |
| # | |
| # state.wsrs.FOO.read_unsigned() | |
| # | |
| # and replace it with the expression | |
| # | |
| # FOO | |
| # Check we have no arguments | |
| if len(node.args) != 0: | |
| return None | |
| # Check this is A.B.C.D for names A, B, C, D. | |
| if not (isinstance(node.func, cst.Attribute) and | |
| isinstance(node.func.value, cst.Attribute) and | |
| isinstance(node.func.value.value, cst.Attribute) and | |
| isinstance(node.func.value.value.value, cst.Name)): | |
| return None | |
| a_name = node.func.value.value.value | |
| b_name = node.func.value.value.attr | |
| c_name = node.func.value.attr | |
| d_name = node.func.attr | |
| if not (a_name.value == 'state' and | |
| b_name.value == 'wsrs' and | |
| d_name.value == 'read_unsigned'): | |
| return None | |
| return c_name | |
| def leave_Call(self, | |
| orig: cst.Call, | |
| updated: cst.Call) -> cst.BaseExpression: | |
| # Handle: | |
| # | |
| # state.gprs.get_reg(FOO).read_unsigned() | |
| # state.gprs.get_reg(FOO).read_signed() | |
| # | |
| reg_read = ImplTransformer._spot_reg_read(updated) | |
| if reg_read is not None: | |
| return reg_read | |
| csr_read = ImplTransformer._spot_csr_read(updated) | |
| if csr_read is not None: | |
| return csr_read | |
| wsr_read_idx = ImplTransformer._spot_wsr_read_idx(updated) | |
| if wsr_read_idx is not None: | |
| return wsr_read_idx | |
| wsr_read_name = ImplTransformer._spot_wsr_read_name(updated) | |
| if wsr_read_name is not None: | |
| return wsr_read_name | |
| return updated | |
| @staticmethod | |
| def _spot_reg_write(node: cst.Expr) -> Optional[NBAssign]: | |
| # Spot | |
| # | |
| # state.gprs.get_reg(foo).write_unsigned(bar) | |
| # state.gprs.get_reg(foo).write_signed(bar) | |
| # | |
| # and turn them into | |
| # | |
| # GPRs[FOO] = bar | |
| # GPRs[FOO] = to_2s_complement(bar) | |
| if not isinstance(node.value, cst.Call): | |
| return None | |
| call = node.value | |
| if len(call.args) != 1 or not isinstance(call.func, cst.Attribute): | |
| return None | |
| value = call.args[0].value | |
| if call.func.attr.value == 'write_unsigned': | |
| rhs = value | |
| elif call.func.attr.value == 'write_signed': | |
| rhs = cst.Call(func=cst.Name('to_2s_complement'), | |
| args=[cst.Arg(value=value)]) | |
| else: | |
| return None | |
| # We expect call.func.value to be match state.gprs.get_reg(foo). | |
| # Extract the array reference if we can. | |
| reg_ref = ImplTransformer.match_get_reg(call.func.value) | |
| if reg_ref is None: | |
| return None | |
| return NBAssign.make(reg_ref, rhs) | |
| @staticmethod | |
| def _spot_csr_write(node: cst.Expr) -> Optional[NBAssign]: | |
| # Spot | |
| # | |
| # state.write_csr(csr, new_val) | |
| # | |
| # and turn it into | |
| # | |
| # CSRs[csr] = new_val | |
| if not isinstance(node.value, cst.Call): | |
| return None | |
| call = node.value | |
| if len(call.args) != 2 or not isinstance(call.func, cst.Attribute): | |
| return None | |
| if not (isinstance(call.func.value, cst.Name) and | |
| call.func.value.value == 'state' and | |
| call.func.attr.value == 'write_csr'): | |
| return None | |
| idx = call.args[0].value | |
| rhs = call.args[1].value | |
| return NBAssign.make(make_aref('CSRs', idx), rhs) | |
| @staticmethod | |
| def _spot_wsr_write_idx(node: cst.Expr) -> Optional[NBAssign]: | |
| # Spot | |
| # | |
| # state.wsrs.write_at_idx(wsr, new_val) | |
| # | |
| # and turn it into | |
| # | |
| # WSRs[wsr] = new_val | |
| if not isinstance(node.value, cst.Call): | |
| return None | |
| call = node.value | |
| if len(call.args) != 2 or not isinstance(call.func, cst.Attribute): | |
| return None | |
| func = call.func | |
| if not (isinstance(func.value, cst.Attribute) and | |
| isinstance(func.value.value, cst.Name) and | |
| func.value.value.value == 'state' and | |
| func.value.attr.value == 'wsrs' and | |
| func.attr.value == 'write_at_idx'): | |
| return None | |
| idx = call.args[0].value | |
| rhs = call.args[1].value | |
| return NBAssign.make(make_aref('WSRs', idx), rhs) | |
| @staticmethod | |
| def _spot_wsr_write_name(node: cst.Expr) -> Optional[NBAssign]: | |
| # Spot | |
| # | |
| # state.wsrs.FOO.write_unsigned(new_val) | |
| # | |
| # and turn it into | |
| # | |
| # FOO = new_val | |
| if not isinstance(node.value, cst.Call): | |
| return None | |
| call = node.value | |
| if len(call.args) != 1 or not isinstance(call.func, cst.Attribute): | |
| return None | |
| # Check this is A.B.C.D for names A, B, C, D. | |
| if not (isinstance(call.func, cst.Attribute) and | |
| isinstance(call.func.value, cst.Attribute) and | |
| isinstance(call.func.value.value, cst.Attribute) and | |
| isinstance(call.func.value.value.value, cst.Name)): | |
| return None | |
| a_name = call.func.value.value.value | |
| b_name = call.func.value.value.attr | |
| c_name = call.func.value.attr | |
| d_name = call.func.attr | |
| if not (a_name.value == 'state' and | |
| b_name.value == 'wsrs' and | |
| d_name.value == 'write_unsigned'): | |
| return None | |
| rhs = call.args[0].value | |
| return NBAssign.make(c_name, rhs) | |
| @staticmethod | |
| def _spot_flag_write(node: cst.Expr) -> Optional[NBAssign]: | |
| # Spot | |
| # | |
| # state.set_flags(fg, flags) | |
| # | |
| # and turn it into | |
| # | |
| # FLAGs[fg] = flags | |
| if not isinstance(node.value, cst.Call): | |
| return None | |
| call = node.value | |
| if len(call.args) != 2 or not isinstance(call.func, cst.Attribute): | |
| return None | |
| if not (isinstance(call.func.value, cst.Name) and | |
| call.func.value.value == 'state' and | |
| call.func.attr.value == 'set_flags'): | |
| return None | |
| fg = call.args[0].value | |
| flags = call.args[1].value | |
| return NBAssign.make(make_aref('FLAGs', fg), flags) | |
| @staticmethod | |
| def _spot_set_next_pc(node: cst.Expr) -> Optional[NBAssign]: | |
| # Spot | |
| # | |
| # state.set_next_pc(next_pc) | |
| # | |
| # and turn it into | |
| # | |
| # PC <= next_pc | |
| if not isinstance(node.value, cst.Call): | |
| return None | |
| call = node.value | |
| if len(call.args) != 1 or not isinstance(call.func, cst.Attribute): | |
| return None | |
| if not (isinstance(call.func.value, cst.Name) and | |
| call.func.value.value == 'state' and | |
| call.func.attr.value == 'set_next_pc'): | |
| return None | |
| next_pc = call.args[0].value | |
| return NBAssign.make(cst.Name(value='PC'), next_pc) | |
| def leave_Expr(self, | |
| orig: cst.Expr, | |
| updated: cst.Expr) -> cst.BaseSmallStatement: | |
| reg_write = ImplTransformer._spot_reg_write(updated) | |
| if reg_write is not None: | |
| return reg_write | |
| csr_write = ImplTransformer._spot_csr_write(updated) | |
| if csr_write is not None: | |
| return csr_write | |
| wsr_write_idx = ImplTransformer._spot_wsr_write_idx(updated) | |
| if wsr_write_idx is not None: | |
| return wsr_write_idx | |
| wsr_write_name = ImplTransformer._spot_wsr_write_name(updated) | |
| if wsr_write_name is not None: | |
| return wsr_write_name | |
| flag_write = ImplTransformer._spot_flag_write(updated) | |
| if flag_write is not None: | |
| return flag_write | |
| set_pc_next = ImplTransformer._spot_set_next_pc(updated) | |
| if set_pc_next is not None: | |
| return set_pc_next | |
| return updated | |
| def read_implementation(path: str) -> Dict[str, str]: | |
| '''Read the implementation at path (probably insn.py) | |
| Returns a dictionary from instruction class name to its pseudo-code | |
| implementation. An instruction class name looks like ADDI (for addi) or | |
| BNADDM (for bn.addm). | |
| ''' | |
| with open(path, 'r') as handle: | |
| node = cst.parse_module(handle.read()) | |
| # Extract the function bodies | |
| visitor = ImplTransformer() | |
| node.visit(visitor) | |
| # Render the function bodies | |
| return {cls: ''.join(node.code_for_node(stmt) for stmt in body) | |
| for cls, body in visitor.impls.items()} |