Repository: amaranth-lang/amaranth
Pull request #1280 (2024-04-04 02:57:34)

back.rtlil: implement remaining format specifiers.
This requires a Yosys version from git. The requirement should be bumped to a proper release before Amaranth 0.5.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
Attention: Patch coverage is `90.00000%` with `3 lines` in your changes are missing coverage. Please review.
> Project coverage is 90.90%. Comparing base [(`d3c5b95`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/d3c5b958d3ae030ae84341bbf2b4d3b2cb69ce2b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) to head [(`2381f3c`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).

| [Files](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) | Patch % | Lines |
|---|---|---|
| [amaranth/back/rtlil.py](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?src=pr&el=tree&filepath=amaranth%2Fback%2Frtlil.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang#diff-YW1hcmFudGgvYmFjay9ydGxpbC5weQ==) | 90.90% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) |
| [amaranth/back/verilog.py](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?src=pr&el=tree&filepath=amaranth%2Fback%2Fverilog.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang#diff-YW1hcmFudGgvYmFjay92ZXJpbG9nLnB5) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1280      +/-   ##
==========================================
+ Coverage   90.84%   90.90%   +0.06%     
==========================================
  Files          44       44              
  Lines       10571    10588      +17     
  Branches     2577     2583       +6     
==========================================
+ Hits         9603     9625      +22     
+ Misses        777      776       -1     
+ Partials      191      187       -4     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1280?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/back/rtlil.py b/amaranth/back/rtlil.py
index a6367bd..af157b9 100644
--- a/amaranth/back/rtlil.py
+++ b/amaranth/back/rtlil.py
@@ -1090,35 +1090,42 @@ class ModuleEmitter:
                         args += chunk.value
                     if type is None:
                         type = "d"
-                    if type == "x" or type == "X":
-                        # TODO(yosys): "H" type
+                    elif type == "x":
                         type = "h"
-                    if type == "s":
-                        # TODO(yosys): support for single unicode character?
+                    elif type == "X":
+                        type = "H"
+                    elif type == "c":
+                        type = "U"
+                    elif type == "s":
                         type = "c"
                     width = spec["width"]
                     align = spec["align"]
                     if align is None:
-                        align = ">" if type != "c" else "<"
-                    if align == "=":
-                        # TODO(yosys): "=" alignment
-                        align = ">"
+                        align = "<" if type in ("c", "U") else ">"
                     fill = spec["fill"]
-                    if fill not in (" ", "0"):
-                        # TODO(yosys): arbitrary fill
-                        fill = " "
-                    # TODO(yosys): support for options, grouping
+                    if fill is None:
+                        fill = ' '
+                    if ord(fill) >= 0x80:
+                        raise NotImplementedError(f"non-ASCII fill character {fill!r} is not supported in RTLIL")
                     sign = spec["sign"]
-                    if sign != "+":
-                        # TODO(yosys): support " " sign
+                    if sign is None:
                         sign = ""
-                    if type == "c":
+                    if type in ("c", "U"):
                         signed = ""
                     elif chunk.signed:
                         signed = "s"
                     else:
                         signed = "u"
-                    format.append(f"{{{len(chunk.value)}:{align}{fill}{width or ''}{type}{sign}{signed}}}")
+                    show_base = "#" if spec["show_base"] and type != "d" else ""
+                    grouping = spec["grouping"] or ""
+                    if type == "U":
+                        if align != "<" and width != 0:
+                            format.append(fill * (width - 1))
+                        format.append(f"{{{len(chunk.value)}:U}}")
+                        if align == "<" and width != 0:
+                            format.append(fill * (width - 1))
+                    else:
+                        format.append(f"{{{len(chunk.value)}:{align}{fill}{width or ''}{type}{sign}{show_base}{grouping}{signed}}}")
         ports = {
             "EN": self.sigspec(cell.en),
             "ARGS": self.sigspec(_nir.Value(args)),
diff --git a/amaranth/back/verilog.py b/amaranth/back/verilog.py
index 50bc41d..ace266c 100644
--- a/amaranth/back/verilog.py
+++ b/amaranth/back/verilog.py
@@ -9,7 +9,7 @@ __all__ = ["YosysError", "convert", "convert_fragment"]
 
 def _convert_rtlil_text(rtlil_text, *, strip_internal_attrs=False, write_verilog_opts=()):
     # This version requirement needs to be synchronized with the one in pyproject.toml!
-    yosys = find_yosys(lambda ver: ver >= (0, 38))
+    yosys = find_yosys(lambda ver: ver >= (0, 39, 0, 165))
 
     script = []
     script.append(f"read_ilang <<rtlil\n{rtlil_text}\nrtlil")
diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py
index 6d91b70..88055cc 100644
--- a/amaranth/hdl/_ast.py
+++ b/amaranth/hdl/_ast.py
@@ -2691,7 +2691,8 @@ class Format:
             (?P<align>[<>=^])
         )?
         (?P<sign>[-+ ])?
-        (?P<options>[#]?[0]?)
+        (?P<show_base>[#]?)
+        (?P<width_zero>[0]?)
         (?P<width>[1-9][0-9]*)?
         (?P<grouping>[_,])?
         (?P<type>[bodxXcsn])?
@@ -2713,9 +2714,9 @@ class Format:
                 raise ValueError(f"Cannot print signed value with format specifier {match['type']!r}")
             if match["align"] == "=":
                 raise ValueError(f"Alignment {match['align']!r} is not allowed with format specifier {match['type']!r}")
-            if "#" in match["options"]:
+            if match["show_base"]:
                 raise ValueError(f"Alternate form is not allowed with format specifier {match['type']!r}")
-            if "0" in match["options"]:
+            if match["width_zero"] != "":
                 raise ValueError(f"Zero fill is not allowed with format specifier {match['type']!r}")
             if match["sign"] is not None:
                 raise ValueError(f"Sign is not allowed with format specifier {match['type']!r}")
@@ -2723,15 +2724,20 @@ class Format:
                 raise ValueError(f"Cannot specify {match['grouping']!r} with format specifier {match['type']!r}")
         if match["type"] == "s" and shape.width % 8 != 0:
             raise ValueError(f"Value width must be divisible by 8 with format specifier {match['type']!r}")
+        fill = match["fill"]
+        align = match["align"]
+        if match["width_zero"] and align is None:
+            fill = "0"
+            align = "="
         return {
             # Single character or None.
-            "fill": match["fill"],
+            "fill": fill,
             # '<', '>', '=', or None. Cannot be '=' for types 'c' and 's'.
-            "align": match["align"],
+            "align": align,
             # '-', '+', ' ', or None. Always None for types 'c' and 's'.
             "sign": match["sign"],
-            # "", "#", "0", or "#0". Always "" for types 'c' and 's'.
-            "options": match["options"],
+            # A bool. Always False for types 'c' and 's'.
+            "show_base": match["show_base"] == "#",
             # An int.
             "width": int(match["width"]) if match["width"] is not None else 0,
             # '_' or None. Always None for types 'c' and 's'.
diff --git a/pyproject.toml b/pyproject.toml
index 4844d64..edd8f26 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,7 +24,7 @@ dependencies = [
 # - pyproject.toml: tool.pdm.dev-dependencies.test
 # - amaranth/back/verilog.py: _convert_rtlil_text
 # - docs/install.rst: yosys-version
-builtin-yosys = ["amaranth-yosys>=0.38"]
+builtin-yosys = ["amaranth-yosys>=0.39.0.165.post92"]
 remote-build  = ["paramiko~=2.7"]
 
 [project.scripts]
@@ -65,7 +65,7 @@ source-includes = [
 [tool.pdm.dev-dependencies]
 # This version requirement needs to be synchronized with the one in pyproject.toml above!
 test = [
-  "yowasp-yosys>=0.38",
+  "yowasp-yosys>=0.39.0.165.post92",
   "coverage",
 ]
 docs = [


Test patch:
diff --git a/tests/test_back_rtlil.py b/tests/test_back_rtlil.py
index 480a3f2..f168b7e 100644
--- a/tests/test_back_rtlil.py
+++ b/tests/test_back_rtlil.py
@@ -1889,6 +1889,127 @@ class PrintTestCase(RTLILTestCase):
         end
         """)
 
+    def test_print_char(self):
+        i = Signal(21)
+        m = Module()
+        m.d.comb += [
+            Print(Format("{:c} {:-<5c} {:->5c}", i, i, i)),
+        ]
+        self.assertRTLIL(m, [i], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 21 input 0 \i
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $print $3
+                parameter \FORMAT "{21:U} {21:U}---- ----{21:U}\n"
+                parameter \ARGS_WIDTH 63
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 0
+                parameter \TRG_WIDTH 0
+                parameter \TRG_POLARITY 0
+                connect \EN $1 [0]
+                connect \ARGS { \i [20:0] \i [20:0] \i [20:0] }
+                connect \TRG { }
+            end
+        end
+        """)
+
+    def test_print_base(self):
+        i = Signal(8)
+        m = Module()
+        m.d.comb += [
+            Print(Format("{:b} {:o} {:d} {:x} {:X} {:#x} {:#d} {:#_x}", i, i, i, i, i, i, i, i)),
+        ]
+        self.assertRTLIL(m, [i], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $print $3
+                parameter \FORMAT "{8:> bu} {8:> ou} {8:> du} {8:> hu} {8:> Hu} {8:> h#u} {8:> du} {8:> h#_u}\n"
+                parameter \ARGS_WIDTH 64
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 0
+                parameter \TRG_WIDTH 0
+                parameter \TRG_POLARITY 0
+                connect \EN $1 [0]
+                connect \ARGS { \i [7:0] \i [7:0] \i [7:0] \i [7:0] \i [7:0] \i [7:0] \i [7:0] \i [7:0] }
+                connect \TRG { }
+            end
+        end
+        """)
+
+    def test_print_sign(self):
+        i = Signal(8)
+        m = Module()
+        m.d.comb += [
+            Print(Format("{:5x} {:-5x} {:+5x} {: 5x}", i, i, i, i)),
+        ]
+        self.assertRTLIL(m, [i], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $print $3
+                parameter \FORMAT "{8:> 5hu} {8:> 5h-u} {8:> 5h+u} {8:> 5h u}\n"
+                parameter \ARGS_WIDTH 32
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 0
+                parameter \TRG_WIDTH 0
+                parameter \TRG_POLARITY 0
+                connect \EN $1 [0]
+                connect \ARGS { \i [7:0] \i [7:0] \i [7:0] \i [7:0] }
+                connect \TRG { }
+            end
+        end
+        """)
+
+    def test_print_align(self):
+        i = Signal(8)
+        m = Module()
+        m.d.comb += [
+            Print(Format("{:<5x} {:>5x} {:=5x} {:05x} {:-<5x}", i, i, i, i, i)),
+        ]
+        self.assertRTLIL(m, [i], R"""
+        attribute \generator "Amaranth"
+        attribute \top 1
+        module \top
+            wire width 8 input 0 \i
+            wire width 1 $1
+            process $2
+                assign $1 [0] 1'0
+                assign $1 [0] 1'1
+            end
+            cell $print $3
+                parameter \FORMAT "{8:< 5hu} {8:> 5hu} {8:= 5hu} {8:=05hu} {8:<-5hu}\n"
+                parameter \ARGS_WIDTH 40
+                parameter signed \PRIORITY 32'11111111111111111111111111111110
+                parameter \TRG_ENABLE 0
+                parameter \TRG_WIDTH 0
+                parameter \TRG_POLARITY 0
+                connect \EN $1 [0]
+                connect \ARGS { \i [7:0] \i [7:0] \i [7:0] \i [7:0] \i [7:0] }
+                connect \TRG { }
+            end
+        end
+        """)
+
+
 class ComponentTestCase(RTLILTestCase):
     def test_component(self):
         class MyComponent(wiring.Component):
@@ -1907,4 +2028,3 @@ class ComponentTestCase(RTLILTestCase):
         connect \o 8'00000000
         end
         """)
-