[otbn] Use libcst to extract implementations for docs
This isn't the full solution: we are going to want to do some
prettifying to make this more "pseudo-codeish". For example, we
currently have lines like
val1 = state.gprs.get_reg(self.grs1).read_unsigned()
state.gprs.get_reg(self.grd).write_unsigned(result)
which should probably be something more like
val1 <- gprs[self.grs1]
gprs[self.grd] <- val1
or similar. However, it does get rid of the duplicated definitions
from the YAML files and, if ugly, the definitions in the spec are now
at least clear.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/data/base-insns.yml b/hw/ip/otbn/data/base-insns.yml
index 2d8112d..60767bb 100644
--- a/hw/ip/otbn/data/base-insns.yml
+++ b/hw/ip/otbn/data/base-insns.yml
@@ -383,10 +383,6 @@
lsu:
type: csr
target: [csr]
- operation: |
- gpr_s_val = GPR[grs1]
- GPR[grd] = CSR[csr]
- CSR[csr] |= gpr_s_val
- mnemonic: csrrw
rv32i: true
@@ -408,13 +404,6 @@
lsu:
type: csr
target: [csr]
- operation: |
- gpr_s_val = GPR[grs1]
-
- if d != 0:
- GPR[grd] = CSR[csr]
-
- CSR[csr] = gpr_s_val
- mnemonic: ecall
rv32i: true
diff --git a/hw/ip/otbn/data/bignum-insns.yml b/hw/ip/otbn/data/bignum-insns.yml
index 6ab256c..5c3e72a 100644
--- a/hw/ip/otbn/data/bignum-insns.yml
+++ b/hw/ip/otbn/data/bignum-insns.yml
@@ -37,12 +37,6 @@
Adds two WDR values, writes the result to the destination WDR and updates
flags. The content of the second source WDR can be shifted by an unsigned
immediate before it is consumed by the operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- (result, flags_out) = AddWithCarry(wrs1, b_shifted, "0")
-
- WDR[wrd] = result
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnaf
mapping:
@@ -63,12 +57,6 @@
destination WDR, and updates the flags. The content of the second source
WDR can be shifted by an unsigned immediate before it is consumed by the
operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- (result, flags_out) = AddWithCarry(wrs1, b_shifted, FLAGS[flag_group].C)
-
- WDR[wrd] = result
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnaf
mapping:
@@ -96,11 +84,6 @@
doc: |
Adds a zero-extended unsigned immediate to the value of a WDR, writes the
result to the destination WDR, and updates the flags.
- operation: |
- (result, flags_out) = AddWithCarry(wrs1, imm, "0")
-
- WDR[wrd] = result
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnai
mapping:
@@ -125,13 +108,6 @@
The intermediate result is small enough if both inputs are less than `MOD`.
Flags are not used or saved.
- operation: |
- result = wrs1 + wrs2
-
- if result >= MOD:
- result = result - MOD
-
- WDR[wrd] = result & ((1 << 256) - 1)
encoding:
scheme: bnam
mapping:
@@ -192,16 +168,6 @@
Multiplies two `WLEN/4` WDR values, shifts the product by `acc_shift_imm` bits, and adds the result to the accumulator.
For versions of the instruction with writeback, see `BN.MULQACC.WO` and `BN.MULQACC.SO`.
- operation: |
- a_qw = GetQuarterWord(wrs1, wrs1_qwsel)
- b_qw = GetQuarterWord(wrs2, wrs2_qwsel)
-
- mul_res = a_qw * b_qw
-
- if zero_acc:
- ACC = 0
-
- ACC = ACC + (mul_res << acc_shift_imm)
encoding:
scheme: bnaq
mapping:
@@ -235,22 +201,6 @@
doc: |
Multiplies two `WLEN/4` WDR values, shifts the product by `acc_shift_imm` bits, and adds the result to the accumulator.
Writes the resulting accumulator to `wrd`.
- Updates the M, L and Z flags of `flag_group`.
- operation: |
- a_qw = GetQuarterWord(wrs1, wrs1_qwsel)
- b_qw = GetQuarterWord(wrs2, wrs2_qwsel)
-
- mul_res = a_qw * b_qw
-
- if zero_acc:
- ACC = 0
-
- ACC = ACC + (mul_res << acc_shift_imm)
-
- WDR[wrd] = ACC
- FLAGS[flag_group].M = ACC[WLEN-1]
- FLAGS[flag_group].L = ACC[0]
- FLAGS[flag_group].Z = (ACC == 0)
encoding:
scheme: bnaq
mapping:
@@ -299,29 +249,6 @@
If `wrd_hwsel` is one (so the instruction is updating the upper half-word of `wrd`), it updates the `M` and `Z` flags and leaves `L` unchanged.
The `M` flag is set iff the top bit of the shifted-out result is zero.
The `Z` flag is left unchanged if the shifted-out result is zero and cleared if not.
- operation: |
- a_qw = GetQuarterWord(wrs1, wrs1_qwsel)
- b_qw = GetQuarterWord(wrs2, wrs2_qwsel)
-
- mul_res = a_qw * b_qw
-
- if zero_acc:
- ACC = 0
-
- ACC = ACC + (mul_res << acc_shift_imm)
-
- shifted = ACC[WLEN/2-1:0]
- ACC = ACC >> (WLEN/2)
-
- if wrd_hwsel == 'L':
- WDR[wrd][WLEN/2-1:0] = shifted
- FLAGS[flag_group].L = shifted[0]
- FLAGS[flag_group].Z = (shifted == 0)
- elif wrd_hwsel == 'U':
- WDR[wrd][WLEN-1:WLEN/2] = shifted
- FLAGS[flag_group].M = shifted[WLEN/2-1]
- if (shifted != 0):
- FLAGS[flag_group].Z = 0
encoding:
scheme: bnaq
mapping:
@@ -352,12 +279,6 @@
doc: |
Subtracts the second WDR value from the first one, writes the result to the destination WDR and updates flags.
The content of the second source WDR can be shifted by an unsigned immediate before it is consumed by the operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- (result, flags_out) = SubtractWithBorrow(wrs1, b_shifted, 0)
-
- WDR[wrd] = result
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnaf
mapping:
@@ -376,12 +297,6 @@
doc: |
Subtracts the second WDR value and the Carry from the first one, writes the result to the destination WDR, and updates the flags.
The content of the second source WDR can be shifted by an unsigned immediate before it is consumed by the operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- (result, flags_out) = SubtractWithBorrow(wrs1, b_shifted, FLAGS[flag_group].C)
-
- WDR[wrd] = result
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnaf
mapping:
@@ -408,11 +323,6 @@
doc: |
Subtracts a zero-extended unsigned immediate from the value of a WDR,
writes the result to the destination WDR, and updates the flags.
- operation: |
- (result, flags_out) = SubtractWithBorrow(wrs1, imm, 0)
-
- WDR[wrd] = result
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnai
mapping:
@@ -437,13 +347,6 @@
This is guaranteed if both inputs are less than `MOD`.
Flags are not used or saved.
- operation: |
- result = wrs1 - wrs2
-
- if result < 0:
- result = MOD + result
-
- WDR[wrd] = result & ((1 << 256) - 1)
encoding:
scheme: bnam
mapping:
@@ -472,13 +375,6 @@
Takes the values stored in registers referenced by `wrs1` and `wrs2` and stores the result in the register referenced by `wrd`.
The content of the second source register can be shifted by an immediate before it is consumed by the operation.
The M, L and Z flags in flag group 0 are updated with the result of the operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- result = wrs1 & b_shifted
-
- WDR[wrd] = result
- flags_out = FlagsForResult(result)
- FLAGS[flag_group] = {M: flags_out.M, L: flags_out.L, Z: flags_out.Z, C: FLAGS[flag_group].C}
encoding:
scheme: bna
mapping:
@@ -499,13 +395,6 @@
Takes the values stored in WDRs referenced by `wrs1` and `wrs2` and stores the result in the WDR referenced by `wrd`.
The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.
The M, L and Z flags in flag group 0 are updated with the result of the operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- result = wrs1 | b_shifted
-
- WDR[wrd] = result
- flags_out = FlagsForResult(result)
- FLAGS[flag_group] = {M: flags_out.M, L: flags_out.L, Z: flags_out.Z, C: FLAGS[flag_group].C}
encoding:
scheme: bna
mapping:
@@ -533,13 +422,6 @@
Negates the value in `wrs` and stores the result in the register referenced by `wrd`.
The source value can be shifted by an immediate before it is consumed by the operation.
The M, L and Z flags in flag group 0 are updated with the result of the operation.
- operation: |
- a_shifted = ShiftReg(wrs1, shift_type, shift_bits)
- result = ~a_shifted
-
- WDR[wrd] = result
- flags_out = FlagsForResult(result)
- FLAGS[flag_group] = {M: flags_out.M, L: flags_out.L, Z: flags_out.Z, C: FLAGS[flag_group].C}
encoding:
scheme: bnan
mapping:
@@ -559,13 +441,6 @@
Takes the values stored in WDRs referenced by `wrs1` and `wrs2` and stores the result in the WDR referenced by `wrd`.
The content of the second source WDR can be shifted by an immediate before it is consumed by the operation.
The M, L and Z flags in flag group 0 are updated with the result of the operation.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- result = wrs1 ^ b_shifted
-
- WDR[wrd] = result
- flags_out = FlagsForResult(result)
- FLAGS[flag_group] = {M: flags_out.M, L: flags_out.L, Z: flags_out.Z, C: FLAGS[flag_group].C}
encoding:
scheme: bna
mapping:
@@ -595,8 +470,6 @@
doc: |
Concatenates the content of WDRs referenced by `wrs1` and `wrs2` (`wrs1` forms the upper part), shifts it right by an immediate value and truncates to WLEN bit.
The result is stored in the WDR referenced by `wrd`.
- operation: |
- WDR[wrd] = (((wrs1 << WLEN) | wrs2) >> imm)[WLEN-1:0]
encoding:
scheme: bnr
mapping:
@@ -628,10 +501,6 @@
<wrd>, <wrs1>, <wrs2>, [FG<flag_group>.]<flag>
doc: |
Returns in the destination WDR the value of the first source WDR if the flag in the chosen flag group is set, otherwise returns the value of the second source WDR.
- operation: |
- flag_is_set = FLAGS[flag_group].get(flag)
-
- WDR[wrd] = wrs1 if flag_is_set else wrs2
encoding:
scheme: bns
mapping:
@@ -656,11 +525,6 @@
doc: |
Subtracts the second WDR value from the first one and updates flags.
This instruction is identical to BN.SUB, except that no result register is written.
- operation: |
- b_shifted = ShiftReg(wrs2, shift_type, shift_bits)
- (, flags_out) = SubtractWithBorrow(wrs1, b_shifted, 0)
-
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnc
mapping:
@@ -678,10 +542,6 @@
doc: |
Subtracts the second WDR value from the first one and updates flags.
This instruction is identical to BN.SUBB, except that no result register is written.
- operation: |
- (, flags_out) = SubtractWithBorrow(wrs1, wrs2, FLAGS[flag_group].C)
-
- FLAGS[flag_group] = flags_out
encoding:
scheme: bnc
mapping:
@@ -740,24 +600,6 @@
The memory address must be aligned to WLEN bits.
Any address that is unaligned or is above the top of memory results in an error (setting bit `bad_data_addr` in `ERR_BITS`).
cycles: 2
- operation: |
- mem_addr = GPR[grs1] + offset
- wdr_dest = GPR[grd]
-
- if grs1_inc and grd_inc:
- raise IllegalInsn()
-
- if mem_addr % (WLEN / 8) or mem_addr + WLEN > DMEM_SIZE:
- raise BadDataAddr()
-
- mem_index = mem_addr // (WLEN / 8)
-
- WDR[wdr_dest] = LoadWlenWordFromMemory(mem_index)
-
- if grs1_inc:
- GPR[grs1] = GPR[grs1] + (WLEN / 8)
- if grd_inc:
- GPR[grd] = (GPR[grd] + 1) & 0x1f
lsu:
type: mem-load
target: [offset, grs1]
@@ -815,24 +657,6 @@
The memory address must be aligned to WLEN bits.
Any address that is unaligned or is above the top of memory results in an error (setting bit `bad_data_addr` in `ERR_BITS`).
- operation: |
- mem_addr = GPR[grs1] + offset
- wdr_src = GPR[grs2]
-
- if grs1_inc and grs2_inc:
- raise IllegalInsn()
-
- if mem_addr % (WLEN / 8) or mem_addr + WLEN > DMEM_SIZE:
- raise BadDataAddr()
-
- mem_index = mem_addr // (WLEN / 8)
-
- StoreWlenWordToMemory(mem_index, WDR[wdr_src])
-
- if grs1_inc:
- GPR[grs1] = GPR[grs1] + (WLEN / 8)
- if grs2_inc:
- GPR[grs2] = (GPR[grs2] + 1) & 0x1f
lsu:
type: mem-store
target: [offset, grs1]
@@ -850,7 +674,6 @@
- mnemonic: bn.mov
synopsis: Copy content between WDRs (direct addressing)
operands: [wrd, wrs]
- operation: WDR[wrd] = WDR[wrs]
encoding:
scheme: bnmov
mapping:
@@ -888,16 +711,6 @@
- If `grd_inc` is set, `grd` is updated to be `(*grd + 1) & 0x1f`.
- If `grs_inc` is set, `grs` is updated to be `(*grs + 1) & 0x1f`.
- operation: |
- WDR[GPR[grd]] = WDR[GPR[grs]]
-
- if grd_inc and grs_inc:
- raise IllegalInsn()
-
- if grs_inc:
- GPR[grs] = GPR[grs] + 1
- if grd_inc:
- GPR[grd] = GPR[grd] + 1
encoding:
scheme: bnmovr
mapping:
@@ -916,8 +729,6 @@
doc: |
Reads a WSR to a WDR.
If `wsr` isn't the index of a valid WSR, this halts with an error (TODO: Specify error code).
- operation: |
- WDR[wrd] = WSR[wsr]
encoding:
scheme: wcsr
mapping:
@@ -940,8 +751,6 @@
doc: |
Writes a WDR to a WSR.
If `wsr` isn't the index of a valid WSR, this halts with an error (TODO: Specify error code).
- operation: |
- WSR[wsr] = WDR[wrs]
encoding:
scheme: wcsr
mapping:
diff --git a/hw/ip/otbn/util/Makefile b/hw/ip/otbn/util/Makefile
index 863b9e2..f2024a9 100644
--- a/hw/ip/otbn/util/Makefile
+++ b/hw/ip/otbn/util/Makefile
@@ -15,7 +15,7 @@
$(build-dir) $(lint-build-dir):
mkdir -p $@
-pylibs := $(wildcard shared/*.py)
+pylibs := $(wildcard shared/*.py docs/*.py)
pyscripts := yaml_to_doc.py otbn-as otbn-ld otbn-objdump
lint-stamps := $(foreach s,$(pyscripts),$(lint-build-dir)/$(s).stamp)
diff --git a/hw/ip/otbn/util/docs/__init__.py b/hw/ip/otbn/util/docs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/hw/ip/otbn/util/docs/__init__.py
diff --git a/hw/ip/otbn/util/docs/get_impl.py b/hw/ip/otbn/util/docs/get_impl.py
new file mode 100644
index 0000000..48ec5ec
--- /dev/null
+++ b/hw/ip/otbn/util/docs/get_impl.py
@@ -0,0 +1,53 @@
+# 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
+
+
+class ImplVisitor(cst.CSTVisitor):
+ '''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) -> None:
+ assert self.cur_class is None
+ self.cur_class = node.name.value
+
+ def leave_ClassDef(self, node: cst.ClassDef) -> None:
+ self.cur_class = None
+
+ def leave_FunctionDef(self, node: cst.FunctionDef) -> None:
+ if ((self.cur_class is None or
+ node.name.value != 'execute' or
+ self.cur_class in self.impls)):
+ return
+
+ # The body of a function definition is always an IndentedBlock. Strip
+ # that out to get at the statements inside.
+ assert isinstance(node.body, cst.IndentedBlock)
+ self.impls[self.cur_class] = node.body.body
+
+
+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 = ImplVisitor()
+ 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()}
diff --git a/hw/ip/otbn/util/shared/insn_yaml.py b/hw/ip/otbn/util/shared/insn_yaml.py
index 3234899..906905b 100644
--- a/hw/ip/otbn/util/shared/insn_yaml.py
+++ b/hw/ip/otbn/util/shared/insn_yaml.py
@@ -27,7 +27,7 @@
['mnemonic', 'operands'],
['group', 'rv32i', 'synopsis',
'syntax', 'doc', 'note', 'trailing-doc',
- 'operation', 'encoding', 'glued-ops',
+ 'encoding', 'glued-ops',
'literal-pseudo-op', 'python-pseudo-op', 'lsu',
'straight-line', 'cycles'])
@@ -86,7 +86,6 @@
self.doc = get_optional_str(yd, 'doc', what)
self.note = get_optional_str(yd, 'note', what)
self.trailing_doc = get_optional_str(yd, 'trailing-doc', what)
- self.operation = get_optional_str(yd, 'operation', what)
raw_syntax = get_optional_str(yd, 'syntax', what)
if raw_syntax is not None:
diff --git a/hw/ip/otbn/util/yaml_to_doc.py b/hw/ip/otbn/util/yaml_to_doc.py
index 8e698f3..f4bf697 100755
--- a/hw/ip/otbn/util/yaml_to_doc.py
+++ b/hw/ip/otbn/util/yaml_to_doc.py
@@ -15,6 +15,8 @@
from shared.insn_yaml import Insn, InsnsFile, InsnGroup, load_file
from shared.operand import EnumOperandType, OptionOperandType, Operand
+from docs.get_impl import read_implementation
+
_O2EDicts = Tuple[Dict[str, List[str]], Dict[int, str]]
@@ -229,7 +231,7 @@
return (o2e, e2o)
-def render_insn(insn: Insn, heading_level: int) -> str:
+def render_insn(insn: Insn, impl: Optional[str], heading_level: int) -> str:
'''Generate the documentation for an instruction
heading_level is the current Markdown heading level. It should be greater
@@ -307,8 +309,7 @@
if insn.literal_pseudo_op is not None:
parts.append(render_literal_pseudo_op(insn.literal_pseudo_op))
- # Show operation pseudo-code if given
- if insn.operation is not None:
+ if impl is not None:
parts.append('{} Operation\n\n'
.format('#' * (heading_level + 1)))
@@ -335,14 +336,18 @@
'to assembly syntax.\n\n'
.format(op_str))
- parts.append('```python3\n'
- '{}\n'
+ # Note: No trailing newline after the inserted contents because libcst
+ # (which we use for extracting documentation) always adds a trailing
+ # newline itself.
+ parts.append('```\n'
+ '{}'
'```\n\n'
- .format(insn.operation))
+ .format(impl))
return ''.join(parts)
def render_insn_group(group: InsnGroup,
+ impls: Dict[str, str],
heading_level: int,
out_file: TextIO) -> None:
# We don't print the group heading: that's done in the top-level
@@ -355,28 +360,33 @@
return
for insn in group.insns:
- out_file.write(render_insn(insn, heading_level))
+ class_name = insn.mnemonic.replace('.', '').upper()
+ impl = impls.get(class_name)
+ out_file.write(render_insn(insn, impl, heading_level))
def render_insns(insns: InsnsFile,
+ impls: Dict[str, str],
heading_level: int,
out_dir: str) -> None:
'''Render documentation for all instructions'''
for group in insns.groups.groups:
group_path = os.path.join(out_dir, group.key + '.md')
with open(group_path, 'w') as group_file:
- render_insn_group(group, heading_level, group_file)
+ render_insn_group(group, impls, heading_level, group_file)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('yaml_file')
+ parser.add_argument('py_file')
parser.add_argument('out_dir')
args = parser.parse_args()
try:
insns = load_file(args.yaml_file)
+ impls = read_implementation(args.py_file)
except RuntimeError as err:
print(err, file=sys.stderr)
return 1
@@ -387,7 +397,7 @@
print('Failed to create output directory {!r}: {}.'
.format(args.out_dir, err))
- render_insns(insns, 2, args.out_dir)
+ render_insns(insns, impls, 2, args.out_dir)
return 0
diff --git a/python-requirements.txt b/python-requirements.txt
index 34ebc0a..bc803e2 100644
--- a/python-requirements.txt
+++ b/python-requirements.txt
@@ -8,6 +8,7 @@
hjson
ipyxact >= 0.2.4
isort
+libcst
livereload
mako
meson >= 0.53.0, <= 0.54 # minimum matches version in meson.build
diff --git a/util/build_docs.py b/util/build_docs.py
index 477361f..4aa6e81 100755
--- a/util/build_docs.py
+++ b/util/build_docs.py
@@ -330,9 +330,11 @@
otbn_dir = SRCTREE_TOP / 'hw/ip/otbn'
script = otbn_dir / 'util/yaml_to_doc.py'
yaml_file = otbn_dir / 'data/insns.yml'
+ impl_file = otbn_dir / 'dv/otbnsim/sim/insn.py'
out_dir = config['outdir-generated'].joinpath('otbn-isa')
- subprocess.run([str(script), str(yaml_file), str(out_dir)], check=True)
+ subprocess.run([str(script), str(yaml_file), str(impl_file), str(out_dir)],
+ check=True)
def hugo_match_version(hugo_bin_path, version):