-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Patgen platform #92
Patgen platform #92
Conversation
Hi @coreyeng, when writing this I realized that I forgot to add an API method to replace and insert nodes into the AST to support something like the runtime cycle compressor. |
Probably also worth saying that this deviated quite a bit from the wiki proposal, mainly with regards to how the derivative ASTs are stored. I think that might be OK since once generation switches to the backend it shouldn't need to swtich back and forth between Python and Rust any more. |
Another thing missing is test support, we will need some helpers for creating test cases to verify that generators produce the correct output. |
@ginty Thanks for putting this together! It does differ quite a bit from wiki but I think I'm still following what's going on. Regarding the services, can a service implemented only in Python interact directly with the AST? For example, if we had an internal protocol similar to JTAG but implemented only in Python would there be a way to inject a new node into the AST? Or would it have to boil down to pin operations, which would disrupt any protocol-aware ATE-side generation? Or could we extend |
Hi @coreyeng, there's nothing currently that would allow a service or any other object in the Python domain to interact with the AST directly. I think node creation should almost always be a side effect of calling an API like Attrs::GenericTransaction(String, BigUint, Option<BigUint>, Option<BigUint>, Option<BigUint>) Then we would have some (tester?) API method to create them: ref = tester.start_transaction("my_trans_type", 0x1234, 0x1234, None, None)
tester.cycle
tester.cycle
tester.close_transaction(ref) Could add some optional string fields too I suppose for extra flexibility. I would say the bigger challenge is how to process such a node into a PA-aware output - I guess ultimately for something like that we would also be talking about a custom generator implemented in Python. I think that means that we also need to be able to create AST processors in Python. That would allow the AST to go over the wall and after that it shouldn't be hard to add the ability to create processors in Python - the Ruby equivalent base processor is only about 20 lines or so. What do you think? |
Hi @ginty, yes, I think a generic node like that would work fine. There definitely would be a custom generator involved, though now that I think more about it, the generator could key off of the |
OK, I'll work on adding some of that, plus having the AST in python might also be useful for unit testing. |
Hi @coreyeng, I added some additional methods: // Replace the current node at position n - 0 with the given node
TEST.replace(node, 0);
// Insert the given node at position n - 0
TEST.insert(node, 0);
// Get (a copy of) the node at position n - 0
TEST.get(0); That should be enough to implement something like the cycle optimizer we talked about: // If the last node was a cycle
if let Attrs::Cycle(repeat, compressable) = TEST.get(0)?.attrs {
if compressable {
TEST.replace(node!(Cycle, repeat + 1, true), 0)?;
}
} |
Looks good! |
Hi @coreyeng, OK, I added the ability to transfer the AST over to Python and to create processors in Python, you can get a handle on it from I used the Serde crate to serialize the AST into a byte format called Pickle which seems to be Python's equivalent to Ruby's Marshal, and that was remarkably easy, that Serde crate is really good. The way you create a processor in Python is much the same as in Rust, the only real difference is that you just get the node as the argument passed into the handler methods rather than the de-composed attributes. There are a couple of examples in these tests: So I think I'm done with this PR unless you can think of anything else that should be added at this stage? If you are happy to merge approve when you are ready. |
Looks good to me! |
This PR adds the following:
services.py
, for hooking up things like drivers and other servicesmy_block.sub_blocks
to be a regular Python dictThe long description:
AST Generation
All AST-related stuff lives in
origen/src/generator
and all processors live inorigen/src/generator/processors
. Expect in future this will expand to../generator/processors/v93k
, etc.There is a singleton object defined by
../generator/test_manager.rs
which is instantiated and available globally fromorigen::TEST
and this is a representation of the current test being generated.This wraps up a single AST instance to make it easy to push nodes from anywhere without having to worry about getting a mutable lock on it.
AST nodes are comprised of three main pieces of information:
RegWrite
orRegVerify
There is also provision made for meta data, e.g. to store source file and line number info but that is not currently hooked up to anything.
All nodes are defined in
../generator/ast.rb
.Program generator nodes and all ATE-specific pattern nodes should also go in here, so this will become a big file, but I think it will be useful when writing processors to have a single place to refer to the full library of possible nodes.
Nodes are created via the
Node::new()
method where the node type and attributes must be supplied.For example, the cycle node is defined as:
To create a node representing a single cycle and which can be compressed:
a macro is available to make this more succinct:
Creating a node does not automatically add it to the current AST, to do that simply push it to the
TEST
manager:Nodes can be cloned (which will also clone all of their children) and to push the same node multiple times it must be cloned:
To add a child to a node:
Note that there is no differentiation between node types that can accept children and those that can't - they can all accept children. In practice though many nodes will be considered as terminal nodes and will be used as such, the cycle here being one such example.
In most cases children would not be added directly like this and would instead be added via the
TEST
manager API.An additional method named
push_and_open(<node>)
is available which will add a node to the AST and then leave it open such that all future nodes will be pushed as children of this node until a correspondingTEST.close()
is called.To help catch coding errors where the user has forgotten to close a node, a reference is returned when leaving a node open and this same reference must be supplied when closing it.
This is best shown by example:
In the console the current test's AST can be viewed by calling:
This is considered temporary and in future I expect this will be hooked up to something like
tester.pattern.ast
or similar.AST Processing
The AST processing system is greatly inspired by the Ruby lib that was used for the O1 program generator. This remains a good and relevant reference on how the O2 processors work - http://whitequark.github.io/ast/AST/Processor/Mixin.html
Here is the boilerplate to create a new processor:
The
run
function is the entry point to the processor, it takes a node as an input (an AST is just the top-most node in the tree) and returns a new node as the output - which is basically a representation of the input node which has been transformed in some way by the processor.The
run
function should instantiate the processor and call theprocess
method on the node, giving the (mutable) processor instance as an argument.The
run
function is a convention rather than being a hard requirement and deviations are allowed where appropriate. For example, the function could return some result value rather than a new node.Fields should be added to the processor's struct to handle any temporary storage that it needs.
All processors must implement the Processor trait. This trait provides default methods to handle all node types and so no methods are actually required.
By default the processor will iterate through all nodes and return an unmodified clone of them, therefore the processor above will return a clone of the input node.
To implement a handler for a particular node type define a function named after the underscored node type preceeded by
on_
, for exampleon_comment
,on_cycle
,on_reg_write
, etc.The arguments for the handler correspond to the node attributes and all handlers take the node itself as the last argument.
For example, the comment node type has the attribute signature:
and here is a simple handler to upcase all comments:
Here is an example of a processor which will remove all comments below the level given to the processor:
All handler functions return a special
processor::Return
type which is defined in../generator/processor.rs
, which provides option like whether to remove the node, keep it unmodified, process its children (but otherwise keep it unmodified), or replace it with a new node or nodes created by the handler function.There are also some special handler functions,
on_all
will be called for every node type which does not have a dedicated handler defined, andon_end_of_block
will be called at the end of processing every node which has children.It is expected that more special functions will be added over time as processors are developed.
See
../generator/processors
for some more examples.TEST AST Processing and Combining Processors
The main
TEST
AST is well wrapped up such that it is not possible to get a direct handle on it.A special method is provided to process the AST which takes a closure function and the ast will be passed into this:
Once you have the initially processed AST it is a simple matter to then make further transforms:
Adding New Nodes
Defining a new node type requires it to be added to 3 places:
Attrs
enum in../generator/ast.rs
Node::process
function immediately below theAttrs
enum in../generator/ast.rs
../generator/processor.rs
It is expected that we will eventually end up with hundreds of node types so try and group related nodes together and use comment blocks for organization.
Note that the
match
block in theNode::process
function will be compiled like a C switch statement, meaning that it will execute in constant time and will not slow down as more nodes are added. i.e. it will be implemented like a jump table in assembly and not like if/else logic which has to be evaluated in turn.JTAG and Services
To provide something to generate a meaningful AST I hooked up the register actions to the controllers and added a simple register action handler in the controller of the example DUT:
I realized that the JTAG driver couldn't be instantiated like a sub-block in O1 without significant internal changes since its not so easy to store different types in the one collection in Rust compared to Python or Ruby.
So I introduced a new block file type and collection called
services
:Basically to define a service on a block (model) you define what you want it to be called and supply an instantiated object.
The above is just a placeholder and the JTAG contructor will probably accept pin arguments and so on in future.
A service implemented in Rust must implement a
set_model()
method as shown here for JTAG - https://github.com/Origen-SDK/o2/blob/patgen_platform/rust/pyapi/src/services.rs#L30This is responsible for creating the service in Rust and attaching it to the parent model.
Python implemented service can implement the same method if they want to, but it is not required and any object can be given as a service (though currently there are no examples of this).
There is a stub of the JTAG driver implemented here which generates a few dummy cycles currently to make some example AST content - https://github.com/Origen-SDK/o2/blob/patgen_platform/rust/origen/src/services/jtag/service.rs
Note that the functions accept a Value type which is a newly added type which can be used to respresent sized data values that could be either a BitCollection or a dumb value.
It has the following signature:
Example AST
Here are a few console operations to show this in action: