From ee67a15e1daa4c16c051c631d140ce38685c4a96 Mon Sep 17 00:00:00 2001 From: Dana Sorensen Date: Thu, 15 May 2025 07:13:29 -0600 Subject: [PATCH 1/3] add fixedpoint/signed UDP definitions --- src/peakrdl_html/__peakrdl__.py | 3 ++ src/peakrdl_html/udps/__init__.py | 8 ++++ src/peakrdl_html/udps/fixedpoint.py | 73 +++++++++++++++++++++++++++++ src/peakrdl_html/udps/signed.py | 33 +++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 src/peakrdl_html/udps/__init__.py create mode 100644 src/peakrdl_html/udps/fixedpoint.py create mode 100644 src/peakrdl_html/udps/signed.py diff --git a/src/peakrdl_html/__peakrdl__.py b/src/peakrdl_html/__peakrdl__.py index 7d5d9ad..efb91a2 100644 --- a/src/peakrdl_html/__peakrdl__.py +++ b/src/peakrdl_html/__peakrdl__.py @@ -4,6 +4,7 @@ from peakrdl.config import schema from .exporter import HTMLExporter +from .udps import ALL_UDPS if TYPE_CHECKING: import argparse @@ -14,6 +15,8 @@ class Exporter(ExporterSubcommandPlugin): short_desc = "Generate HTML documentation" long_desc = "Generate dynamic HTML documentation pages" + udp_definitions = ALL_UDPS + cfg_schema = { "user_template_dir": schema.DirectoryPath(), "user_static_dir": schema.DirectoryPath(), diff --git a/src/peakrdl_html/udps/__init__.py b/src/peakrdl_html/udps/__init__.py new file mode 100644 index 0000000..86fd44c --- /dev/null +++ b/src/peakrdl_html/udps/__init__.py @@ -0,0 +1,8 @@ +from .fixedpoint import IntWidth, FracWidth +from .signed import IsSigned + +ALL_UDPS = [ + IntWidth, + FracWidth, + IsSigned, +] diff --git a/src/peakrdl_html/udps/fixedpoint.py b/src/peakrdl_html/udps/fixedpoint.py new file mode 100644 index 0000000..cf47534 --- /dev/null +++ b/src/peakrdl_html/udps/fixedpoint.py @@ -0,0 +1,73 @@ +from typing import Any + +from systemrdl.component import Field +from systemrdl.node import Node, FieldNode +from systemrdl.udp import UDPDefinition + + +class _FixedpointWidth(UDPDefinition): + valid_components = {Field} + valid_type = int + + def validate(self, node: "Node", value: Any) -> None: + assert isinstance(node, FieldNode) + + intwidth = node.get_property("intwidth") + fracwidth = node.get_property("fracwidth") + assert intwidth is not None + assert fracwidth is not None + prop_ref = node.inst.property_src_ref.get(self.name) + + # incompatible with "counter" fields + if node.get_property("counter"): + self.msg.error( + "Fixed-point representations are not supported for counter fields.", + prop_ref + ) + + # incompatible with "encode" fields + if node.get_property("encode") is not None: + self.msg.error( + "Fixed-point representations are not supported for fields encoded as an enum.", + prop_ref + ) + + # ensure node width = fracwidth + intwidth + if intwidth + fracwidth != node.width: + self.msg.error( + f"Number of integer bits ({intwidth}) plus number of fractional bits ({fracwidth})" + f" must be equal to the width of the component ({node.width}).", + prop_ref + ) + + +class IntWidth(_FixedpointWidth): + name = "intwidth" + + def get_unassigned_default(self, node: "Node") -> Any: + """ + If 'fracwidth' is defined, 'intwidth' is inferred from the node width. + """ + assert isinstance(node, FieldNode) + fracwidth = node.get_property("fracwidth", default=None) + if fracwidth is not None: + return node.width - fracwidth + else: + # not a fixed-point number + return None + + +class FracWidth(_FixedpointWidth): + name = "fracwidth" + + def get_unassigned_default(self, node: "Node") -> Any: + """ + If 'intwidth' is defined, 'fracwidth' is inferred from the node width. + """ + assert isinstance(node, FieldNode) + intwidth = node.get_property("intwidth", default=None) + if intwidth is not None: + return node.width - intwidth + else: + # not a fixed-point number + return None diff --git a/src/peakrdl_html/udps/signed.py b/src/peakrdl_html/udps/signed.py new file mode 100644 index 0000000..b342986 --- /dev/null +++ b/src/peakrdl_html/udps/signed.py @@ -0,0 +1,33 @@ +from typing import Any + +from systemrdl.component import Field +from systemrdl.node import Node +from systemrdl.udp import UDPDefinition + + +class IsSigned(UDPDefinition): + name = "is_signed" + valid_components = {Field} + valid_type = bool + default_assignment = True + + def validate(self, node: "Node", value: Any) -> None: + # "counter" fields can not be signed + if value and node.get_property("counter"): + self.msg.error( + "The property is_signed=true is not supported for counter fields.", + node.inst.property_src_ref["is_signed"] + ) + + # incompatible with "encode" fields + if value and node.get_property("encode") is not None: + self.msg.error( + "The property is_signed=true is not supported for fields encoded as an enum.", + node.inst.property_src_ref["is_signed"] + ) + + def get_unassigned_default(self, node: "Node") -> Any: + """ + Unsigned by default if not specified. + """ + return False From cfe19083bc27059d2f7a588b781318d0077bc6d4 Mon Sep 17 00:00:00 2001 From: Dana Sorensen Date: Fri, 16 May 2025 13:36:43 -0600 Subject: [PATCH 2/3] added "R" radix, handle signed/fixedpoint parsing/formatting --- src/peakrdl_html/exporter.py | 21 +++- src/peakrdl_html/static/js/field_testers.js | 123 +++++++++++++++++--- 2 files changed, 125 insertions(+), 19 deletions(-) diff --git a/src/peakrdl_html/exporter.py b/src/peakrdl_html/exporter.py index f714081..0985759 100755 --- a/src/peakrdl_html/exporter.py +++ b/src/peakrdl_html/exporter.py @@ -220,12 +220,15 @@ def visit_addressable_node(self, node: Node, parent_id: 'Optional[int]'=None) -> # support this, so stuff a 0 in its place field_reset = 0 + is_signed = field.get_property("is_signed") + ral_field = { - 'name' : field.inst.inst_name, - 'lsb' : field.inst.lsb, - 'msb' : field.inst.msb, - 'reset': BigInt(field_reset), - 'disp' : 'H' + 'name' : field.inst.inst_name, + 'lsb' : field.inst.lsb, + 'msb' : field.inst.msb, + 'reset' : BigInt(field_reset), + 'is_signed': is_signed, + 'disp' : 'H', } field_enum = field.get_property("encode") @@ -233,6 +236,14 @@ def visit_addressable_node(self, node: Node, parent_id: 'Optional[int]'=None) -> ral_field['encode'] = True ral_field['disp'] = 'E' + if is_signed: + ral_field['disp'] = 'D' + + fracwidth = field.get_property("fracwidth") + if fracwidth is not None: + ral_field['fracwidth'] = fracwidth + ral_field['disp'] = 'R' + ral_fields.append(ral_field) ral_entry['fields'] = ral_fields diff --git a/src/peakrdl_html/static/js/field_testers.js b/src/peakrdl_html/static/js/field_testers.js index 4418cd9..05dc950 100644 --- a/src/peakrdl_html/static/js/field_testers.js +++ b/src/peakrdl_html/static/js/field_testers.js @@ -76,7 +76,16 @@ function update_reg_value_tester(){ var msb = BigInt(node.fields[i].msb); var lsb = BigInt(node.fields[i].lsb); var el = document.getElementById("_FieldValueTester" + node.fields[i].name); - var value = BigInt(el.value); + var value; + try { + value = parse_field_value(i, el.value); + } catch(error) { + if(error instanceof RangeError) { + value = error.clamped; + } else { + throw error; + } + } var mask = (1n << (msb - lsb + 1n)) - 1n; value = value & mask; reg_value = reg_value + (value << lsb); @@ -107,14 +116,100 @@ function update_field_value_tester(idx){ } } +// Helper: Convert raw unsigned value to signed integer +function toSigned(value, width) { + var sign_bit = 1n << (BigInt(width) - 1n); + if((value & sign_bit) !== 0n) { + value = value - (1n << BigInt(width)); + } + return value; +} + +// Helper: Convert signed integer to its raw unsigned representation +function fromSigned(value, width) { + var sign_bit = 1n << (BigInt(width) - 1n); + if((value & sign_bit) !== 0n) { + value = value + (1n << BigInt(width)); + } + return value; +} + +// Helper: Convert fixed-point field value to a real number +function fromFixedPoint(value, width, fracw, is_signed) { + if(is_signed) { + value = toSigned(value, width); + } + return Number(value) * Math.pow(2, -fracw); +} + function format_field_value(idx, value) { - if(RAL.get_node(CurrentID).fields[idx].disp == "D"){ - return(value.toString()); + var field = RAL.get_node(CurrentID).fields[idx]; + var width = BigInt(field.msb) - BigInt(field.lsb) + 1n; + if(field.disp == "R") { + var num = fromFixedPoint(value, width, field.fracwidth, field.is_signed); + // Always print a decimal point for real numbers + return num % 1 === 0 ? num.toFixed(1) : num.toString(); + } else if(field.disp == "D") { + if(field.is_signed) { + return toSigned(value, width).toString(); + } else { + return value.toString(); + } } else { return("0x" + value.toString(16)); } } +// parse a formatted (input) value into the raw field value +// if out of bounds, throw a RangeError with a "clamped" +// property containg the closest valid field value +function parse_field_value(idx, str) { + var node = RAL.get_node(CurrentID); + var disp = node.fields[idx].disp; + var width = BigInt(node.fields[idx].msb) - BigInt(node.fields[idx].lsb) + 1n; + var is_signed = node.fields[idx].is_signed; + + var value; + if(disp === "R") { + // Fixed-point: parse as real, convert to (signed) integer + var fracw = node.fields[idx].fracwidth; + var realval = Number(str); + if(isNaN(realval)) throw new SyntaxError("Invalid real number"); + value = BigInt(Math.round(realval * Math.pow(2, fracw))); + } else { + // Parse as integer + value = BigInt(str); + } + + // range checks + if(is_signed && (disp === "R" || disp === "D")) { + // check signed integer + var min = -(1n << (width - 1n)); + var max = (1n << (width - 1n)) - 1n; + + if(value < min || value > max) { + const err = new RangeError("Input out of bounds"); + clamped = value < min ? min : (value > max ? max : value); + err.clamped = fromSigned(clamped, width); + throw err; + } + + return fromSigned(value, width); + } else { + // check unsigned integer + var min = 0n; + var max = (1n << width) - 1n; + + if(value < min || value > max) { + const err = new RangeError("Input out of bounds"); + err.clamped = value < min ? min : (value > max ? max : value); + throw err; + } + + return value; + } +} + function update_field_enum_visibility(idx){ var node = RAL.get_node(CurrentID); @@ -140,10 +235,13 @@ function onRadixSwitch(el){ var idx = RAL.lookup_field_idx(el.dataset.name); var node = RAL.get_node(CurrentID); var d = node.fields[idx].disp; + var field = node.fields[idx]; if(d == "H") { d = "D"; - } else if((d == "D") && ("encode" in node.fields[idx])) { + } else if(d == "D" && ("encode" in field)) { d = "E"; + } else if((d == "D" || d == "E") && ("fracwidth" in field)) { + d = "R"; } else { d = "H"; } @@ -165,22 +263,19 @@ function onDecodedFieldEnumChange(el) { function onDecodedFieldInput(el){ var idx = RAL.lookup_field_idx(el.dataset.name); var node = RAL.get_node(CurrentID); - var msb = BigInt(node.fields[idx].msb); - var lsb = BigInt(node.fields[idx].lsb); var value; + el.classList.remove("invalid"); try { - value = BigInt(el.value); + value = parse_field_value(idx, el.value); } catch(error) { - value = -1n; - } - - var max_value = 1n << (msb - lsb + 1n); - if((value < 0) || (value >= max_value)){ if(!el.classList.contains("invalid")) el.classList.add("invalid"); - return; + if(error instanceof RangeError) { + value = error.clamped; + } else { + return; + } } - el.classList.remove("invalid"); if("encode" in node.fields[idx]) { var el2 = document.getElementById("_FieldValueEnumTester" + node.fields[idx].name); From 8795880e6eb8d6433d3abd0f90c5b319c93ec799 Mon Sep 17 00:00:00 2001 From: Dana Sorensen Date: Fri, 16 May 2025 20:57:07 -0600 Subject: [PATCH 3/3] added field display below input box for signed/fixedpoint numbers --- src/peakrdl_html/static/js/field_testers.js | 42 +++++++++++++++------ src/peakrdl_html/templates/reg_base.html | 5 +++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/peakrdl_html/static/js/field_testers.js b/src/peakrdl_html/static/js/field_testers.js index 05dc950..379a81a 100644 --- a/src/peakrdl_html/static/js/field_testers.js +++ b/src/peakrdl_html/static/js/field_testers.js @@ -58,6 +58,7 @@ function reset_field_inputs(){ } } update_reg_value_tester(); + update_field_value_testers(); } function update_field_value_testers(){ @@ -99,21 +100,36 @@ function update_reg_value_tester(){ function update_field_value_tester(idx){ var reg_el = document.getElementById("_RegValueTester"); var reg_value = BigInt(reg_el.value); - var node = RAL.get_node(CurrentID); + var field = RAL.get_node(CurrentID).fields[idx]; - var msb = BigInt(node.fields[idx].msb); - var lsb = BigInt(node.fields[idx].lsb); + var msb = BigInt(field.msb); + var lsb = BigInt(field.lsb); var value = reg_value >> lsb; var mask = (1n << (msb - lsb + 1n)) - 1n; value = value & mask; - var el = document.getElementById("_FieldValueTester" + node.fields[idx].name); + var el = document.getElementById("_FieldValueTester" + field.name); el.value = format_field_value(idx, value); el.classList.remove("invalid"); - if("encode" in RAL.get_node(CurrentID).fields[idx]) { - var el = document.getElementById("_FieldValueEnumTester" + node.fields[idx].name); + if("encode" in field) { + var el = document.getElementById("_FieldValueEnumTester" + field.name); el.value = "0x" + value.toString(16); } + + update_field_display(idx, value); +} + +function update_field_display(idx, value){ + // Update the display field for signed/fixed-point fields + var field = RAL.get_node(CurrentID).fields[idx]; + if (field.is_signed || ("fracwidth" in field)) { + var dispEl = document.getElementById("_FieldValueDisplay" + field.name); + if (dispEl) { + // Always show as signed int or real, regardless of current disp + var show_disp = ("fracwidth" in field) ? "R" : "D"; + dispEl.textContent = format_field_value(idx, value, show_disp); + } + } } // Helper: Convert raw unsigned value to signed integer @@ -142,14 +158,15 @@ function fromFixedPoint(value, width, fracw, is_signed) { return Number(value) * Math.pow(2, -fracw); } -function format_field_value(idx, value) { +function format_field_value(idx, value, override_disp) { var field = RAL.get_node(CurrentID).fields[idx]; var width = BigInt(field.msb) - BigInt(field.lsb) + 1n; - if(field.disp == "R") { + var disp = override_disp !== undefined ? override_disp : field.disp; + if(disp == "R") { var num = fromFixedPoint(value, width, field.fracwidth, field.is_signed); // Always print a decimal point for real numbers return num % 1 === 0 ? num.toFixed(1) : num.toString(); - } else if(field.disp == "D") { + } else if(disp == "D") { if(field.is_signed) { return toSigned(value, width).toString(); } else { @@ -262,7 +279,7 @@ function onDecodedFieldEnumChange(el) { function onDecodedFieldInput(el){ var idx = RAL.lookup_field_idx(el.dataset.name); - var node = RAL.get_node(CurrentID); + var field = RAL.get_node(CurrentID).fields[idx]; var value; el.classList.remove("invalid"); @@ -277,10 +294,11 @@ function onDecodedFieldInput(el){ } } - if("encode" in node.fields[idx]) { - var el2 = document.getElementById("_FieldValueEnumTester" + node.fields[idx].name); + if("encode" in field) { + var el2 = document.getElementById("_FieldValueEnumTester" + field.name); el2.value = "0x" + value.toString(16); } + update_field_display(idx, value); update_reg_value_tester(); save_reg_state(); } diff --git a/src/peakrdl_html/templates/reg_base.html b/src/peakrdl_html/templates/reg_base.html index 53e0505..3521e43 100644 --- a/src/peakrdl_html/templates/reg_base.html +++ b/src/peakrdl_html/templates/reg_base.html @@ -80,6 +80,11 @@ {% endfor %} {%- endif %} + {%- if field.get_property('is_signed') or field.get_property('fracwidth') != None %} +
+ +
+ {%- endif %} {{(field.get_html_name() or "-")|safe}}