├── .gitignore ├── LICENSE ├── README.md ├── fmt.vhd ├── fmt_examples.vhd ├── fmt_test.vhd ├── test.sh └── vectors └── format_test_vectors.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.cf 2 | *.o 3 | fmt_test 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VHDL String Formatting Package 2 | This is a VHDL string formatting package that is based on [f-strings found in Python 3](https://docs.python.org/3/reference/lexical_analysis.html#f-strings). 3 | 4 | ## Problem 5 | Working with strings in VHDL is somewhat difficult. They require known sizes, are 6 | concatenated with the `&` operator, and can be difficult to align concisely. 7 | 8 | A typical string building line might look like: 9 | ```vhdl 10 | report "int: " & integer'image(int) & " real: " & real'image(r); 11 | ``` 12 | 13 | ## Solution 14 | VHDL has operator overloading which includes the return style, so we can emulate 15 | the style of f-strings in VHDL. 16 | 17 | ```vhdl 18 | report f("int: {} real: {}", f(int), f(r)); 19 | ``` 20 | 21 | Here, we have defined the function `f()` for all the standard types included with 22 | VHDL as well as a function that uses the formatted string as the first argument. 23 | In fact, we can extend this further and insert formatting information into the 24 | string itself, or when we perform the initial conversion on the type. 25 | 26 | ```vhdl 27 | report f("int: {:>12d} real: {}", f(int), f(r, "0.12f")); 28 | ``` 29 | 30 | In this example, we are right justifying a signed decimal number with a width of 12, 31 | and creating a string from the value `r` with 12 bits of precision after the decimal 32 | point. 33 | 34 | ## `fmt` package 35 | 36 | The `fmt` package defines the following functions for use. 37 | 38 | ```vhdl 39 | -- Format string building function using a string_list 40 | procedure f(sfmt : string ; variable args : inout string_list ; variable l : inout line) ; 41 | alias fmt is f[string, string_list, line] ; 42 | 43 | -- Format string building function using up to 16 arguments 44 | function f(sfmt : string ; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 : in string := "") return string ; 45 | alias fmt is f[string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string return string] ; 46 | 47 | -- Single argument formatting 48 | function f(sfmt : string ; value : bit) return string ; 49 | function f(sfmt : string ; value : bit_vector) return string ; 50 | function f(sfmt : string ; value : boolean) return string ; 51 | function f(sfmt : string ; value : character) return string ; 52 | function f(sfmt : string ; value : integer) return string ; 53 | function f(sfmt : string ; value : real) return string ; 54 | function f(sfmt : string ; value : time) return string ; 55 | 56 | -- Functions to format standard types 57 | -- NOTE: Aliases added for ambiguous types 58 | function f(value : bit ; sfmt : string := "b") return string ; 59 | alias fbit is f[bit, string return string] ; 60 | 61 | function f(value : bit_vector ; sfmt : string := "b") return string ; 62 | alias fbv is f[bit_vector, string return string] ; 63 | 64 | function f(value : boolean ; sfmt : string := "s") return string ; 65 | 66 | function f(value : character ; sfmt : string := "c") return string ; 67 | alias fchar is f[character, string return string] ; 68 | 69 | function f(value : integer ; sfmt : string := "d") return string ; 70 | 71 | function f(value : real ; sfmt : string := "f") return string ; 72 | 73 | function f(value : string ; sfmt : string := "s") return string ; 74 | alias fstr is f[string, string return string] ; 75 | 76 | function f(value : time ; sfmt : string := ".9t") return string ; 77 | ``` 78 | 79 | These are all included in a single file: [`fmt.vhd`](https://github.com/bpadalino/vhdl-format/blob/main/fmt.vhd). 80 | 81 | ## Format Specification 82 | 83 | The format specification is close to the Python 3 format specification, but not 84 | exactly. 85 | 86 | ``` 87 | [fill][align][sign][width][.precision][class] 88 | ``` 89 | 90 | __fill__: Any character 91 | 92 | The character to fill any extra space when the string does not fit the requested width. 93 | 94 | Example: 95 | ```vhdl 96 | fmt("{:~20s}", "string") 97 | ``` 98 | 99 | Output: `string~~~~~~~~~~~~~~` 100 | 101 | __align__: `<` `>` `^` `=` 102 | 103 | Left Justification (`<`), Right Justification (`>`), Centered (`^`), and Sign Align 104 | Justification (`=`, for `d`, `e`, `f`, `u` classes only) 105 | 106 | __sign__: `+` 107 | 108 | Always print the sign of a number (for `d`, `e`, `f`, `u` classes only) 109 | 110 | __width__: A number 111 | 112 | The minimum number of characters to write. 113 | 114 | __.precision__: A point then a number 115 | 116 | For the (`e`, `f`) classes, defines the number of places to the right of the decimal. 117 | For the `t` class, determines which timebase to utilize for the conversion. 118 | 119 | | Precision | Time Unit | 120 | |-----------|---------------| 121 | | `.0` | 1 second | 122 | | `.3` | 1 millisecond | 123 | | `.6` | 1 microsecond | 124 | | `.9` | 1 nanosecond | 125 | | `.12` | 1 picosecond | 126 | | `.15` | 1 femtosecond | 127 | 128 | __class__: `b`, `c`, `d`, `e`, `f`, `o`, `s`, `t`, `u`, `x` 129 | 130 | The class of the string represented. 131 | 132 | | Character | Class | 133 | |-----------|--------------------------------------------------| 134 | | `b` | Binary | 135 | | `c` | Character | 136 | | `d` | Signed integer | 137 | | `e` | Floating point (exp notation - i.e. 3.14159e+00) | 138 | | `f` | Floating point (fixed notation - i.e. 3.14159) | 139 | | `o` | Octal | 140 | | `s` | String | 141 | | `t` | Time value | 142 | | `u` | Unsigned integer | 143 | | `x` | Hexadecimal | 144 | 145 | Note: Both uppercase and lowercase versions of the class are accepted. 146 | 147 | ## Usage 148 | The usage of the package should be straight forward and easy. The main goal is 149 | easier string manipulation, so being able to do simple string substitutions is 150 | the easiest way to use the package. 151 | 152 | ```vhdl 153 | report f("{} {}", "hello", "world"); 154 | ``` 155 | 156 | Argument reordering can also be done. 157 | 158 | ```vhdl 159 | report f("{1} {0}", "world", "hello"); 160 | ``` 161 | 162 | Formatting can be done inline. 163 | 164 | ```vhdl 165 | report f("{:<20s} {:>20s}", "hello", "world"); 166 | ``` 167 | 168 | Formatting may also be done on the argument. 169 | 170 | ```vhdl 171 | report f("{} {}", fstr("hello","<20s"), fstr("world",">20s)); 172 | ``` 173 | 174 | Lastly, for convenience, single argument overloads are available, too. 175 | 176 | ```vhdl 177 | report f("realnum: {:12.8f}", 3.14159)); 178 | ``` 179 | 180 | For more examples, please see the [`fmt_examples.vhd`](https://github.com/bpadalino/vhdl-format/blob/main/fmt_examples.vhd) 181 | file. 182 | 183 | ## TODO Items 184 | * [ ] Binary/Octal/Hex Conversions for Integers 185 | * [ ] Add `std_logic_vector` `f()` 186 | * [ ] Add `signed` and `unsigned` `f()` 187 | * [ ] Add `sfixed` and `ufixed` `f()` 188 | * [ ] Generic `f()` for easier customization with default `'image` or `to_string()` conversions 189 | * [ ] Add the `#` option for printing `0b`, `0o`, or `0x` before the printed number 190 | 191 | ## License 192 | The package is provided under the Unlicense so feel free to do whatever you like with it. 193 | If you like the project, please let me know. If you run into issues, please feel free to 194 | file an issue as well. 195 | -------------------------------------------------------------------------------- /fmt.vhd: -------------------------------------------------------------------------------- 1 | -- Package: colors 2 | -- Description 3 | -- A package containing the strings for producing ANSI colored output on a 4 | -- terminal. 5 | 6 | package colors is 7 | type colors_t is record 8 | BLACK : string ; 9 | RED : string ; 10 | GREEN : string ; 11 | YELLOW : string ; 12 | BLUE : string ; 13 | PURPLE : string ; 14 | CYAN : string ; 15 | WHITE : string ; 16 | end record ; 17 | 18 | type ansi_t is record 19 | -- Foreground colors, typical 20 | BLACK : string ; 21 | RED : string ; 22 | GREEN : string ; 23 | YELLOW : string ; 24 | BLUE : string ; 25 | PURPLE : string ; 26 | CYAN : string ; 27 | WHITE : string ; 28 | 29 | -- More styles 30 | bold : colors_t ; 31 | underline : colors_t ; 32 | intense : colors_t ; 33 | boldintense : colors_t ; 34 | -- Background colors 35 | background : colors_t ; 36 | intensebg : colors_t ; 37 | 38 | -- Control sequence 39 | RESET : string ; 40 | end record ; 41 | 42 | constant FOREGROUND_COLORS : colors_t := ( 43 | BLACK => (ESC & "[30m"), 44 | RED => (ESC & "[31m"), 45 | GREEN => (ESC & "[32m"), 46 | YELLOW => (ESC & "[33m"), 47 | BLUE => (ESC & "[34m"), 48 | PURPLE => (ESC & "[35m"), 49 | CYAN => (ESC & "[36m"), 50 | WHITE => (ESC & "[37m") 51 | ) ; 52 | 53 | constant BOLD_COLORS : colors_t := ( 54 | BLACK => (ESC & "[1;30m"), 55 | RED => (ESC & "[1;31m"), 56 | GREEN => (ESC & "[1;32m"), 57 | YELLOW => (ESC & "[1;33m"), 58 | BLUE => (ESC & "[1;34m"), 59 | PURPLE => (ESC & "[1;35m"), 60 | CYAN => (ESC & "[1;36m"), 61 | WHITE => (ESC & "[1;37m") 62 | ) ; 63 | 64 | constant UNDERLINE_COLORS : colors_t := ( 65 | BLACK => (ESC & "[4;30m"), 66 | RED => (ESC & "[4;31m"), 67 | GREEN => (ESC & "[4;32m"), 68 | YELLOW => (ESC & "[4;33m"), 69 | BLUE => (ESC & "[4;34m"), 70 | PURPLE => (ESC & "[4;35m"), 71 | CYAN => (ESC & "[4;36m"), 72 | WHITE => (ESC & "[4;37m") 73 | ) ; 74 | 75 | constant BACKGROUND_COLORS : colors_t := ( 76 | BLACK => (ESC & "[40m"), 77 | RED => (ESC & "[41m"), 78 | GREEN => (ESC & "[42m"), 79 | YELLOW => (ESC & "[43m"), 80 | BLUE => (ESC & "[44m"), 81 | PURPLE => (ESC & "[45m"), 82 | CYAN => (ESC & "[46m"), 83 | WHITE => (ESC & "[47m") 84 | ) ; 85 | 86 | constant INTENSE_COLORS : colors_t := ( 87 | BLACK => (ESC & "[0;90m"), 88 | RED => (ESC & "[0;91m"), 89 | GREEN => (ESC & "[0;92m"), 90 | YELLOW => (ESC & "[0;93m"), 91 | BLUE => (ESC & "[0;94m"), 92 | PURPLE => (ESC & "[0;95m"), 93 | CYAN => (ESC & "[0;96m"), 94 | WHITE => (ESC & "[0;97m") 95 | ) ; 96 | 97 | constant BOLD_INTENSE_COLORS : colors_t := ( 98 | BLACK => (ESC & "[1;90m"), 99 | RED => (ESC & "[1;91m"), 100 | GREEN => (ESC & "[1;92m"), 101 | YELLOW => (ESC & "[1;93m"), 102 | BLUE => (ESC & "[1;94m"), 103 | PURPLE => (ESC & "[1;95m"), 104 | CYAN => (ESC & "[1;96m"), 105 | WHITE => (ESC & "[1;97m") 106 | ) ; 107 | 108 | 109 | constant INTENSE_BACKGROUND_COLORS : colors_t := ( 110 | BLACK => (ESC & "[0;100m"), 111 | RED => (ESC & "[0;101m"), 112 | GREEN => (ESC & "[0;102m"), 113 | YELLOW => (ESC & "[0;103m"), 114 | BLUE => (ESC & "[0;104m"), 115 | PURPLE => (ESC & "[0;105m"), 116 | CYAN => (ESC & "[0;106m"), 117 | WHITE => (ESC & "[0;107m") 118 | ) ; 119 | 120 | constant ansi : ansi_t := ( 121 | BLACK => FOREGROUND_COLORS.BLACK, 122 | RED => FOREGROUND_COLORS.RED, 123 | GREEN => FOREGROUND_COLORS.GREEN, 124 | YELLOW => FOREGROUND_COLORS.YELLOW, 125 | BLUE => FOREGROUND_COLORS.BLUE, 126 | PURPLE => FOREGROUND_COLORS.PURPLE, 127 | CYAN => FOREGROUND_COLORS.CYAN, 128 | WHITE => FOREGROUND_COLORS.WHITE, 129 | bold => BOLD_COLORS, 130 | underline => UNDERLINE_COLORS, 131 | intense => INTENSE_COLORS, 132 | boldintense => BOLD_INTENSE_COLORS, 133 | background => BACKGROUND_COLORS, 134 | intensebg => INTENSE_BACKGROUND_COLORS, 135 | RESET => (ESC & "[0m") 136 | ) ; 137 | 138 | end package ; 139 | 140 | -- Package: string_list 141 | -- Description 142 | -- A string_list is a dynamic array of strings which can efficiently be passed 143 | -- between procedures such that the string does not need to be the same length. 144 | -- NOTE 145 | -- In the future, I'd like this to be a generic list package. If this is the 146 | -- case then string_list can just be an instantiation of the generic list. 147 | -- The only extra procedures to write are then sumlength and concatenate_list. 148 | use std.textio.line ; 149 | 150 | package string_list is 151 | 152 | type string_list ; 153 | type string_list_item ; 154 | type string_list_item_ptr is access string_list_item ; 155 | 156 | type string_list is record 157 | root : string_list_item_ptr ; 158 | length : natural ; 159 | end record ; 160 | 161 | type string_list_item is record 162 | str : line ; 163 | next_item : string_list_item_ptr ; 164 | end record ; 165 | 166 | -- Procedures for manipulating string_list 167 | procedure append(variable list : inout string_list ; s : string) ; 168 | procedure clear(variable list : inout string_list) ; 169 | procedure get(variable list : in string_list ; index : integer ; variable l : out line) ; 170 | procedure length(variable list : string_list; variable len : out natural) ; 171 | procedure sumlength(variable list : string_list ; rv : out natural) ; 172 | procedure concatenate_list(variable parts : string_list ; variable rv : inout line) ; 173 | 174 | end package ; 175 | 176 | package body string_list is 177 | 178 | procedure append(variable list : inout string_list ; s : string) is 179 | variable l : line := new string'(s) ; 180 | variable new_item : string_list_item_ptr := new string_list_item ; 181 | variable item : string_list_item_ptr := list.root ; 182 | begin 183 | new_item.str := l ; 184 | new_item.next_item := null ; 185 | if list.length = 0 then 186 | list.root := new_item ; 187 | else 188 | while item.next_item /= null loop 189 | item := item.next_item ; 190 | end loop ; 191 | item.next_item := new_item ; 192 | end if ; 193 | list.length := list.length + 1 ; 194 | end procedure ; 195 | 196 | procedure clear(variable list : inout string_list) is 197 | variable item : string_list_item_ptr := list.root ; 198 | variable next_item : string_list_item_ptr := null ; 199 | begin 200 | if item /= null then 201 | next_item := item.next_item ; 202 | end if ; 203 | while item /= null loop 204 | next_item := item.next_item ; 205 | deallocate(item) ; 206 | item := next_item ; 207 | end loop ; 208 | list.root := null ; 209 | list.length := 0 ; 210 | end procedure ; 211 | 212 | procedure get(variable list : in string_list ; index : integer ; variable l : out line) is 213 | variable item : string_list_item_ptr := list.root ; 214 | begin 215 | if index >= list.length then 216 | report "Cannot retrieve item, index out of bounds" 217 | severity warning ; 218 | l := null ; 219 | end if ; 220 | for i in 1 to index loop 221 | item := item.next_item ; 222 | end loop ; 223 | l := item.str ; 224 | end procedure ; 225 | 226 | procedure length(variable list : string_list; variable len : out natural) is 227 | begin 228 | len := list.length ; 229 | end procedure ; 230 | 231 | procedure sumlength(variable list : string_list ; rv : out natural) is 232 | variable l : line := null ; 233 | variable len : natural := 0 ; 234 | variable count : natural := 0 ; 235 | begin 236 | length(list, len) ; 237 | for i in 0 to len-1 loop 238 | get(list, i, l) ; 239 | count := count + l.all'length ; 240 | end loop ; 241 | rv := count ; 242 | end procedure ; 243 | 244 | procedure concatenate_list(variable parts : string_list ; variable rv : inout line) is 245 | variable start : positive := 1 ; 246 | variable stop : positive := 1 ; 247 | variable l : line := null ; 248 | variable len : natural := 0 ; 249 | begin 250 | sumlength(parts, len) ; 251 | rv := new string(1 to len) ; 252 | for i in 0 to parts.length-1 loop 253 | get(parts, i, l) ; 254 | stop := start + l.all'length - 1 ; 255 | rv(start to stop) := l.all ; 256 | start := stop + 1 ; 257 | end loop ; 258 | end procedure ; 259 | 260 | end package body ; 261 | 262 | -- Package: fmt 263 | -- Description 264 | -- A string formatting package that is based on the Python format specifier. 265 | -- See this website for some information: 266 | -- 267 | -- https://realpython.com/python-formatted-output/#the-format_spec-component 268 | -- 269 | -- Currently supported is: 270 | -- 271 | -- :[fill][align][sign][width][.precision][class] 272 | -- 273 | -- fill: Any character 274 | -- The character to fill any extra space when the string does not fit the 275 | -- requested width. 276 | -- 277 | -- align: '<', '>', '^', '=' 278 | -- < Left alignment 279 | -- > Right alignment 280 | -- ^ Center alignment 281 | -- = Sign alignment (d, e, f, u classes only) 282 | -- 283 | -- sign: '+' 284 | -- Ensures the sign is always printed for a number. 285 | -- 286 | -- width: A number 287 | -- The minimum number of characters to write. 288 | -- 289 | -- .precision: A point then a number. 290 | -- For the (e, f) classes, prints the number of points to the right of the decimal. 291 | -- For the t class, determines which timebase to utilize for conversion. 292 | -- Precision Time Unit 293 | -- .0 1 second 294 | -- .3 1 millisecond 295 | -- .6 1 microsecond 296 | -- .9 1 nanosecond 297 | -- .12 1 picosecond 298 | -- .15 1 femtosecond 299 | -- 300 | -- class: 'b', 'c', 'd', 'e', 'f', 'o', 's', 't', 'u', 'x' 301 | -- Character Class 302 | -- b Binary 303 | -- c Character 304 | -- d Signed integer 305 | -- e Floating point (exp notation - i.e. 3.14159e+00) 306 | -- f Floating point (fixed notation - i.e. 3.14159) 307 | -- o Octal 308 | -- s String 309 | -- t Time value 310 | -- u Unsigned integer 311 | -- x Hexadecimal 312 | -- Note: Both lowercase and uppercase class values are accepetd. 313 | use std.textio.line ; 314 | use std.textio.side ; 315 | 316 | use std.textio.read ; 317 | use std.textio.bread ; 318 | use std.textio.hread ; 319 | use std.textio.oread ; 320 | 321 | use std.textio.write ; 322 | use std.textio.bwrite ; 323 | use std.textio.hwrite ; 324 | use std.textio.owrite ; 325 | 326 | use work.string_list.all ; 327 | 328 | library ieee ; 329 | use ieee.std_logic_1164.std_logic ; 330 | use ieee.std_logic_1164.std_logic_vector ; 331 | use ieee.std_logic_1164.hwrite ; 332 | use ieee.std_logic_1164.owrite ; 333 | use ieee.std_logic_1164.bwrite ; 334 | use ieee.std_logic_1164.write ; 335 | 336 | use ieee.numeric_std.signed ; 337 | use ieee.numeric_std.unsigned ; 338 | use ieee.numeric_std.to_integer ; 339 | use ieee.numeric_std.hwrite ; 340 | use ieee.numeric_std.owrite ; 341 | use ieee.numeric_std.bwrite ; 342 | 343 | use ieee.fixed_pkg.sfixed ; 344 | use ieee.fixed_pkg.ufixed ; 345 | use ieee.fixed_pkg.hwrite ; 346 | use ieee.fixed_pkg.owrite ; 347 | use ieee.fixed_pkg.bwrite ; 348 | use ieee.fixed_pkg.to_real ; 349 | use ieee.fixed_pkg.to_slv ; 350 | 351 | package fmt is 352 | --------------------------------------------------------------------------- 353 | -- VHDL-2008 Generic Function 354 | --------------------------------------------------------------------------- 355 | ---- TODO: Generic f() function which utilizes the type'image to get the string and just pass to fstr()? 356 | ---- Useful for custom enumerated types? 357 | --function fstring 358 | -- generic(type t; function to_string(x : t) return string is <>) 359 | -- parameter(value : t ; sfmt : string := "s") 360 | -- return string ; 361 | -- 362 | --function fimage 363 | -- generic(type t) 364 | -- parameter(value : t ; sfmt : string := "s") 365 | -- return string; 366 | 367 | -- Format string building function using a string_list 368 | procedure f(sfmt : string ; variable args : inout string_list ; variable l : inout line) ; 369 | alias fmt is f[string, string_list, line] ; 370 | 371 | -- Format string building function using up to 16 arguments 372 | function f(sfmt : string ; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 : in string := "") return string ; 373 | alias fmt is f[string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string, string return string] ; 374 | 375 | -- Single argument formatting 376 | function f(sfmt : string ; value : bit) return string ; 377 | function f(sfmt : string ; value : bit_vector) return string ; 378 | function f(sfmt : string ; value : boolean) return string ; 379 | function f(sfmt : string ; value : character) return string ; 380 | function f(sfmt : string ; value : integer) return string ; 381 | function f(sfmt : string ; value : real) return string ; 382 | function f(sfmt : string ; value : time) return string ; 383 | 384 | -- Functions to format standard types 385 | -- NOTE: Aliases added for ambiguous types 386 | function f(value : bit ; sfmt : string := "b") return string ; 387 | alias fbit is f[bit, string return string] ; 388 | 389 | function f(value : bit_vector ; sfmt : string := "b") return string ; 390 | alias fbv is f[bit_vector, string return string] ; 391 | 392 | function f(value : boolean ; sfmt : string := "s") return string ; 393 | 394 | function f(value : character ; sfmt : string := "c") return string ; 395 | alias fchar is f[character, string return string] ; 396 | 397 | function f(value : integer ; sfmt : string := "d") return string ; 398 | function f(value : real ; sfmt : string := "f") return string ; 399 | 400 | function f(value : string ; sfmt : string := "s") return string ; 401 | alias fstr is f[string, string return string] ; 402 | 403 | function f(value : time ; sfmt : string := ".9t") return string ; 404 | 405 | -- Functions to format fixed point types 406 | function f(value : sfixed ; sfmt : string := "f") return string ; 407 | function f(value : ufixed ; sfmt : string := "f") return string ; 408 | 409 | function f(value : signed ; sfmt : string := "d") return string ; 410 | function f(value : unsigned ; sfmt : string := "d") return string ; 411 | 412 | function f(value : std_logic ; sfmt : string := "b") return string ; 413 | function f(value : std_logic_vector ; sfmt : string := "b") return string ; 414 | 415 | -- Printing to output 416 | procedure p(x : string) ; 417 | alias print is p[string]; 418 | 419 | end package ; 420 | 421 | package body fmt is 422 | 423 | use std.textio.write ; 424 | use std.textio.writeline ; 425 | use std.textio.output ; 426 | 427 | procedure p(x : string) is 428 | variable l : line ; 429 | begin 430 | write(l, x) ; 431 | writeline(output, l) ; 432 | end procedure ; 433 | 434 | -- Internal private types 435 | -- Format Alignment 436 | -- LEFT 'example ' 437 | -- RIGHT ' example' 438 | -- CENTERED ' example ' 439 | -- SIGN_EDGE '+ 3' 440 | type align_t is (LEFT, RIGHT, CENTERED, SIGN_EDGE) ; 441 | 442 | -- Format Class 443 | -- b Binary 444 | -- c Character 445 | -- d Signed integer 446 | -- e Floating point (exp notation) 447 | -- f Floating point (fixed notation) 448 | -- o Octal 449 | -- s String 450 | -- t Time value 451 | -- u Unsigned integer 452 | -- x Hexadecimal 453 | type class_t is (BINARY, CHAR, INT, FLOAT_EXP, FLOAT_FIXED, OCTAL, STR, TIMEVAL, UINT, HEX) ; 454 | 455 | function f(sfmt : string ; value : align_t) return string ; 456 | function f(sfmt : string ; value : class_t) return string ; 457 | function f(value : align_t ; sfmt : string := "s") return string ; 458 | function f(value : class_t ; sfmt : string := "s") return string ; 459 | 460 | -- [fill][align][sign][width][.precision][class] 461 | -- NOTE: # after sign might be good for prefixes (0b, 0o, 0x) and might be easy to implement. 462 | -- NOTE: Grouping might be good, but python only limits to [,_] and doesn't allow for arbitrary 463 | -- grouping size. Could be arbitrary character like fill, and how many digits? Sounds complicated, though. 464 | type fmt_spec_t is record 465 | fill : character ; 466 | align : align_t ; 467 | sign : boolean ; 468 | width : natural ; 469 | precision : natural ; 470 | class : class_t ; 471 | end record ; 472 | 473 | constant DEFAULT_FMT_SPEC : fmt_spec_t := ( 474 | fill => ' ', 475 | align => LEFT, 476 | sign => false, 477 | width => 0, 478 | precision => 0, 479 | class => STR 480 | ) ; 481 | 482 | -- Private Helper functions 483 | function parse(sfmt : string ; default_class : class_t := STR) return fmt_spec_t ; 484 | 485 | -- Collapse align_t to be side (LEFT, RIGHT) 486 | function to_side(value : align_t) return side ; 487 | 488 | -- Helper functions for line manipulation for custom f-functions 489 | procedure fill(variable l : inout line ; variable fmt_spec : fmt_spec_t ; variable fillcount : inout natural) ; 490 | procedure shift(variable l : inout line ; count : in natural) ; 491 | 492 | --------------------------------------------------------------------------- 493 | -- VHDL-2008 Generic Function 494 | --------------------------------------------------------------------------- 495 | --function f 496 | -- generic(type t; function to_string(x : t) return string is <>) 497 | -- parameter(value : t ; sfmt : string := "s") 498 | -- return string 499 | --is 500 | --begin 501 | -- return fstr(to_string(value), sfmt) ; 502 | --end function ; 503 | 504 | --function f 505 | -- generic(type t) 506 | -- parameter(value : t ; sfmt : string := "s") 507 | -- return string 508 | --is 509 | --begin 510 | -- return fstr(t'image(value), sfmt) ; 511 | --end function ; 512 | 513 | function parse(sfmt : string ; default_class : class_t := STR) return fmt_spec_t is 514 | type fsm_t is (START, FILL, ALIGN, SIGN, WIDTH, DOT, PRECISION, CLASS, EXTRA) ; 515 | alias fn : string(1 to sfmt'length) is sfmt ; 516 | variable l : line := null ; 517 | variable fsm : fsm_t := START ; 518 | variable rv : fmt_spec_t := DEFAULT_FMT_SPEC ; 519 | variable idx : positive := 1 ; 520 | variable numstart : natural := 0 ; 521 | variable numstop : natural := 0 ; 522 | variable precision_present : boolean := false ; 523 | begin 524 | assert fn'length > 0 525 | report "Format string must not be empty" 526 | severity warning ; 527 | rv.class := default_class ; 528 | while idx <= fn'length loop 529 | case fsm is 530 | when START => 531 | if fn'length = 1 then 532 | -- Only a single character 533 | case fn(idx) is 534 | when '<'|'>'|'^'|'=' => 535 | -- Alignment but it doesn't matter since no width 536 | null ; 537 | 538 | when '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 539 | -- Single character width 540 | fsm := WIDTH ; 541 | 542 | when '+' => 543 | -- Sign 544 | rv.sign := true ; 545 | 546 | when 'b'|'B'|'c'|'C'|'d'|'D'|'e'|'E'|'f'|'F'|'o'|'O'|'s'|'S'|'t'|'T'|'u'|'U'|'x'|'X' => 547 | -- Class 548 | fsm := CLASS ; 549 | 550 | when '.' => 551 | -- Illegal precision 552 | report "Format specifier missing precision" 553 | severity warning ; 554 | exit ; 555 | 556 | when others => 557 | report fstr("Unknown format code: {}", f(fn(idx))) 558 | severity warning ; 559 | exit ; 560 | end case ; 561 | else 562 | -- Guaranteed to be at least 2 characters 563 | case fn(idx) is 564 | -- Check the first character class 565 | when '<'|'>'|'^'|'=' => 566 | -- Alignment character first, but could also be a fill character 567 | case fn(idx+1) is 568 | when '<'|'>'|'^'|'=' => 569 | -- 2 alignment characters in a row, so one must be for filling 570 | fsm := FILL ; 571 | when others => 572 | -- Alignment character is first, followed by a non-alignment character 573 | fsm := ALIGN ; 574 | end case ; 575 | 576 | when '+' => 577 | -- Sign character first, but might be fill, check for alignment character next 578 | case fn(idx+1) is 579 | when '<'|'>'|'^'|'=' => 580 | -- Alignment character second, so consume FILL character 581 | fsm := FILL ; 582 | when others => 583 | -- Second character is not an alignment character 584 | -- Assume first character is alignment and not fill 585 | fsm := SIGN ; 586 | end case ; 587 | 588 | when '0' => 589 | -- With a leading zero, either FILL or WIDTH 590 | case fn(idx+1) is 591 | when '+'|'.' => 592 | fsm := WIDTH ; 593 | when others => 594 | fsm := FILL ; 595 | end case ; 596 | 597 | when '1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 598 | case fn(idx+1) is 599 | when '<'|'>'|'^'|'='|'+' => 600 | -- Non-Zero number followed by alignment character or sign, so consume as fill character 601 | fsm := FILL ; 602 | when others => 603 | -- Non-Zero number followed by something else, so assume width 604 | fsm := WIDTH ; 605 | end case ; 606 | 607 | when '.' => 608 | -- Start with DOT precision 609 | fsm := DOT ; 610 | 611 | when others => 612 | case fn(idx+1) is 613 | when '<'|'>'|'^'|'='|'0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 614 | -- Alignment character is second, so fill character is first 615 | fsm := FILL ; 616 | 617 | when others => 618 | report fmt("Invalid format specifier: {}", fstr(fn)) 619 | severity warning ; 620 | exit ; 621 | end case ; 622 | 623 | end case ; 624 | end if ; 625 | next ; 626 | 627 | when FILL => 628 | rv.fill := fn(idx) ; 629 | idx := idx + 1 ; 630 | fsm := ALIGN ; 631 | next ; 632 | 633 | when ALIGN => 634 | case fn(idx) is 635 | when '<' => 636 | rv.align := LEFT ; 637 | idx := idx + 1 ; 638 | when '>' => 639 | rv.align := RIGHT ; 640 | idx := idx + 1 ; 641 | when '^' => 642 | rv.align := CENTERED ; 643 | idx := idx + 1 ; 644 | when '=' => 645 | rv.align := SIGN_EDGE ; 646 | idx := idx + 1 ; 647 | when others => 648 | null ; 649 | end case ; 650 | fsm := SIGN ; 651 | 652 | when SIGN => 653 | case fn(idx) is 654 | when '+' => 655 | rv.sign := true ; 656 | idx := idx + 1 ; 657 | 658 | when others => 659 | null ; 660 | end case ; 661 | fsm := WIDTH ; 662 | 663 | when WIDTH => 664 | case fn(idx) is 665 | when '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 666 | if numstart = 0 then 667 | numstart := idx ; 668 | numstop := idx ; 669 | elsif numstart > 0 then 670 | numstop := idx ; 671 | end if ; 672 | idx := idx + 1 ; 673 | when others => 674 | if numstart > 0 then 675 | l := new string'(fn(numstart to numstop)) ; 676 | read(l, rv.width) ; 677 | numstart := 0 ; 678 | numstop := 0 ; 679 | end if ; 680 | fsm := DOT ; 681 | end case ; 682 | 683 | when DOT => 684 | case fn(idx) is 685 | when '.' => 686 | idx := idx + 1 ; 687 | fsm := PRECISION ; 688 | when others => 689 | fsm := CLASS ; 690 | end case ; 691 | 692 | when PRECISION => 693 | case fn(idx) is 694 | when '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 695 | if numstart = 0 then 696 | numstart := idx ; 697 | numstop := idx ; 698 | elsif numstart > 0 then 699 | numstop := idx ; 700 | end if ; 701 | idx := idx + 1 ; 702 | when others => 703 | if numstart > 0 then 704 | l := new string'(fn(numstart to numstop)) ; 705 | read(l, rv.precision) ; 706 | precision_present := true ; 707 | numstart := 0 ; 708 | numstop := 0 ; 709 | else 710 | report "Format specifier missing precision" 711 | severity warning ; 712 | end if ; 713 | fsm := CLASS ; 714 | end case ; 715 | 716 | when CLASS => 717 | case fn(idx) is 718 | when 'b'|'B' => 719 | rv.class := BINARY ; 720 | when 'c'|'C' => 721 | rv.class := CHAR ; 722 | when 'd'|'D' => 723 | rv.class := INT ; 724 | when 'e'|'E' => 725 | -- Normalized d[.precision]e[+-]dd notation 726 | rv.class := FLOAT_EXP ; 727 | if precision_present = false then 728 | -- Precision isn't mentioned, so default to 6 729 | rv.precision := 6 ; 730 | end if ; 731 | when 'f'|'F' => 732 | if precision_present = true then 733 | if rv.precision = 0 then 734 | -- Precision was specified, so change the class to int to cut off 735 | -- any decimal representation 736 | rv.class := INT ; 737 | else 738 | rv.class := FLOAT_FIXED ; 739 | end if ; 740 | else 741 | -- Precision isn't present, so default the precision to 6 places 742 | rv.precision := 6 ; 743 | rv.class := FLOAT_FIXED ; 744 | end if ; 745 | when 'o'|'O' => 746 | rv.class := OCTAL ; 747 | when 's'|'S' => 748 | rv.class := STR ; 749 | when 't'|'T' => 750 | rv.class := TIMEVAL ; 751 | when 'u'|'U' => 752 | rv.class := UINT ; 753 | when 'x'|'X' => 754 | rv.class := HEX ; 755 | when others => 756 | rv.class := BINARY ; 757 | report fmt("Unknown class: {} is not [bcdofsux] - defaulting to BINARY", f(fn(idx))) 758 | severity warning ; 759 | end case ; 760 | idx := idx + 1 ; 761 | fsm := EXTRA ; 762 | 763 | when EXTRA => 764 | report fmt("Extra characters in format specifier ignored : {}", fstr(fn(idx to fn'length))) 765 | severity warning ; 766 | exit ; 767 | 768 | end case ; 769 | end loop ; 770 | 771 | -- Parse the last bit of data 772 | case fsm is 773 | when WIDTH => 774 | l := new string'(sfmt(numstart to numstop)) ; 775 | read(l, rv.width) ; 776 | when PRECISION => 777 | l := new string'(sfmt(numstart to numstop)) ; 778 | read(l, rv.precision) ; 779 | when others => 780 | null ; 781 | end case ; 782 | return rv ; 783 | end function ; 784 | 785 | procedure shift(variable l : inout line ; count : in natural) is 786 | variable newl : line := new string'(l.all) ; 787 | variable dest : positive := count + 1 ; 788 | begin 789 | if count > 0 then 790 | for idx in l'range loop 791 | newl(dest) := l(idx) ; 792 | dest := dest + 1 ; 793 | if dest = l'length + 1 then 794 | dest := 1 ; 795 | end if ; 796 | end loop ; 797 | l := newl ; 798 | end if ; 799 | end procedure ; 800 | 801 | function to_side(value : align_t) return side is 802 | begin 803 | case value is 804 | when RIGHT|SIGN_EDGE => 805 | return right ; 806 | when others => 807 | return left ; 808 | end case ; 809 | end function ; 810 | 811 | procedure fill(variable l : inout line ; variable fmt_spec : fmt_spec_t ; variable fillcount : inout natural) is 812 | variable inc : integer ; 813 | variable idx : integer ; 814 | begin 815 | fillcount := 0 ; 816 | case fmt_spec.align is 817 | when RIGHT|SIGN_EDGE => 818 | -- Start on the left side to fill in 819 | idx := 1 ; 820 | inc := 1 ; 821 | when others => 822 | -- Start on the right side to fill in 823 | idx := l'length ; 824 | inc := -1 ; 825 | end case ; 826 | while true loop 827 | if l(idx) = ' ' then 828 | fillcount := fillcount + 1 ; 829 | l(idx) := fmt_spec.fill ; 830 | idx := idx + inc ; 831 | else 832 | exit ; 833 | end if ; 834 | end loop ; 835 | end procedure ; 836 | 837 | function f(value : string ; sfmt : string := "s") return string is 838 | alias s : string(1 to value'length) is value ; 839 | variable fmt_spec : fmt_spec_t := parse(sfmt, STR) ; 840 | variable l : line ; 841 | variable fillcount : integer := fmt_spec.width - value'length ; 842 | constant static_fill : string(1 to fillcount) := (others => fmt_spec.fill) ; 843 | begin 844 | if (fmt_spec.precision > 0) and (value'length > fmt_spec.precision) then 845 | -- Limiting the string size based on precision 846 | return s(1 to fmt_spec.precision) ; 847 | else 848 | -- The string might have spaces included, so lets create 849 | fillcount := fmt_spec.width - value'length ; 850 | case fmt_spec.align is 851 | when LEFT|CENTERED => 852 | write(l, s & static_fill, to_side(fmt_spec.align)) ; 853 | when others => 854 | write(l, static_fill & s, to_side(fmt_spec.align)) ; 855 | end case ; 856 | end if ; 857 | if fillcount > 0 and fmt_spec.align = CENTERED then 858 | shift(l, fillcount/2) ; 859 | end if ; 860 | return l.all ; 861 | end function ; 862 | 863 | function f(value : align_t ; sfmt : string := "s") return string is 864 | constant s : string := align_t'image(value) ; 865 | begin 866 | return fstr(s, sfmt) ; 867 | end function ; 868 | 869 | function f(value : class_t ; sfmt : string := "s") return string is 870 | constant s : string := class_t'image(value) ; 871 | begin 872 | return fstr(s, sfmt) ; 873 | end function ; 874 | 875 | function f(value : bit ; sfmt : string := "b") return string is 876 | variable fmt_spec : fmt_spec_t := parse(sfmt, BINARY) ; 877 | variable l : line ; 878 | variable fillcount : natural ; 879 | begin 880 | write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 881 | fill(l, fmt_spec, fillcount) ; 882 | if fmt_spec.align = CENTERED then 883 | shift(l, fillcount/2) ; 884 | end if ; 885 | return l.all ; 886 | end function ; 887 | 888 | function f(value : bit_vector ; sfmt : string := "b") return string is 889 | variable fmt_spec : fmt_spec_t := parse(sfmt, BINARY) ; 890 | variable l : line ; 891 | variable fillcount : natural ; 892 | begin 893 | case fmt_spec.class is 894 | when BINARY => 895 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 896 | when OCTAL => 897 | owrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 898 | when HEX => 899 | hwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 900 | when others => 901 | report f("Unsupported class for bit_vector ({}), using binary: {}", fmt_spec.class) 902 | severity warning ; 903 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 904 | end case ; 905 | fill(l, fmt_spec, fillcount) ; 906 | if fmt_spec.align = CENTERED then 907 | shift(l, fillcount/2) ; 908 | end if ; 909 | return l.all ; 910 | end function ; 911 | 912 | function f(value : boolean ; sfmt : string := "s") return string is 913 | variable fmt_spec : fmt_spec_t := parse(sfmt, BINARY) ; 914 | variable l : line ; 915 | variable bit_arg : bit := '0' ; 916 | variable fillcount : natural ; 917 | begin 918 | case fmt_spec.class is 919 | when STR => 920 | write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 921 | 922 | when BINARY => 923 | if value = true then 924 | bit_arg := '1' ; 925 | end if ; 926 | l := new string'(f(bit_arg, sfmt)) ; 927 | return l.all ; 928 | 929 | when others => 930 | report fstr("Unsupported class for boolean - {}, using STR", f(fmt_spec.class)) 931 | severity warning ; 932 | write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 933 | end case ; 934 | fill(l, fmt_spec, fillcount) ; 935 | if fmt_spec.align = CENTERED then 936 | shift(l, fillcount/2) ; 937 | end if ; 938 | return l.all ; 939 | end function ; 940 | 941 | function f(value : character ; sfmt : string := "c") return string is 942 | variable fmt_spec : fmt_spec_t := parse(sfmt, CHAR) ; 943 | variable l : line ; 944 | variable fillcount : natural ; 945 | begin 946 | write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 947 | fill(l, fmt_spec, fillcount) ; 948 | if fmt_spec.align = CENTERED then 949 | shift(l, fillcount/2) ; 950 | end if ; 951 | return l.all ; 952 | end function ; 953 | 954 | function f(value : time ; sfmt : string := ".9t" ) return string is 955 | variable fmt_spec : fmt_spec_t := parse(sfmt, TIMEVAL) ; 956 | variable l : line ; 957 | variable unit : time := 1 ns ; 958 | variable fillcount : natural ; 959 | begin 960 | case fmt_spec.precision is 961 | when 0 => unit := 1 sec ; 962 | when 3 => unit := 1 ms ; 963 | when 6 => unit := 1 us ; 964 | when 9 => unit := 1 ns ; 965 | when 12 => unit := 1 ps ; 966 | when 15 => unit := 1 fs ; 967 | when others => 968 | report fmt("Time precision unknown: {}, using default", f(fmt_spec.precision)) 969 | severity warning ; 970 | end case ; 971 | write(l, value, to_side(fmt_spec.align), fmt_spec.width, unit) ; 972 | fill(l, fmt_spec, fillcount) ; 973 | if fmt_spec.align = CENTERED then 974 | shift(l, fillcount/2) ; 975 | end if ; 976 | return l.all ; 977 | end function ; 978 | 979 | procedure add_sign(variable l : inout line ; s : character ; fmt_fill : character ) is 980 | variable idx : natural := 1 ; 981 | begin 982 | while l(idx) = fmt_fill loop 983 | idx := idx + 1 ; 984 | end loop ; 985 | l(idx-1) := s ; 986 | end procedure ; 987 | 988 | function f(value : integer ; sfmt : string := "d") return string is 989 | variable fmt_spec : fmt_spec_t := parse(sfmt, INT) ; 990 | variable l : line := null ; 991 | variable temp : line := null ; 992 | variable fillcount : natural := 0 ; 993 | variable sign : character ; 994 | begin 995 | write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 996 | fill(l, fmt_spec, fillcount) ; 997 | if fmt_spec.align = CENTERED then 998 | shift(l, fillcount/2) ; 999 | end if ; 1000 | if fmt_spec.sign = true then 1001 | if fmt_spec.width = 0 then 1002 | write(temp, l.all, right, l'length+1) ; 1003 | l := temp ; 1004 | else 1005 | -- Has a specific size, so shift it over only if there isn't fill at the start 1006 | if l(1) /= fmt_spec.fill then 1007 | shift(l, 1) ; 1008 | end if ; 1009 | end if ; 1010 | if fmt_spec.align = SIGN_EDGE or fmt_spec.width = 0 then 1011 | if value < 0 then 1012 | l(1) := '-' ; 1013 | else 1014 | l(1) := '+' ; 1015 | end if ; 1016 | else 1017 | if value < 0 then 1018 | sign := '-' ; 1019 | else 1020 | sign := '+' ; 1021 | end if ; 1022 | add_sign(l, sign, fmt_spec.fill) ; 1023 | end if ; 1024 | end if ; 1025 | --case fmt_spec.class is 1026 | -- when BINARY => 1027 | -- return f(ieee.std_logic_1164.std_logic_vector(ieee.numeric_std.to_unsigned(value, fmt_spec.width)), sfmt) ; 1028 | -- when OCTAL => 1029 | -- return f(ieee.std_logic_1164.std_logic_vector(ieee.numeric_std.to_unsigned(value, fmt_spec.width*3)), sfmt) ; 1030 | -- when HEX => 1031 | -- return f(ieee.std_logic_1164.std_logic_vector(ieee.numeric_std.to_unsigned(value, fmt_spec.width*4)), sfmt) ; 1032 | -- when INT|UINT => 1033 | -- write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1034 | -- when others => 1035 | -- report "Unsure what to do here" 1036 | -- severity warning ; 1037 | --end case ; 1038 | return l.all ; 1039 | end function ; 1040 | 1041 | function f(value : sfixed ; sfmt : string := "f") return string is 1042 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1043 | variable l : line ; 1044 | begin 1045 | case fmt_spec.class is 1046 | when INT => 1047 | -- Casting: 1048 | -- sfixed -> std_logic_vector 1049 | -- std_logic_vector -> signed 1050 | -- signed -> integer 1051 | -- format integer 1052 | return f(to_integer(signed(to_slv(value))), sfmt) ; 1053 | 1054 | when FLOAT_EXP|FLOAT_FIXED => 1055 | -- Casting: 1056 | -- sfixed -> real 1057 | -- format real 1058 | return f(to_real(value), sfmt) ; 1059 | 1060 | when OCTAL => 1061 | -- Casting: 1062 | -- sfixed -> formatted string 1063 | owrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1064 | return l.all ; 1065 | 1066 | when HEX => 1067 | -- Casting 1068 | -- sfixed -> formatted string using hwrite 1069 | hwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1070 | return l.all ; 1071 | 1072 | when BINARY => 1073 | -- Casting 1074 | -- sfixed -> formatted string using bwrite 1075 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1076 | return l.all ; 1077 | 1078 | when others => 1079 | report "Could not apply format to sfixed" severity failure ; 1080 | end case ; 1081 | end function ; 1082 | 1083 | function f(value : ufixed ; sfmt : string := "f") return string is 1084 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1085 | variable l : line ; 1086 | begin 1087 | case fmt_spec.class is 1088 | when INT => 1089 | -- Casting: 1090 | -- ufixed -> std_logic_vector 1091 | -- std_logic_vector -> unsigned 1092 | -- signed -> integer 1093 | -- format integer 1094 | return f(to_integer(unsigned(to_slv(value))), sfmt) ; 1095 | 1096 | when FLOAT_EXP|FLOAT_FIXED => 1097 | -- Casting: 1098 | -- ufixed -> real 1099 | -- format real 1100 | return f(to_real(value), sfmt) ; 1101 | 1102 | when OCTAL => 1103 | -- Casting: 1104 | -- ufixed -> formatted string 1105 | owrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1106 | return l.all ; 1107 | 1108 | when HEX => 1109 | -- Casting 1110 | -- ufixed -> formatted string using hwrite 1111 | hwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1112 | return l.all ; 1113 | 1114 | when BINARY => 1115 | -- Casting 1116 | -- ufixed -> formatted string using bwrite 1117 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1118 | return l.all ; 1119 | 1120 | when others => 1121 | report "Could not apply format to ufixed:" & class_t'image(fmt_spec.class) severity failure ; 1122 | end case ; 1123 | end function ; 1124 | 1125 | function f(value : signed ; sfmt : string := "d") return string is 1126 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1127 | variable l : line ; 1128 | begin 1129 | case fmt_spec.class is 1130 | when INT => 1131 | -- Casting: 1132 | -- signed -> integer 1133 | -- format integer 1134 | return f(to_integer(value), sfmt) ; 1135 | 1136 | when OCTAL => 1137 | -- Casting: 1138 | -- signed -> formatted string 1139 | owrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1140 | return l.all ; 1141 | 1142 | when HEX => 1143 | -- Casting 1144 | -- signed -> formatted string using hwrite 1145 | hwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1146 | return l.all ; 1147 | 1148 | when BINARY => 1149 | -- Casting 1150 | -- signed -> formatted string using bwrite 1151 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1152 | return l.all ; 1153 | 1154 | when others => 1155 | report "Could not apply format to signed" & class_t'image(fmt_spec.class) severity failure ; 1156 | end case ; 1157 | end function ; 1158 | 1159 | function f(value : unsigned ; sfmt : string := "d") return string is 1160 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1161 | variable l : line ; 1162 | begin 1163 | case fmt_spec.class is 1164 | when INT => 1165 | -- Casting: 1166 | -- unsigned -> integer 1167 | -- format integer 1168 | return f(to_integer(value), sfmt) ; 1169 | 1170 | when OCTAL => 1171 | -- Casting: 1172 | -- unsigned -> formatted string 1173 | owrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1174 | return l.all ; 1175 | 1176 | when HEX => 1177 | -- Casting 1178 | -- unsigned -> formatted string using hwrite 1179 | hwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1180 | return l.all ; 1181 | 1182 | when BINARY => 1183 | -- Casting 1184 | -- unsigned -> formatted string using bwrite 1185 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1186 | return l.all ; 1187 | 1188 | when others => 1189 | report "Could not apply format to unsigned" & class_t'image(fmt_spec.class) severity failure ; 1190 | end case ; 1191 | end function ; 1192 | 1193 | function f(value : std_logic ; sfmt : string := "b") return string is 1194 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1195 | variable l : line ; 1196 | begin 1197 | case fmt_spec.class is 1198 | when BINARY => 1199 | write(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1200 | return l.all ; 1201 | 1202 | when others => 1203 | report "Could not apply format to std_logic: " & class_t'image(fmt_spec.class) severity failure ; 1204 | end case ; 1205 | end function ; 1206 | 1207 | function f(value : std_logic_vector ; sfmt : string := "b") return string is 1208 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1209 | variable l : line ; 1210 | begin 1211 | case fmt_spec.class is 1212 | when INT => 1213 | -- Casting: 1214 | -- std_logic_vector -> signed 1215 | -- unsigned -> integer 1216 | -- format integer 1217 | return f(to_integer(signed(value)), sfmt) ; 1218 | 1219 | when UINT => 1220 | -- Casting: 1221 | -- std_logic_vector -> unsigned 1222 | -- unsigned -> integer 1223 | -- format integer 1224 | return f(to_integer(unsigned(value)), sfmt) ; 1225 | 1226 | when OCTAL => 1227 | -- Casting: 1228 | -- std_logic_vector -> formatted string 1229 | owrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1230 | return l.all ; 1231 | 1232 | when HEX => 1233 | -- Casting 1234 | -- std_logic_vector -> formatted string using hwrite 1235 | hwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1236 | return l.all ; 1237 | 1238 | when BINARY => 1239 | -- Casting 1240 | -- std_logic_vector -> formatted string using bwrite 1241 | bwrite(l, value, to_side(fmt_spec.align), fmt_spec.width) ; 1242 | return l.all ; 1243 | 1244 | when others => 1245 | report "Could not apply format to signed" & class_t'image(fmt_spec.class) severity failure ; 1246 | end case ; 1247 | end function ; 1248 | 1249 | function f(value : real ; sfmt : string := "f") return string is 1250 | variable fmt_spec : fmt_spec_t := parse(sfmt, FLOAT_FIXED) ; 1251 | variable l : line := null ; 1252 | variable exp : line := null ; 1253 | variable precision : line := null ; 1254 | variable temp : line := null ; 1255 | variable tempreal : real := 0.0 ; 1256 | variable fillcount : natural := 0 ; 1257 | variable sign : character ; 1258 | variable prec : real ; 1259 | variable expstart : positive := 1 ; 1260 | begin 1261 | if fmt_spec.class = INT then 1262 | -- Cast to an integer 1263 | return f(integer(value), sfmt) ; 1264 | elsif fmt_spec.class = FLOAT_EXP then 1265 | -- Limit the precision first so things round correctly 1266 | if fmt_spec.align = SIGN_EDGE then 1267 | write(exp, abs(value), left, fmt_spec.width, 0) ; 1268 | else 1269 | write(exp, value, left, fmt_spec.width, 0) ; 1270 | end if ; 1271 | -- Find expstart 1272 | while expstart < exp'length and exp(expstart) /= 'e' and exp(expstart) /= 'E' loop 1273 | expstart := expstart + 1 ; 1274 | end loop ; 1275 | if expstart = exp'length then 1276 | expstart := exp'length + 1 ; 1277 | end if ; 1278 | 1279 | -- Precision is just for digits, not for sign 1280 | prec := abs(value) ; 1281 | 1282 | -- Make sure we're going to get the right decimal points 1283 | while prec > 10.0 loop 1284 | prec := prec / 10.0 ; 1285 | end loop ; 1286 | -- Make sure we are e+00 1287 | if abs(prec) < 0.1 then 1288 | prec := 1.0 + prec ; 1289 | end if ; 1290 | write(precision, prec, left, 0, fmt_spec.precision); 1291 | -- ... now concatenate the info from exp and precision into the full string 1292 | if fmt_spec.precision > 0 then 1293 | if value < 0.0 and fmt_spec.align /= SIGN_EDGE then 1294 | l := new string'(exp(1 to 3) & precision(3 to 3+fmt_spec.precision-1) & exp(expstart to exp'high)) ; 1295 | else 1296 | l := new string'(exp(1 to 2) & precision(3 to 3+fmt_spec.precision-1) & exp(expstart to exp'high)) ; 1297 | end if ; 1298 | else 1299 | if value < 0.0 and fmt_spec.align /= SIGN_EDGE then 1300 | l := new string'(exp(1 to 2) & exp(expstart to exp'high)) ; 1301 | else 1302 | l := new string'(exp(1 to 1) & exp(expstart to exp'high)) ; 1303 | end if ; 1304 | end if ; 1305 | -- Justify the string that is left justified 1306 | write(temp, l.all, to_side(fmt_spec.align), fmt_spec.width) ; 1307 | l := temp ; 1308 | temp := null ; 1309 | elsif fmt_spec.class = FLOAT_FIXED then 1310 | if fmt_spec.align = SIGN_EDGE then 1311 | -- Don't write the sign since SIGN_EDGE formatting down lower takes care of it 1312 | write(l, abs(value), to_side(fmt_spec.align), fmt_spec.width, fmt_spec.precision) ; 1313 | else 1314 | write(l, value, to_side(fmt_spec.align), fmt_spec.width, fmt_spec.precision) ; 1315 | end if ; 1316 | end if ; 1317 | 1318 | if value < 0.0 then 1319 | sign := '-' ; 1320 | else 1321 | sign := '+' ; 1322 | end if ; 1323 | 1324 | fill(l, fmt_spec, fillcount) ; 1325 | 1326 | case fmt_spec.align is 1327 | 1328 | when SIGN_EDGE => 1329 | if fmt_spec.sign = false then 1330 | report "Format alignment set to SIGN_EDGE without SIGN - assuming SIGN" 1331 | severity warning ; 1332 | end if ; 1333 | if fillcount = 0 then 1334 | write(temp, l.all, right, l'length+1) ; 1335 | l := temp ; 1336 | end if ; 1337 | l(1) := sign ; 1338 | 1339 | when LEFT|CENTERED => 1340 | if not (value < 0.0) and fmt_spec.sign = true then 1341 | if fillcount = 0 then 1342 | -- Full number in line, but we need room for the sign, so extend by 1 1343 | write(temp, l.all, left, l'length+1) ; 1344 | l := temp ; 1345 | end if ; 1346 | -- Shift the line to the right by 1 1347 | shift(l, 1) ; 1348 | 1349 | -- Add the sign 1350 | l(1) := sign ; 1351 | end if ; 1352 | 1353 | if fmt_spec.align = CENTERED then 1354 | -- Alignment just needs to be shifted 1355 | shift(l, fillcount/2) ; 1356 | end if ; 1357 | 1358 | when RIGHT => 1359 | if fmt_spec.sign = true then 1360 | if fillcount = 0 then 1361 | -- Full number in line, but we need room for the sign, so extend by 1 1362 | write(temp, l.all, right, l'length+1) ; 1363 | l := temp ; 1364 | end if ; 1365 | -- Add the sign 1366 | if not (value < 0.0) then 1367 | add_sign(l, sign, fmt_spec.fill) ; 1368 | end if ; 1369 | end if ; 1370 | 1371 | when others => 1372 | report "Got into a strange place" 1373 | severity warning ; 1374 | end case ; 1375 | 1376 | return l.all ; 1377 | end function ; 1378 | 1379 | procedure reformat(variable l : inout line ; sfmt : in string) is 1380 | type bv_ptr is access bit_vector ; 1381 | constant fmt_spec : fmt_spec_t := parse(sfmt) ; 1382 | variable good : boolean ; 1383 | variable bit_arg : bit ; 1384 | variable bv_arg_ptr : bv_ptr ; 1385 | variable boolean_arg : boolean ; 1386 | variable integer_arg : integer ; 1387 | variable real_arg : real ; 1388 | variable time_arg : time ; 1389 | variable strl : line := l ; 1390 | begin 1391 | case fmt_spec.class is 1392 | when STR => 1393 | -- Just strings that need to be put into the actual format 1394 | l := new string'(fstr(strl.all, sfmt)) ; 1395 | 1396 | when BINARY => 1397 | -- boolean, bit, bit_vector, or integer could be the source for this 1398 | -- Try boolean: true|false 1399 | read(strl, boolean_arg, good) ; 1400 | if good = true then 1401 | if sfmt'length > 0 then 1402 | l := new string'(f(boolean_arg, sfmt)) ; 1403 | else 1404 | l := strl ; 1405 | end if ; 1406 | end if; 1407 | 1408 | -- Try bit_vector 1409 | if good = false then 1410 | bv_arg_ptr := new bit_vector(0 to strl'length-1) ; 1411 | read(strl, bv_arg_ptr.all, good) ; 1412 | if good = true then 1413 | if sfmt'length > 0 then 1414 | l := new string'(f(bv_arg_ptr.all, sfmt)) ; 1415 | else 1416 | l := strl ; 1417 | end if ; 1418 | end if ; 1419 | end if ; 1420 | 1421 | -- Try integer: note integer might be read, but actually bit_vector, so ambiguous should be noted 1422 | if good = false then 1423 | read(strl, integer_arg, good) ; 1424 | if good = true then 1425 | if sfmt'length > 0 then 1426 | l := new string'(f(integer_arg, sfmt)) ; 1427 | else 1428 | l := strl ; 1429 | end if ; 1430 | end if ; 1431 | end if ; 1432 | 1433 | -- Never parsed it so warn 1434 | if good = false then 1435 | report fmt("Could not parse '{}' as BINARY", strl.all) 1436 | severity warning ; 1437 | end if ; 1438 | 1439 | when OCTAL|HEX => 1440 | -- Try bit_vector 1441 | bv_arg_ptr := new bit_vector(0 to strl'length-1) ; 1442 | read(strl, bv_arg_ptr.all, good) ; 1443 | if good = true then 1444 | if sfmt'length > 0 then 1445 | l := new string'(f(bv_arg_ptr.all, sfmt)) ; 1446 | else 1447 | l := strl ; 1448 | end if ; 1449 | end if ; 1450 | 1451 | if good = false then 1452 | -- Try integer 1453 | read(strl, integer_arg, good) ; 1454 | if good = true then 1455 | if sfmt'length > 0 then 1456 | l := new string'(f(integer_arg, sfmt)) ; 1457 | else 1458 | l := strl ; 1459 | end if ; 1460 | end if ; 1461 | end if ; 1462 | 1463 | -- Never parsed it so warn 1464 | if good = false then 1465 | report fmt("Could not parse '{}' as OCTAL or HEX", strl.all) 1466 | severity warning ; 1467 | end if ; 1468 | 1469 | when CHAR => 1470 | if sfmt'length > 0 then 1471 | l := new string'(f(strl(strl'low), sfmt)) ; 1472 | else 1473 | l := strl ; 1474 | end if ; 1475 | 1476 | when INT|UINT => 1477 | -- Try integer 1478 | read(strl, integer_arg, good) ; 1479 | if good = false then 1480 | report fmt("Invalid integer argument: {}", strl.all) 1481 | severity warning ; 1482 | end if ; 1483 | if sfmt'length > 0 then 1484 | l := new string'(f(integer_arg, sfmt)) ; 1485 | else 1486 | l := strl ; 1487 | end if ; 1488 | 1489 | -- Try bit_vector 1490 | 1491 | when FLOAT_EXP|FLOAT_FIXED => 1492 | read(strl, real_arg, good) ; 1493 | if good = false then 1494 | report fmt("Invalid real argument: {}", strl.all) 1495 | severity warning ; 1496 | end if ; 1497 | if sfmt'length > 0 then 1498 | l := new string'(f(real_arg, sfmt)) ; 1499 | else 1500 | l := strl ; 1501 | end if ; 1502 | 1503 | when TIMEVAL => 1504 | read(strl, time_arg, good) ; 1505 | if good = false then 1506 | report fmt("Invalid time argument: {}", strl.all) 1507 | severity warning ; 1508 | end if ; 1509 | if sfmt'length > 0 then 1510 | l := new string'(f(time_arg, sfmt)) ; 1511 | else 1512 | l := strl ; 1513 | end if ; 1514 | 1515 | when others => 1516 | report "Unknown type to reformat" 1517 | severity warning ; 1518 | end case ; 1519 | end procedure ; 1520 | 1521 | procedure create_parts(fn : string ; variable parts : inout string_list ; variable args : inout string_list) is 1522 | type fsm_t is (COPY_STRING, LBRACE, RBRACE, READ_ARGNUM, READ_FMT) ; 1523 | variable fsm : fsm_t := COPY_STRING ; 1524 | variable start : positive ; 1525 | variable stop : positive ; 1526 | variable argnum : integer := 0 ; 1527 | variable numstart : positive ; 1528 | variable numstop : positive ; 1529 | variable argnum_used : boolean := false ; 1530 | variable argnum_limited : natural := 0 ; 1531 | variable len : natural ; 1532 | variable l : line ; 1533 | variable fmt_start : positive ; 1534 | variable fmt_stop : positive ; 1535 | begin 1536 | start := 1 ; 1537 | stop := 1 ; 1538 | for i in fn'range loop 1539 | case fsm is 1540 | when COPY_STRING => 1541 | case fn(i) is 1542 | when '{' => 1543 | if i /= 1 then 1544 | -- Copy the current simple string to the parts 1545 | append(parts, fn(start to stop)) ; 1546 | end if ; 1547 | 1548 | -- Parse the { 1549 | fsm := LBRACE ; 1550 | when '}' => 1551 | if i /= 1 then 1552 | -- Copy the simple string to the parts 1553 | append(parts, fn(start to stop)) ; 1554 | end if ; 1555 | 1556 | -- Parse the } 1557 | fsm := RBRACE ; 1558 | when others => 1559 | stop := i ; 1560 | end case ; 1561 | 1562 | when LBRACE => 1563 | case fn(i) is 1564 | when '{' => 1565 | -- {{ so just add a single { 1566 | append(parts, "{") ; 1567 | 1568 | -- Start a new piece on the next character 1569 | start := i + 1 ; 1570 | stop := i ; 1571 | fsm := COPY_STRING ; 1572 | 1573 | when '}' => 1574 | -- {} so add the next argument to the piecs 1575 | 1576 | -- Check the length 1577 | length(args, len) ; 1578 | assert argnum <= len 1579 | report fmt("Too many arguments given the list: {} > {}", f(argnum), f(len)) 1580 | severity warning ; 1581 | if argnum_used = true then 1582 | report fmt("Cannot mix argnum usage in format string: {} @ {}", fn, f(i)) ; 1583 | end if ; 1584 | if argnum >= len then 1585 | argnum_limited := argnum_limited + 1 ; 1586 | argnum := len - 1 ; 1587 | end if ; 1588 | 1589 | -- Append the argument 1590 | get(args, argnum, l) ; 1591 | append(parts, l.all) ; 1592 | argnum := argnum + 1 ; 1593 | 1594 | -- New start position on next character 1595 | start := i + 1 ; 1596 | stop := i ; 1597 | fsm := COPY_STRING ; 1598 | 1599 | when '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 1600 | -- A number to read to get the argument we want 1601 | numstart := i ; 1602 | numstop := i ; 1603 | fsm := READ_ARGNUM ; 1604 | argnum_used := true ; 1605 | 1606 | when ':' => 1607 | -- No argument number here, just format specifier ... 1608 | -- ... but don't increment the argnum yet 1609 | length(args, len) ; 1610 | 1611 | -- Get ready to parse the format specifier 1612 | fmt_start := i + 1 ; 1613 | fmt_stop := i + 1 ; 1614 | fsm := READ_FMT ; 1615 | 1616 | when others => 1617 | report f("Invalid character inside formatter at position {}: {}", f(i), f(fn(i))) 1618 | severity warning ; 1619 | end case ; 1620 | 1621 | when RBRACE => 1622 | case fn(i) is 1623 | when '}' => 1624 | -- }} so remove one of them 1625 | append(parts, "}") ; 1626 | 1627 | -- Start a new piece on the next character 1628 | start := i + 1 ; 1629 | stop := i ; 1630 | fsm := COPY_STRING ; 1631 | 1632 | when others => 1633 | report fmt("Parsing error, RBRACE without corresponding LBRACE or RBRACE at {}: {}", f(i-1), fstr(fn)) 1634 | severity warning ; 1635 | end case ; 1636 | 1637 | when READ_ARGNUM => 1638 | case fn(i) is 1639 | when '}' => 1640 | l := new string'(fn(numstart to numstop)) ; 1641 | read(l, argnum) ; 1642 | 1643 | -- Check the length 1644 | length(args, len) ; 1645 | 1646 | assert argnum < len 1647 | report fmt("Invalid argnum ({}) - total arguments: {}", f(argnum), f(len)) 1648 | severity warning ; 1649 | 1650 | if argnum >= len then 1651 | argnum_limited := argnum_limited + 1 ; 1652 | argnum := len - 1 ; 1653 | end if ; 1654 | 1655 | -- Append the argument from the args string_list 1656 | get(args, argnum, l) ; 1657 | append(parts, l.all) ; 1658 | 1659 | -- Start the next piece 1660 | fsm := COPY_STRING ; 1661 | start := i + 1 ; 1662 | stop := i ; 1663 | 1664 | when '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => 1665 | numstop := i ; 1666 | 1667 | when ':' => 1668 | -- Read the argnum, but go off to read and reformat the argument 1669 | l := new string'(fn(numstart to numstop)) ; 1670 | read(l, argnum) ; 1671 | 1672 | -- Check the length 1673 | length(args, len) ; 1674 | 1675 | assert argnum < len 1676 | report fmt("Invalid argnum ({}) - total arguments: {}", f(argnum), f(len)) 1677 | severity warning ; 1678 | 1679 | if argnum >= len then 1680 | argnum_limited := argnum_limited + 1 ; 1681 | argnum := len - 1 ; 1682 | end if ; 1683 | 1684 | fsm := READ_FMT ; 1685 | fmt_start := i + 1 ; 1686 | fmt_stop := i + 1 ; 1687 | 1688 | when others => 1689 | report fmt("Invalid argument specifier ({}) at position {}", f(fn(i)), f(i)) 1690 | severity warning ; 1691 | 1692 | end case ; 1693 | 1694 | when READ_FMT => 1695 | case fn(i) is 1696 | when '}' => 1697 | -- End of the format so check argument numbers 1698 | length(args, len) ; 1699 | 1700 | assert argnum <= len 1701 | report fmt("Too many arguments given the list: {} > {}", f(argnum), f(len)) 1702 | severity warning ; 1703 | 1704 | -- Keep track of the number of times we limit arguments 1705 | if argnum >= len then 1706 | argnum_limited := argnum_limited + 1 ; 1707 | argnum := len - 1 ; 1708 | end if ; 1709 | 1710 | -- Initial formatted argument now in l 1711 | get(args, argnum, l) ; 1712 | 1713 | -- Reformat 1714 | reformat(l, fn(fmt_start to fmt_stop)) ; 1715 | 1716 | -- Add the reformatted line to the argnums 1717 | append(parts, l.all) ; 1718 | 1719 | -- Increment the argnum if we aren't explicitly noting the arguments 1720 | if argnum_used = false then 1721 | argnum := argnum + 1 ; 1722 | end if ; 1723 | 1724 | -- Start the next piece 1725 | fsm := COPY_STRING ; 1726 | start := i + 1 ; 1727 | stop := i ; 1728 | 1729 | when others => 1730 | -- Haven't closed the brace yet, so just keep going 1731 | fmt_stop := i ; 1732 | 1733 | end case ; 1734 | end case ; 1735 | end loop ; 1736 | 1737 | if fn(start to stop)'length > 0 then 1738 | -- Add the final bit 1739 | append(parts, fn(start to stop) ) ; 1740 | end if ; 1741 | 1742 | -- Check if we hit an argnum limit 1743 | if argnum_limited > 0 then 1744 | length(args, len) ; 1745 | report f("More formats than arguments: {} extra", argnum_limited) 1746 | severity warning ; 1747 | end if ; 1748 | 1749 | if argnum_used = false then 1750 | length(args, len) ; 1751 | if argnum /= len then 1752 | report fmt("Extra arguments passed into format expression - passed {}, but used {}", f(len), f(argnum)) 1753 | severity warning ; 1754 | end if ; 1755 | end if ; 1756 | end procedure ; 1757 | 1758 | procedure f(sfmt : string ; variable args : inout string_list ; variable l : inout line) is 1759 | alias fn : string(1 to sfmt'length) is sfmt ; 1760 | variable parts : string_list ; 1761 | begin 1762 | -- Zero length format string short circuit 1763 | if fn'length = 0 then 1764 | l := new string'("") ; 1765 | return ; 1766 | end if ; 1767 | 1768 | -- Create parts to concatenate removing the formatting 1769 | create_parts(fn, parts, args) ; 1770 | 1771 | -- Return the concatenated parts 1772 | concatenate_list(parts, l) ; 1773 | end procedure ; 1774 | 1775 | function f(sfmt : string ; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 : in string := "") return string is 1776 | -- Normalize the format string 1777 | alias fn : string(1 to sfmt'length) is sfmt ; 1778 | 1779 | -- Arguments and parts of the strings to put together 1780 | variable args : string_list ; 1781 | variable parts : string_list ; 1782 | 1783 | -- Concatenation line 1784 | variable l : line := null ; 1785 | 1786 | -- Add the arguments to the string_list only if the argument isn't null 1787 | procedure add_args is 1788 | variable len : natural ; 1789 | begin 1790 | length(parts, len) ; 1791 | assert len = 0 ; 1792 | length(parts, len) ; 1793 | assert len = 0 ; 1794 | if a0'length = 0 then return ; else append(args, a0) ; end if ; 1795 | if a1'length = 0 then return ; else append(args, a1) ; end if ; 1796 | if a2'length = 0 then return ; else append(args, a2) ; end if ; 1797 | if a3'length = 0 then return ; else append(args, a3) ; end if ; 1798 | if a4'length = 0 then return ; else append(args, a4) ; end if ; 1799 | if a5'length = 0 then return ; else append(args, a5) ; end if ; 1800 | if a6'length = 0 then return ; else append(args, a6) ; end if ; 1801 | if a7'length = 0 then return ; else append(args, a7) ; end if ; 1802 | if a8'length = 0 then return ; else append(args, a8) ; end if ; 1803 | if a9'length = 0 then return ; else append(args, a9) ; end if ; 1804 | if a10'length = 0 then return ; else append(args, a10) ; end if ; 1805 | if a11'length = 0 then return ; else append(args, a11) ; end if ; 1806 | if a12'length = 0 then return ; else append(args, a12) ; end if ; 1807 | if a13'length = 0 then return ; else append(args, a13) ; end if ; 1808 | if a14'length = 0 then return ; else append(args, a14) ; end if ; 1809 | if a15'length = 0 then return ; else append(args, a15) ; end if ; 1810 | end procedure ; 1811 | begin 1812 | -- Zero length format string short circuit 1813 | if fn'length = 0 then 1814 | return "" ; 1815 | end if ; 1816 | 1817 | -- Set the arguments for the formatter 1818 | add_args ; 1819 | 1820 | -- Call into the one taking the string_list for args 1821 | f(sfmt, args, l) ; 1822 | 1823 | -- Return the string 1824 | return l.all ; 1825 | end function ; 1826 | 1827 | -- Single argument formatters 1828 | function f(sfmt : string ; value : align_t) return string is 1829 | begin 1830 | return fmt(sfmt, f(value)) ; 1831 | end function ; 1832 | 1833 | function f(sfmt : string ; value : bit) return string is 1834 | begin 1835 | return fmt(sfmt, f(value)) ; 1836 | end function ; 1837 | 1838 | function f(sfmt : string ; value : bit_vector) return string is 1839 | begin 1840 | return fmt(sfmt, f(value)) ; 1841 | end function ; 1842 | 1843 | function f(sfmt : string ; value : boolean) return string is 1844 | begin 1845 | return fmt(sfmt, f(value)) ; 1846 | end function ; 1847 | 1848 | function f(sfmt : string ; value : character) return string is 1849 | begin 1850 | return fmt(sfmt, f(value)) ; 1851 | end function ; 1852 | 1853 | function f(sfmt : string ; value : class_t) return string is 1854 | begin 1855 | return fmt(sfmt, f(value)) ; 1856 | end function ; 1857 | 1858 | function f(sfmt : string ; value : integer) return string is 1859 | begin 1860 | return fmt(sfmt, f(value)) ; 1861 | end function ; 1862 | 1863 | function f(sfmt : string ; value : real) return string is 1864 | begin 1865 | return fmt(sfmt, f(value)) ; 1866 | end function ; 1867 | 1868 | function f(sfmt : string ; value : time) return string is 1869 | begin 1870 | return fmt(sfmt, f(value)) ; 1871 | end function ; 1872 | 1873 | end package body ; 1874 | 1875 | -------------------------------------------------------------------------------- /fmt_examples.vhd: -------------------------------------------------------------------------------- 1 | use std.textio.write ; 2 | use std.textio.output ; 3 | 4 | use work.fmt.f ; 5 | use work.fmt.p ; 6 | use work.fmt.fbit ; 7 | use work.fmt.fbv ; 8 | use work.fmt.fchar ; 9 | use work.fmt.fmt ; 10 | use work.fmt.fstr ; 11 | 12 | use work.colors.ansi ; 13 | 14 | library ieee ; 15 | use ieee.math_real.MATH_PI ; 16 | use ieee.fixed_pkg.sfixed ; 17 | use ieee.fixed_pkg.to_sfixed ; 18 | 19 | entity fmt_examples is end entity ; 20 | 21 | architecture arch of fmt_examples is 22 | 23 | begin 24 | 25 | process 26 | variable sfval : sfixed(5 downto -10) := to_sfixed(MATH_PI, 5, -10) ; 27 | begin 28 | -- Easy string substitution 29 | p(fmt("{} {}", "hello", "world")) ; 30 | 31 | -- Argument renumbering, if you're into that 32 | p(fmt("{1} {0}", "world", "hello")) ; 33 | 34 | -- Types supported 35 | p(f ("bit : '{}'", bit'('1'))) ; 36 | p(f ("bit_vector : '{}'", bit_vector'("110010001110"))) ; 37 | p(f ("boolean : '{}'", false)) ; 38 | p(f ("character : '{}'", character'('c'))) ; 39 | p(f ("real : '{}'", MATH_PI)) ; 40 | p(fmt("string : '{}'", "the quick brown fox jumps over the lazy dog")) ; 41 | p(f (string'("time : '{}'"), 1.23 ns)) ; 42 | 43 | -- Inserting braces into your string 44 | p(fmt("braces : '{{{}}}'", "included")) ; 45 | 46 | -- Formatting supported 47 | p(fmt("justified : '{}'", fstr("left", "<20s"))) ; 48 | p(fmt("justified : '{}'", fstr("centered", "^20s"))) ; 49 | p(fmt("justified : '{}'", fstr("right", ">20s"))) ; 50 | p(fmt("filled : '{}'", fstr("left", "~<20s"))) ; 51 | p(fmt("filled : '{}'", fstr("centered", "~^20s"))) ; 52 | p(fmt("filled : '{}'", fstr("right", "~>20s"))) ; 53 | 54 | -- Time resolutions 55 | -- When the precision is set to a number, it's the exponent for the unit, so ... 56 | -- 15 10^-15 fs 57 | -- 12 10^-12 ps 58 | -- 9 10^-9 ns 59 | -- 6 10^-6 us 60 | -- 3 10^-3 ms 61 | -- 0 10^0 seconds 62 | p(fmt("1 us in fs : '{}'", f(1 us, "0.15f"))) ; 63 | p(fmt("1 us in ps : '{}'", f(1 us, "0.12f"))) ; 64 | p(fmt("1 us in ns : '{}'", f(1 us, "0.9f"))) ; 65 | p(fmt("1 us in us : '{}'", f(1 us, "0.6f"))) ; 66 | p(fmt("1 us in ms : '{}'", f(1 us, "0.3f"))) ; 67 | p(fmt("1 us in seconds : '{}'", f(1 us, "0.0f"))) ; 68 | 69 | -- Real Numbers 70 | p(fmt("real exp : '{}'", f(MATH_PI, "e"))) ; 71 | p(fmt("real fixed : '{}'", f(MATH_PI, "f"))) ; 72 | p(fmt("real exp sign : '{}'", f(MATH_PI, "+0.6e"))) ; 73 | p(fmt("real fixed sign : '{}'", f(MATH_PI, "+0.6f"))) ; 74 | p(fmt("real width exp < : '{}'", f(MATH_PI, "<+20.6e"))) ; 75 | p(fmt("real width exp ^ : '{}'", f(MATH_PI, "^+20.6e"))) ; 76 | p(fmt("real width exp > : '{}'", f(MATH_PI, ">+20.6e"))) ; 77 | p(fmt("real width exp = : '{}'", f(MATH_PI, "=+20.6e"))) ; 78 | p(fmt("real width fixed < : '{}'", f(-MATH_PI, "<+20.6f"))) ; 79 | p(fmt("real width fixed ^ : '{}'", f(-MATH_PI, "^+20.6f"))) ; 80 | p(fmt("real width fixed > : '{}'", f(-MATH_PI, ">+20.6f"))) ; 81 | p(fmt("real width fixed = : '{}'", f(-MATH_PI, "=+20.6f"))) ; 82 | p(fmt("real wide > filled : '{}'", f(MATH_PI, "#>40.30f"))) ; 83 | 84 | -- Integer Number base conversion 85 | p(fmt("integer : '{}'", f(13, "8d"))) ; 86 | p(fmt("integer zp : '{}'", f(13, "08d"))) ; 87 | 88 | p(fmt("integer/binary : '{}'", f(13, "8b"))) ; 89 | p(fmt("integer/binary zp : '{}'", f(13, "08b"))) ; 90 | 91 | p(fmt("integer/octal : '{}'", f(13, "8o"))) ; 92 | p(fmt("integer/octal zp : '{}'", f(13, "08o"))) ; 93 | 94 | p(fmt("integer/hex : '{}'", f(13, "8x"))) ; 95 | p(fmt("integer/hex zp : '{}'", f(13, "08x"))) ; 96 | 97 | -- Bit Vector base conversion 98 | p(fmt("bv to int : '{}'", fbv("10010001", "4d"))) ; 99 | p(fmt("bv to uint : '{}'", fbv("10010001", "4u"))) ; 100 | p(fmt("bv to binary : '{}'", fbv("10010001", ">16b"))) ; 101 | p(fmt("bv to binary zp : '{}'", fbv("10010001", ">016b"))) ; 102 | p(fmt("bv to octal : '{}'", fbv("10010001", ">4o"))) ; 103 | p(fmt("bv to octal zp : '{}'", fbv("10010001", ">04o"))) ; 104 | p(fmt("bv to hex : '{}'", fbv("10010001", ">4x"))) ; 105 | p(fmt("bv to hex zp : '{}'", fbv("10010001", ">04x"))) ; 106 | 107 | -- Boolean conversions 108 | p(fmt("boolean to binary : '{}'", f(true, ">04b"))) ; 109 | 110 | -- Strings 111 | p(fmt("limited to 4 : '{}'", fstr("limited", "0.4s"))) ; 112 | p(fmt("limited to 20 : '{}'", fstr("limited", ">10.20s"))) ; 113 | p(fmt("fill keep spaces < : '{}'" & lF, fstr(" spaces ", "~<20s"))) ; 114 | p(fmt("fill keep spaces > : '{}'" & lF, fstr(" spaces ", "~>20s"))) ; 115 | p(fmt("fill keep spaces ^ : '{}'" & lF, fstr(" spaces ", "~^20s"))) ; 116 | 117 | -- Inline reformatting 118 | p(fmt("reformatted string : {{{:>8s}}}", "value")) ; 119 | p(fmt("log message : '{:>10s}@{:>20.9t}: {:~40s}!'", fstr("uut"), f(10 us), fstr("A silly message to log"))) ; 120 | 121 | -- Ambiguous/Problematic strings 122 | p(fmt("integer/binary amb : '{:>16b}'", f(1100))) ; 123 | p(fmt("integer/binary amb : '{}'", f(1100, ">16b"))) ; 124 | p(fmt("integer/octal amb : '{:>16o}'", f(1100))) ; 125 | p(fmt("integer/octal amb : '{}'", f(1100, ">16o"))) ; 126 | p(fmt("integer/hex amb : '{:>16x}'", f(1100))) ; 127 | p(fmt("integer/hex amb : '{}'", f(1100, ">16x"))) ; 128 | 129 | -- ANSI Foreground Colors 130 | p(fmt("colors : '{}{:~^10s}{}'", ansi.BLACK, "BLACK", ansi.RESET)) ; 131 | p(fmt("colors : '{}{:~^10s}{}'", ansi.RED, "RED", ansi.RESET)) ; 132 | p(fmt("colors : '{}{:~^10s}{}'", ansi.GREEN, "GREEN", ansi.RESET)) ; 133 | p(fmt("colors : '{}{:~^10s}{}'", ansi.YELLOW, "YELLOW", ansi.RESET)) ; 134 | p(fmt("colors : '{}{:~^10s}{}'", ansi.BLUE, "BLUE", ansi.RESET)) ; 135 | p(fmt("colors : '{}{:~^10s}{}'", ansi.PURPLE, "PURPLE", ansi.RESET)) ; 136 | p(fmt("colors : '{}{:~^10s}{}'", ansi.CYAN, "CYAN", ansi.RESET)) ; 137 | p(fmt("colors : '{}{:~^10s}{}'", ansi.WHITE, "WHITE", ansi.RESET)) ; 138 | 139 | -- ANSI Bold Colors 140 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.BLACK, "BLACK", ansi.RESET)) ; 141 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.RED, "RED", ansi.RESET)) ; 142 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.GREEN, "GREEN", ansi.RESET)) ; 143 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.YELLOW, "YELLOW", ansi.RESET)) ; 144 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.BLUE, "BLUE", ansi.RESET)) ; 145 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.PURPLE, "PURPLE", ansi.RESET)) ; 146 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.CYAN, "CYAN", ansi.RESET)) ; 147 | p(fmt("colors : '{}{:~^10s}{}'", ansi.bold.WHITE, "WHITE", ansi.RESET)) ; 148 | 149 | -- ANSI Underline Colors 150 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.BLACK, "BLACK", ansi.RESET)) ; 151 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.RED, "RED", ansi.RESET)) ; 152 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.GREEN, "GREEN", ansi.RESET)) ; 153 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.YELLOW, "YELLOW", ansi.RESET)) ; 154 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.BLUE, "BLUE", ansi.RESET)) ; 155 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.PURPLE, "PURPLE", ansi.RESET)) ; 156 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.CYAN, "CYAN", ansi.RESET)) ; 157 | p(fmt("colors : '{}{:~^10s}{}'", ansi.underline.WHITE, "WHITE", ansi.RESET)) ; 158 | 159 | -- ANSI Background Colors 160 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.BLACK, "BLACK", ansi.RESET)) ; 161 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.RED, "RED", ansi.RESET)) ; 162 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.GREEN, "GREEN", ansi.RESET)) ; 163 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.YELLOW, "YELLOW", ansi.RESET)) ; 164 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.BLUE, "BLUE", ansi.RESET)) ; 165 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.PURPLE, "PURPLE", ansi.RESET)) ; 166 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.CYAN, "CYAN", ansi.RESET)) ; 167 | p(fmt("colors : '{}{:~^10s}{}'", ansi.background.WHITE, "WHITE", ansi.RESET)) ; 168 | 169 | -- ANSI Intense Colors 170 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.BLACK, "BLACK", ansi.RESET)) ; 171 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.RED, "RED", ansi.RESET)) ; 172 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.GREEN, "GREEN", ansi.RESET)) ; 173 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.YELLOW, "YELLOW", ansi.RESET)) ; 174 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.BLUE, "BLUE", ansi.RESET)) ; 175 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.PURPLE, "PURPLE", ansi.RESET)) ; 176 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.CYAN, "CYAN", ansi.RESET)) ; 177 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intense.WHITE, "WHITE", ansi.RESET)) ; 178 | 179 | -- ANSI Bold Intense Colors 180 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.BLACK, "BLACK", ansi.RESET)) ; 181 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.RED, "RED", ansi.RESET)) ; 182 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.GREEN, "GREEN", ansi.RESET)) ; 183 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.YELLOW, "YELLOW", ansi.RESET)) ; 184 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.BLUE, "BLUE", ansi.RESET)) ; 185 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.PURPLE, "PURPLE", ansi.RESET)) ; 186 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.CYAN, "CYAN", ansi.RESET)) ; 187 | p(fmt("colors : '{}{:~^10s}{}'", ansi.boldintense.WHITE, "WHITE", ansi.RESET)) ; 188 | 189 | -- ANSI Intense Background Colors 190 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.BLACK, "BLACK", ansi.RESET)) ; 191 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.RED, "RED", ansi.RESET)) ; 192 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.GREEN, "GREEN", ansi.RESET)) ; 193 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.YELLOW, "YELLOW", ansi.RESET)) ; 194 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.BLUE, "BLUE", ansi.RESET)) ; 195 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.PURPLE, "PURPLE", ansi.RESET)) ; 196 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.CYAN, "CYAN", ansi.RESET)) ; 197 | p(fmt("colors : '{}{:~^10s}{}'", ansi.intensebg.WHITE, "WHITE", ansi.RESET)) ; 198 | 199 | -- More colors 200 | p(fmt("colors : '{}{:~^20s}{}'", ansi.underline.BLACK & ansi.background.white, "BLACK ON WHITE", ansi.RESET)) ; 201 | 202 | -- sfixed 203 | p(fmt("sfixed real : '{}'", f(sfval))) ; 204 | p(fmt("sfixed binary : '{}'", f(sfval, "b"))) ; 205 | p(fmt("sfixed octal : '{}'", f(sfval, "o"))) ; 206 | p(fmt("sfixed hex : '{}'", f(sfval, "x"))) ; 207 | p(fmt("sfixed int : '{}'", f(sfval, "d"))) ; 208 | 209 | std.env.stop ; 210 | end process ; 211 | 212 | end architecture ; 213 | 214 | -------------------------------------------------------------------------------- /fmt_test.vhd: -------------------------------------------------------------------------------- 1 | use std.textio.all ; 2 | 3 | library ieee ; 4 | use ieee.std_logic_1164.all ; 5 | 6 | library work ; 7 | use work.fmt.all ; 8 | use work.string_list.all ; 9 | 10 | entity fmt_test is 11 | generic ( 12 | FILEPATH : string := "./vectors/format_test_vectors.txt" 13 | ) ; 14 | end entity ; 15 | 16 | architecture arch of fmt_test is 17 | 18 | procedure split(variable lines : inout string_list ; variable l : inout line) is 19 | variable start : positive := 1 ; 20 | variable stop : positive := 1 ; 21 | type fsm_t is (WHITESPACE, INQUOTE, INWORD) ; 22 | variable fsm : fsm_t := WHITESPACE ; 23 | variable len : natural ; 24 | begin 25 | length(lines, len) ; 26 | clear(lines) ; 27 | while stop <= l'length loop 28 | case fsm is 29 | when WHITESPACE => 30 | case l(stop) is 31 | when ' '|HT => 32 | start := start + 1 ; 33 | stop := start ; 34 | when '"' => 35 | start := start + 1 ; 36 | stop := start ; 37 | fsm := INQUOTE ; 38 | when others => 39 | stop := start ; 40 | fsm := INWORD ; 41 | end case ; 42 | 43 | when INQUOTE => 44 | case l(stop) is 45 | when '"' => 46 | append(lines, l(start to stop-1)) ; 47 | start := stop + 1 ; 48 | stop := start ; 49 | fsm := WHITESPACE ; 50 | when others => 51 | stop := stop + 1 ; 52 | end case ; 53 | 54 | when INWORD => 55 | case l(stop) is 56 | when ' '|HT => 57 | append(lines, l(start to stop-1)) ; 58 | start := stop ; 59 | fsm := WHITESPACE ; 60 | when others => 61 | stop := stop + 1 ; 62 | end case ; 63 | end case ; 64 | end loop ; 65 | end procedure ; 66 | 67 | -- Define the custom type 68 | type state_t is (IDLE, CHECKING, FOO, BAR) ; 69 | signal state : state_t := CHECKING ; 70 | 71 | function to_string(x : state_t) return string is 72 | begin 73 | return state_t'image(x) ; 74 | end function ; 75 | 76 | -- VHDL-2008 required with generic subprograms 77 | --function f is new f generic map (t => state_t) ; 78 | 79 | begin 80 | 81 | test : process 82 | type lines_t is array(positive range <>) of line ; 83 | type bit_vector_ptr is access bit_vector ; 84 | file fin : text ; 85 | variable fstatus : file_open_status ; 86 | variable l : line ; 87 | variable ll : line ; 88 | variable bit_arg : bit ; 89 | variable bv_ptr : bit_vector_ptr ; 90 | variable bool_arg : boolean ; 91 | variable char_arg : character ; 92 | variable int_arg : integer ; 93 | variable real_arg : real ; 94 | variable time_arg : time ; 95 | variable lines : string_list ; 96 | variable len : natural ; 97 | variable num_args : integer ; 98 | variable lineno : natural := 0 ; 99 | variable good : boolean ; 100 | 101 | variable tests : natural := 0 ; 102 | variable failed : natural := 0 ; 103 | 104 | variable cmd : line ; 105 | variable sfmt : line ; 106 | variable result : line ; 107 | variable gold : line ; 108 | variable args : lines_t(1 to 16) := (others => null) ; 109 | variable args_list : string_list ; 110 | begin 111 | -- Open the test file 112 | file_open(fin, FILEPATH, READ_MODE) ; 113 | 114 | -- Read the line and parse 115 | while not endfile(fin) loop 116 | -- Read the line and keep track of where we are in the file 117 | readline(fin, l) ; 118 | lineno := lineno + 1 ; 119 | 120 | -- Check if the current line is commented using a # 121 | if l(1) = '#' then 122 | -- Skip 123 | next ; 124 | end if ; 125 | 126 | -- Split out whitespace and quoted strings 127 | split(lines, l) ; 128 | 129 | -- Let the total number of lines we split 130 | length(lines, len) ; 131 | 132 | -- Calculate arguments given the number of lines we split 133 | num_args := len - 1 - 1 - 1 ; 134 | if num_args < 0 then 135 | report fmt("Invalid test at line {}: {}", f(lineno), l.all) 136 | severity warning ; 137 | next ; 138 | end if ; 139 | 140 | -- Populate Command 141 | get(lines, 0, cmd) ; 142 | 143 | -- Populate Format String 144 | get(lines, 1, sfmt) ; 145 | 146 | -- Clear any old arguments 147 | args := (others => null) ; 148 | 149 | -- Populate the arguments 150 | for idx in 2 to 1+num_args loop 151 | -- Ensure we don't run over 152 | if idx-1 > args'length then 153 | exit ; 154 | end if ; 155 | get(lines, idx, args(idx-1)) ; 156 | end loop ; 157 | 158 | -- Last line is always gold 159 | get(lines, len-1, gold) ; 160 | 161 | -- Process the different commands 162 | ------------------------------------------------------------------- 163 | -- fb 164 | ------------------------------------------------------------------- 165 | if cmd.all = "fb" then 166 | read(args(1), bit_arg, good) ; 167 | if good = false then 168 | report fmt("Invalid bit argument: {}", args(1).all) 169 | severity warning ; 170 | end if ; 171 | if sfmt'length > 0 then 172 | result := new string'(f(sfmt.all, bit_arg)) ; 173 | else 174 | report "fb command requires a format string, none given" 175 | severity warning ; 176 | result := new string'("") ; 177 | end if ; 178 | 179 | ------------------------------------------------------------------- 180 | -- fbv 181 | ------------------------------------------------------------------- 182 | elsif cmd.all = "fbv" then 183 | bv_ptr := new bit_vector(0 to args(1)'length-1) ; 184 | bread(args(1), bv_ptr.all, good) ; 185 | if good = false then 186 | report fmt("Invalid bit_vector argument: {}", args(1).all) 187 | severity warning ; 188 | end if ; 189 | if sfmt'length > 0 then 190 | result := new string'(f(sfmt.all, bv_ptr.all)) ; 191 | else 192 | report "fbv command requires a format string, none given" 193 | severity warning ; 194 | result := new string'("") ; 195 | end if ; 196 | 197 | ------------------------------------------------------------------- 198 | -- fbit 199 | ------------------------------------------------------------------- 200 | elsif cmd.all = "fbit" then 201 | read(args(1), bit_arg, good) ; 202 | if good = false then 203 | report fmt("Invalid bit argument: {}", args(1).all) 204 | severity warning ; 205 | end if ; 206 | if sfmt'length > 0 then 207 | result := new string'(f(bit_arg, sfmt.all)) ; 208 | else 209 | result := new string'(f(bit_arg)) ; 210 | end if ; 211 | 212 | ------------------------------------------------------------------- 213 | -- fbitvector 214 | ------------------------------------------------------------------- 215 | elsif cmd.all = "fbitvector" then 216 | bv_ptr := new bit_vector(0 to l'length-1) ; 217 | read(args(1), bv_ptr.all, good) ; 218 | if good = false then 219 | report fmt("Invalid bit_vector argument: {}", args(1).all) 220 | severity warning ; 221 | end if ; 222 | if sfmt'length > 0 then 223 | result := new string'(f(bv_ptr.all, sfmt.all)) ; 224 | else 225 | result := new string'(f(bv_ptr.all)) ; 226 | end if ; 227 | 228 | ------------------------------------------------------------------- 229 | -- fbool 230 | ------------------------------------------------------------------- 231 | elsif cmd.all = "fbool" then 232 | read(args(1), bool_arg, good) ; 233 | if good = false then 234 | report fmt("Invalid bool argument: {}", args(1).all) 235 | severity warning ; 236 | end if ; 237 | if sfmt'length > 0 then 238 | result := new string'(f(sfmt.all, bool_arg)) ; 239 | else 240 | report "fbool command requires a format string, none given" 241 | severity warning ; 242 | result := new string'("") ; 243 | end if ; 244 | 245 | ------------------------------------------------------------------- 246 | -- fboolean 247 | ------------------------------------------------------------------- 248 | elsif cmd.all = "fboolean" then 249 | read(args(1), bool_arg, good) ; 250 | if good = false then 251 | report fmt("Invalid bool argument: {}", args(1).all) 252 | severity warning ; 253 | end if ; 254 | if sfmt'length > 0 then 255 | result := new string'(f(bool_arg, sfmt.all)) ; 256 | else 257 | result := new string'(f(bool_arg)) ; 258 | end if ; 259 | 260 | ------------------------------------------------------------------- 261 | -- fchar 262 | ------------------------------------------------------------------- 263 | elsif cmd.all = "fchar" then 264 | read(args(1), char_arg, good) ; 265 | if good = false then 266 | report fmt("Invalid character argument: {}", args(1).all) 267 | severity warning ; 268 | end if ; 269 | if sfmt'length > 0 then 270 | result := new string'(f(char_arg, sfmt.all)) ; 271 | else 272 | result := new string'(f(char_arg)) ; 273 | end if ; 274 | 275 | ------------------------------------------------------------------- 276 | -- fi 277 | ------------------------------------------------------------------- 278 | elsif cmd.all = "fi" then 279 | read(args(1), int_arg, good) ; 280 | if good = false then 281 | report fmt("Invalid integer argument: {}", args(1).all) 282 | severity warning ; 283 | end if ; 284 | if sfmt'length > 0 then 285 | result := new string'(f(sfmt.all, int_arg)) ; 286 | else 287 | report "fi command requires a format string, none given" 288 | severity warning ; 289 | result := new string'("") ; 290 | end if ; 291 | 292 | ------------------------------------------------------------------- 293 | -- fr 294 | ------------------------------------------------------------------- 295 | elsif cmd.all = "fr" then 296 | read(args(1), real_arg, good) ; 297 | if good = false then 298 | report fmt("Invalid real argument: {}", args(1).all) 299 | severity warning ; 300 | end if ; 301 | if sfmt'length > 0 then 302 | result := new string'(f(sfmt.all, real_arg)) ; 303 | else 304 | report "fr command requires a format string, none given" 305 | severity warning ; 306 | result := new string'("") ; 307 | end if ; 308 | 309 | ------------------------------------------------------------------- 310 | -- ft 311 | ------------------------------------------------------------------- 312 | elsif cmd.all = "ft" then 313 | read(args(1), time_arg, good) ; 314 | if good = false then 315 | report fmt("Invalid time argument: {}", args(1).all) 316 | severity warning ; 317 | end if ; 318 | if sfmt'length > 0 then 319 | result := new string'(f(sfmt.all, time_arg)) ; 320 | else 321 | report "ft command requires a format string, none given" 322 | severity warning ; 323 | result := new string'("") ; 324 | end if ; 325 | 326 | ------------------------------------------------------------------- 327 | -- fint 328 | ------------------------------------------------------------------- 329 | elsif cmd.all = "fint" then 330 | read(args(1), int_arg, good) ; 331 | if good = false then 332 | report fmt("Invalid integer argument: {}", args(1).all) 333 | severity warning ; 334 | end if ; 335 | if sfmt'length > 0 then 336 | result := new string'(f(int_arg, sfmt.all)) ; 337 | else 338 | result := new string'(f(int_arg)) ; 339 | end if ; 340 | 341 | ------------------------------------------------------------------- 342 | -- fmt 343 | ------------------------------------------------------------------- 344 | elsif cmd.all = "fmt" then 345 | case num_args is 346 | when 0 => 347 | result := new string'(fmt(sfmt.all)) ; 348 | when 1 => 349 | result := new string'(fmt(sfmt.all, args(1).all)) ; 350 | when 2 => 351 | result := new string'(fmt(sfmt.all, 352 | args(1).all, 353 | args(2).all)) ; 354 | when 3 => 355 | result := new string'(fmt(sfmt.all, 356 | args(1).all, 357 | args(2).all, 358 | args(3).all)) ; 359 | when 4 => 360 | result := new string'(fmt(sfmt.all, 361 | args(1).all, 362 | args(2).all, 363 | args(3).all, 364 | args(4).all)) ; 365 | when 5 => 366 | result := new string'(fmt(sfmt.all, 367 | args(1).all, 368 | args(2).all, 369 | args(3).all, 370 | args(4).all, 371 | args(5).all)) ; 372 | when 6 => 373 | result := new string'(fmt(sfmt.all, 374 | args(1).all, 375 | args(2).all, 376 | args(3).all, 377 | args(4).all, 378 | args(5).all, 379 | args(6).all)) ; 380 | when 7 => 381 | result := new string'(fmt(sfmt.all, 382 | args(1).all, 383 | args(2).all, 384 | args(3).all, 385 | args(4).all, 386 | args(5).all, 387 | args(6).all, 388 | args(7).all)) ; 389 | when 8 => 390 | result := new string'(fmt(sfmt.all, 391 | args(1).all, 392 | args(2).all, 393 | args(3).all, 394 | args(4).all, 395 | args(5).all, 396 | args(6).all, 397 | args(7).all, 398 | args(8).all)) ; 399 | when 9 => 400 | result := new string'(fmt(sfmt.all, 401 | args(1).all, 402 | args(2).all, 403 | args(3).all, 404 | args(4).all, 405 | args(5).all, 406 | args(6).all, 407 | args(7).all, 408 | args(8).all, 409 | args(9).all)) ; 410 | when 10 => 411 | result := new string'(fmt(sfmt.all, 412 | args(1).all, 413 | args(2).all, 414 | args(3).all, 415 | args(4).all, 416 | args(5).all, 417 | args(6).all, 418 | args(7).all, 419 | args(8).all, 420 | args(9).all, 421 | args(10).all)) ; 422 | when 11 => 423 | result := new string'(fmt(sfmt.all, 424 | args(1).all, 425 | args(2).all, 426 | args(3).all, 427 | args(4).all, 428 | args(5).all, 429 | args(6).all, 430 | args(7).all, 431 | args(8).all, 432 | args(9).all, 433 | args(10).all, 434 | args(11).all)) ; 435 | when 12 => 436 | result := new string'(fmt(sfmt.all, 437 | args(1).all, 438 | args(2).all, 439 | args(3).all, 440 | args(4).all, 441 | args(5).all, 442 | args(6).all, 443 | args(7).all, 444 | args(8).all, 445 | args(9).all, 446 | args(10).all, 447 | args(11).all, 448 | args(12).all)) ; 449 | when 13 => 450 | result := new string'(fmt(sfmt.all, 451 | args(1).all, 452 | args(2).all, 453 | args(3).all, 454 | args(4).all, 455 | args(5).all, 456 | args(6).all, 457 | args(7).all, 458 | args(8).all, 459 | args(9).all, 460 | args(10).all, 461 | args(11).all, 462 | args(12).all, 463 | args(13).all)) ; 464 | when 14 => 465 | result := new string'(fmt(sfmt.all, 466 | args(1).all, 467 | args(2).all, 468 | args(3).all, 469 | args(4).all, 470 | args(5).all, 471 | args(6).all, 472 | args(7).all, 473 | args(8).all, 474 | args(9).all, 475 | args(10).all, 476 | args(11).all, 477 | args(12).all, 478 | args(13).all, 479 | args(14).all)) ; 480 | when 15 => 481 | result := new string'(fmt(sfmt.all, 482 | args(1).all, 483 | args(2).all, 484 | args(3).all, 485 | args(4).all, 486 | args(5).all, 487 | args(6).all, 488 | args(7).all, 489 | args(8).all, 490 | args(9).all, 491 | args(10).all, 492 | args(11).all, 493 | args(12).all, 494 | args(13).all, 495 | args(14).all, 496 | args(15).all)) ; 497 | when others => 498 | if num_args > 16 then 499 | report fmt("Too many arguments on line {}: {} > 16, trimming to 16", f(lineno), f(num_args)) 500 | severity warning ; 501 | end if ; 502 | result := new string'(fmt(sfmt.all, 503 | args(1).all, 504 | args(2).all, 505 | args(3).all, 506 | args(4).all, 507 | args(5).all, 508 | args(6).all, 509 | args(7).all, 510 | args(8).all, 511 | args(9).all, 512 | args(10).all, 513 | args(11).all, 514 | args(12).all, 515 | args(13).all, 516 | args(14).all, 517 | args(15).all, 518 | args(16).all)) ; 519 | end case ; 520 | 521 | ------------------------------------------------------------------- 522 | -- fproc 523 | ------------------------------------------------------------------- 524 | elsif cmd.all = "fproc" then 525 | -- Clear the args_list 526 | clear(args_list) ; 527 | 528 | -- Add the args to the args_list 529 | for idx in 2 to len-1-1 loop 530 | get(lines, idx, l) ; 531 | append(args_list, l.all) ; 532 | end loop ; 533 | 534 | -- Format 535 | f(sfmt.all, args_list, result) ; 536 | 537 | ------------------------------------------------------------------- 538 | -- freal 539 | ------------------------------------------------------------------- 540 | elsif cmd.all = "freal" then 541 | read(args(1), real_arg, good) ; 542 | if good = false then 543 | report fmt("Invalid real argument: {}", args(1).all) 544 | severity warning ; 545 | end if ; 546 | if sfmt'length > 0 then 547 | result := new string'(f(real_arg, sfmt.all)) ; 548 | else 549 | result := new string'(f(real_arg)) ; 550 | end if ; 551 | 552 | ------------------------------------------------------------------- 553 | -- fstr 554 | ------------------------------------------------------------------- 555 | elsif cmd.all = "fstr" then 556 | if sfmt'length > 0 then 557 | result := new string'(fstr(args(1).all, sfmt.all)) ; 558 | else 559 | result := new string'(fstr(args(1).all)) ; 560 | end if ; 561 | 562 | ------------------------------------------------------------------- 563 | -- ftime 564 | ------------------------------------------------------------------- 565 | elsif cmd.all = "ftime" then 566 | read(args(1), time_arg, good) ; 567 | if good = false then 568 | report fmt("Invalid time argument: {}", args(1).all) 569 | severity warning ; 570 | end if ; 571 | if sfmt'length > 0 then 572 | result := new string'(f(time_arg, sfmt.all)) ; 573 | else 574 | result := new string'(f(time_arg)) ; 575 | end if ; 576 | 577 | ------------------------------------------------------------------- 578 | -- Unknown command 579 | ------------------------------------------------------------------- 580 | else 581 | report fmt("Unknown command on line {}: {}", f(lineno), cmd.all) ; 582 | end if ; 583 | 584 | -- Increment test count 585 | tests := tests + 1 ; 586 | 587 | -- Check if result is null which is an error 588 | if result = null then 589 | failed := failed + 1 ; 590 | report "Result is null, skipping comparison" 591 | severity warning ; 592 | next ; 593 | end if ; 594 | 595 | -- Check if gold is null which is an error 596 | if gold = null then 597 | failed := failed + 1 ; 598 | report "Gold is null, skipping comparison" 599 | severity warning ; 600 | next ; 601 | end if ; 602 | 603 | -- Perform the actual comparison 604 | if result.all /= gold.all then 605 | failed := failed + 1 ; 606 | report f("Failure line {}: '{}' /= '{}'", f(lineno), fstr(result.all), fstr(gold.all)) 607 | severity warning ; 608 | end if ; 609 | end loop ; 610 | 611 | -- Final report 612 | write(output, fmt("Tests: {:>8d} Passed: {:>8d} Failed: {:>8d}" & LF, f(tests), f(tests-failed), f(failed))) ; 613 | 614 | -- Close the test file 615 | file_close(fin) ; 616 | 617 | -- VHDL-2008 required with generic subprograms 618 | --report fmt("state: {}", f(state, "->20s")) ; 619 | -- Alternative that doesn't require generic subprograms 620 | --report fmt("state: {}", fstr(state_t'image(state), "->20s")) ; 621 | 622 | -- Done 623 | std.env.stop ; 624 | end process ; 625 | 626 | end architecture ; 627 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | ghdl -a --std=08 fmt.vhd fmt_test.vhd fmt_examples.vhd 6 | 7 | ghdl -e --std=08 fmt_test 8 | ghdl -e --std=08 fmt_examples 9 | 10 | ghdl -r --std=08 fmt_test 11 | ghdl -r --std=08 fmt_examples 12 | -------------------------------------------------------------------------------- /vectors/format_test_vectors.txt: -------------------------------------------------------------------------------- 1 | # Function fmt arg1 arg2 ... arg20 gold 2 | fb "{}" "0" "0" 3 | fb "{}" "1" "1" 4 | fb "{:>20b}" "0" " 0" 5 | fb "{:~^5b}" "1" "~~1~~" 6 | fbool "{:>8s}" "TRUE" " true" 7 | fboolean "8s" "false" "false " 8 | fboolean "8b" "FALSE" "0 " 9 | fbv "{:~>5b}" "101" "~~101" 10 | fbv "{:>5o}" "1101" " 15" 11 | fbv "{:5x}" "101" "5 " 12 | fi "{:8d}" "200" "200 " 13 | fi "int: {:d}" "10" "int: 10" 14 | fr "{:>10.3f}" "3.14159" " 3.142" 15 | ft "{:.9t}" "1 us" "1000 ns" 16 | fint "~^20d" "200" "~~~~~~~~200~~~~~~~~~" 17 | fint "" "2000" "2000" 18 | fmt "{} - {}" "one" "two" "one - two" 19 | fmt "naked string" "naked string" 20 | fmt "{1} - {0}" "3.14159" "200" "200 - 3.14159" 21 | fmt "{{ }} {}" "value" "{ } value" 22 | fmt "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 16 16 16 16" 23 | fmt "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16" 24 | fmt "{1:>8d} {0:~^9s}" "two" "200" " 200 ~~~two~~~" 25 | fmt "{:>8d} {:~^9s}""200" "two" " 200 ~~~two~~~" 26 | fproc "{1} - {0}" "one" "two" "two - one" 27 | fproc "naked string" "naked string" 28 | # Testing an arbitrary length of strings appended to each other 29 | fproc "{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}" "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20" 30 | freal "~=+18.8f" "3.14159" "+~~~~~~~3.14159000" 31 | freal ">8.15f" "3.14159" "3.141590000000000" 32 | freal "" "3.14159" "3.141590" 33 | freal "+f" "3.14159" "+3.141590" 34 | freal "=+f" "3.14159" "+3.141590" 35 | freal "=+.0f" "3.14159" "+3" 36 | freal "=+10.0f" "3.14159" "+ 3" 37 | freal "=+10.3f" "3.14159" "+ 3.142" 38 | freal "=+10.3f" "123456.123" "+123456.123" 39 | freal "=+18f" "3.14159" "+ 3.141590" 40 | freal "e" "3.14159" "3.141590e+00" 41 | freal ".3e" "3.14159" "3.142e+00" 42 | freal ".10e" "3.14159" "3.1415900000e+00" 43 | freal ".0e" "3.14159" "3e+00" 44 | freal ">10.3f" "3.14159" " 3.142" 45 | fstr "<20s" "yadda" "yadda " 46 | fstr ">20s" "yadda" " yadda" 47 | fstr "" "naked string" "naked string" 48 | fstr "-^20s" "/\/\/\/\" "------/\/\/\/\------" 49 | fstr ".4s" "12345678" "1234" 50 | ftime "12.9t" "1 us" "1000 ns " 51 | ftime "" "1 us" "1000 ns" 52 | ftime ".9t" "1 us" "1000 ns" 53 | --------------------------------------------------------------------------------