Skip to content

Updates and improvements to SDF parser. - #51

Open
brabect1 wants to merge 6 commits into
chipsalliance:masterfrom
brabect1:master
Open

Updates and improvements to SDF parser.#51
brabect1 wants to merge 6 commits into
chipsalliance:masterfrom
brabect1:master

Conversation

@brabect1

Copy link
Copy Markdown

This pull requests makes quite a few changes to the original code. The list of major updates:

  • Removed parser internal helper collections. The updated parser now returns the parser structure as a return value of grammar rule expansions. This change simplifies the code and would enable future refactoring to the visitor pattern (for greater flexibility of the parser).
  • Changed internal structure of parsed data to better represent input data 1:1. The new structure keeps any duplicities if they existed in the input SDF and it preservers order of SDF timing records. These changes are to better correlate emitted parsed SDF to its original input.
  • Added support for more SDF constructs (e.g. RECREM, PERIOD, RETAIN), incl. SDF comments. The comments are handled by the lexer as an ignored token and hence does not presently propagate to the parser.
  • Fixed semantics of delval_list to correspond to SDF 3.0 Std.
  • Added unit tests of individual SDF syntax elements. The unit test is however far from complete.
  • Added unit tests of complete SDF parsing and writing out the parsed data back to SDF. This is much like the original parse_all_test.py except that test_parse_and_write.py operates on SDF strings and not files.
  • Added few simple utilities, e.g. to convert SDF to JSON or to reformat SDF.

This update breaks some compatibility with the original code. This makes the parse_all_test.py test suite fails as some
of the golden files no longer correspond to how the parsed data looks like now, when emitted into a file. To make the test
pass again, new golden file set is to be generated and reviewed for expected contents. This is something I ceased to
do as my expectations of the output data may be different than that of the original authors.

@kgugala
kgugala self-requested a review November 21, 2022 14:00
@kgugala

kgugala commented Nov 21, 2022

Copy link
Copy Markdown
Member

@brabect1 thanks for the PR!

I've just merged #52, fixing the CI. Can you rebase on top of that so we can run the tests here?

).

This commit squashes all the changes made in PR chipsalliance#51. The summary of important
changes made by PR chipsalliance#51 is as follows:

* Removed parser internal helper collections. The updated parser now returns the parser
  structure as a return value of grammar rule expansions. This change simplifies the code
  and would enable future refactoring to the visitor pattern (for greater flexibility
  of the parser).

* Changed internal structure of parsed data to better represent input data 1:1.
  The new structure keeps any duplicities if they existed in the input SDF and
  it preservers order of SDF timing records. These changes are to better correlate
  emitted parsed SDF to its original input.

* Added support for more SDF constructs (e.g. `RECREM`, `PERIOD`, `RETAIN`), incl.
  SDF comments. The comments are handled by the lexer as an ignored token and hence
  does not presently propagate to the parser.

* Fixed semantics of delval_list to correspond to SDF 3.0 Std.

* Added unit tests of individual SDF syntax elements. The unit test is however far from complete.

* Added unit tests of complete SDF parsing and writing out the parsed data back
  to SDF. This is much like the original `parse_all_test.py` except that
  `test_parse_and_write.py` operates on SDF strings and not files.

* Added few simple utilities, e.g. to convert SDF to JSON or to reformat SDF.

* This update makes the `parse_all_test.py` test suite fails as some
  of the golden files no longer correspond to how the parsed data looks like now,
  when emitted into a file. To make the test pass again, new golden file set is
  to be generated and reviewed for expected contents.

The rest of commit message consolidates messages from individual commits that
were squashed during the rebase process but were worth to keep.

* Changes to expressions:

  > The *conditional port expression* (`cond_expr`) is part of the `COND`
  > conditional path delay. Past implementation did not handle bracketing.
  > New implementation split the grammar between binary expressions and
  > unary expressions.

  > Presently we do care only for the expression string and not about
  > (binary) operator precedence. Hence we do not build the (precedence)
  > binary tree for the expression. The present implementation is likely
  > to have better performance and if the operator precedence determination
  > were needed, I would suggest to have a separate, on-demand parser for
  > it.

  > Along with the parser change, minor changes/fixes were made to the lexer.
  > Namely the `ARITHMETIC` token was broken into individual symbols,
  > `PLUS`, `MINUS` and `TIMES`. This was due to `PLUS` and `MINUS` acting
  > as both unary and binary operators.

  > `SCALARCONSTANT` was fixed not to match strings `0'b1` and `0'b0`, which
  > are not valid constants.

* Fixed `real_triple` to allow single numeric value.

  > As per SDF Std., `rvalue` (which is equivalent to the `real_triple` parser
  > rule in sdfyacc.py) can be either a triple or a single value. The latter
  > was missing in the grammar and fixed by this commit.

* Added `PERIOD` and `NOCHANGE` timing checks.

  > The two timing checks were missing in grammar rules and no lexical
  > tokens were defined for them. Also added some basic unit tests.

* Reduced usage of helper collections in parser.

  > The legacy parser code have been using global collections to aggregate
  > various parsed constructs. This coding style is very fragile for refactoring
  > and there is also no reason why to use it. Instead, one can aggregate the
  > collections within return values of grammar rules.

  > This commit should fix the above coding pattern for lists of DELAY,
  > TIMINGCHECK and TIMINGENV collections.

* Added `RETAIN` and `RECREM`. Added an ignored comment token to lexer. Fixed `delval_list`.

  > Fixed/changed `delval_list` grammar rule to follow SDF standard (3.0).
  > Now one, two, three, six and twelve delays can be used. They are then
  > expanded into a map of twelve transitions (as per Std.). In legacy
  > code, the delay value were treated as slow/nominal/fast delays, which
  > was obviously away from SDF standard.

  > The retain delay is put under its parent `iopath`. The retain delval
  > list goes under the `retain_paths` dictionary key. This key is populated only
  > when the `IOPATH` has an associated `RETAIN`; otherwise the `retain_paths`
  > key will not exist. The existince of `RETAIN` can be tested through a bool
  > key `has_retain` that will be populated for every `iopath` type timing
  > arc/record structure.

* Uniquification of names of timing annotations.

  > When adding collected timing annotations under the cell construct,
  > it may happen that there will be two or more annotations with the
  > same name. For example, two conditional IOPATH delays (with different
  > conditions) for the same from-to pair of pins. The way how the
  > derived timing annotations are named and indexed under the cell
  > will yield name collision.

  > This commit adds code to detect such collisions and append a sequential
  > number to uniquify every colliding occurrence. For example::

      (CELL
          (CELLTYPE "AND2")
          (INSTANCE top.b.d)
          (DELAY
              (ABSOLUTE
                  (COND !en (IOPATH a y (1:2:3)(1:2:3)))
                  (COND en  (IOPATH a y (4:5:6)(4:5:6)))
              )
          )
      )

  > The first annotation will be named `iopath_a_y` and the second
  > `iopath_a_y_1`.

* Timing checks grammar rules now return scalar.

* Changed delval_list result to remain a list.

  > This is to be able to perform original stability tests. Otherwise it would be
  > impossible to identify how many delval entries there were in the input SDF.

  > Also removed sorting delays for a cell as there is no reason to do so.

* Changed dict keys holding removal and recovery times.

  > Changed `removal` to `hold` and `recovery` to `setup` for the `recrem`
  > timing check to make it easier for printing out (as there will be the
  > same keys for `recrem` as for `setuphold` and `nochange` arcs).

* Temporary util `utils/sdfwriter.py` using legacy `sdfparse.emit()`.

  > Temporarily added a new utility that works like `utils/reformat.py` but uses
  > the legacy SDF formatting API. The utility is to prep for updates in `cell`
  > and `instance` structure reorganization in the parser. Present tree-like
  > structure cannot handle well name duplicities in cells or instances. Cell
  > name duplicities now yields partial loss of sequential order in the parsed
  > structure (when compared to the input file). Instance name duplicities
  > (assuming the same cell name) will lead to loss/overwriting of certain
  > timing data. While cell & instance duplicities should not generally occur
  > in a semantically correct SDF input, their resolution shall be deferred
  > after parsing.

  > The above limitations will be addressed in future commits. The new utility
  > will be used for testing to ensure the future `sdfparse.emit()` will remain
  > compatible with the legacy one as much as possible.

* Changed parsed structure of scalar delay values.

  > Newly represent a scalar delval (e.g. `(1.1)` or `()`) by using a dictionary
  > with a single `all` key rather than with the `min`, `avg` and `max` key triplet
  > with the same value. This helps to indicate the parsed value was indeed a scalar
  > and not a triplet.

* Changed parsed structure to list of cells.

  > Changed the `cells` key under the timings structure to be a list rather
  > than a dictionary. This will keep order of cell records as they were in
  > the input SDF file.
@brabect1

Copy link
Copy Markdown
Author

Hi Karol,
I do have little experience with collaboration on Github and will need a bit of guidance. I rebased master in my fork. Am I supposed to start a new PR now?

@mithro

mithro commented Nov 28, 2022

Copy link
Copy Markdown
Contributor

@brabect1 - You just push over the previous branch and it will update the pull request -- seems like you figured that out?

@kgugala

kgugala commented Nov 29, 2022

Copy link
Copy Markdown
Member

there seems to be a number of python errors like e.g.:

E   ImportError: attempted relative import with no known parent package

@brabect1

Copy link
Copy Markdown
Author

Hi guys, I will need more guidance on how to fix these CI workflows. Say a make a change that is assumed to fix a problem. I guess I have to push into master branch. How do I run the CI tasks then to see if the fix worked?

@tmichalak

Copy link
Copy Markdown
Contributor

@brabect1 opening PR should be enough to trigger the CI.

@brabect1

Copy link
Copy Markdown
Author

Hi, can someone trigger CI to see if there are more things to fix? thanks

@kgugala

kgugala commented Jan 12, 2023

Copy link
Copy Markdown
Member

Hi, can someone trigger CI to see if there are more things to fix? thanks

CI was triggered and finished, but it is still red - mostly in package tests:

E ImportError: attempted relative import with no known parent package

@brabect1

Copy link
Copy Markdown
Author

Hi Karol, I did one more fix. Could you please trigger CI again? I am not able to reproduce the issue locally, which is why this trial and error approach ...

@brabect1

Copy link
Copy Markdown
Author

Ok, looks good. Now the interesting part. parse_all_test.py is failing because it now produces a different output than in the reference data set. For example, the original parser code removed duplicate SDF records; the new code keeps the duplicities as it is not up to the parser to resolve it (it is rather up to the consumer of parser output).

So I guess someone from the team should review, if the new output is ok. You can either generate the new reference set yourself, or I can make it a part of the pull request (replacing the old reference set). Which way you prefer?

@brabect1

Copy link
Copy Markdown
Author

Hi guys, as no one responded to my last post I made updates to the golden set files to match what the parser now generates. Can you trigger CI so we see there are no other issues? You can then review the changes to the golden set. Thanks.

(TIMINGCHECK
(HOLD CLK (posedge CLK) (0.507::0.411))
(HOLD CLK (posedge CLK) (0.728::0.538))
(HOLD CLK (posedge CLK) (0.579::0.46))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we have 4 entries for the same timing? Shouldn't this be handled by speed corners?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate arcs are due to the fact they are in the input clb.sdf file. The original parser code removed these duplicate entries so that it kept the last record. A parser shall not do that as it would be then losing information it provides to downstream SW layers. Think that you would like to do pretty printing or write a syntax aware editor; it would not be possible if the parser is removing syntax elements.

So if you compare the output SDF to clb.sdf input, you would notice that now they are the same. The only difference is that sdfparse.emit() sorts the parse elements so they appear reordered in the output file. I did not fix emit() to not do sorting. Instead, I created a new output sdfwrite.print_sdf() that writes out elements in the order of the input. To see the difference, compare the outputs of utils/sdfwriter.py and utils/reformat.py. For example:

python3 utils/sdfwriter.py --stdout sdf_timing/tests/data/clb.sdf
python3 utils/reformat.py --stdout sdf_timing/tests/data/clb.sdf

(DELAY
(ABSOLUTE
(COND (A == 1'b0 && B == 1'b0 && C == 1'b0)
(COND (B == 1'b0 && C == 1'b0 && D == 1'b0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any idea why the conditions here change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original output file was wrong. Compare to the input fixpoint.sdf. I do not remember if this was a problem in the parser or in the sdfwrite.emit(). If you compare the new golden output file with the test input file, they should match now.

(PATHCONSTRAINT I2.H01 I1.N01 (989.0:1269.0:1269.0) (989.0:1269.0:1269.0))
(PATHCONSTRAINT I2.H01 I3.N01 (904.0:1087.0:1087.0) (904.0:1087.0:1087.0))
(PATHCONSTRAINT I1.N01 I2.H01 (989.0:1269.0:1269.0) (989.0:1269.0:1269.0))
(PATHCONSTRAINT I3.N01 I2.H01 (904.0:1087.0:1087.0) (904.0:1087.0:1087.0))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this OK?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that is not right. This is a problem in sdfwrite.emit(). If I recall right, the original parser did incorrectly swap source and destination ports in few cases; guess that this is one of them. I fixed that in the parser but not in emit() (either because I did not notice or because it wasn't easily possible). After all, I think that emit() should be discontinued in favor of print_sdf(). To see difference between the two, compare outputs of utils/sdfwriter.py and utils/reformat.py on the input file in question.

#!/usr/bin/env python3
# coding: utf-8

# Copyright 2022 Tomas Brabec

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we stay with F4PGA Authors (as it is in all the other files)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so as long as you eventually merge in the pull request; there is nothing special about the test code that I would regret losing copyright to. You may then add me to AUTHORS if you like and consider this pull request a significant contribution; that's really up to you.

So once we resolve all the other comments, I will change the notice.

@kgugala

kgugala commented Feb 21, 2023

Copy link
Copy Markdown
Member

The PR looks good in general, I left a few comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants