-
Notifications
You must be signed in to change notification settings - Fork 48
SV translation steps
Since our framework is cycle-based and update blocks are statically scheduled, our RTL has more restrictions than SV/V. As a result, our update block is somehow a subset of always_comb block you can write in SV/V. This means that our statically schedule RTL will definitely work after direct translation to verilog, while some always_comb block in verilog may create cyclic dependency if someone tries to statically schedule it.
The most important implication is that we can just directly translate logics to SV/V.
However, there are some things that cannot be translated directly and might need special considerations.
- An array of input/output ports, go with v2's approach? https://github.com/cornell-brg/pymtl/blob/master/pymtl/tools/translation/verilog_structural.py#L345
- Temporary variables, still infer types?
- Define a subset of arbitrary python object as translatable structs?
- Parent component writes to child component's input, which is not the case in SV/V. Go with v2's approach? https://github.com/cornell-brg/pymtl/blob/master/pymtl/tools/translation/verilog_structural.py#L278
- Output reg if the output port is sequential.
- Interfaces? Should we flatten port bundles as we did in v2??
I'm sketching out the whole translation process for v3 before I start to ramp up the code.
-
First of all, build a "trie" whose alphabet is the field name strings in the hierarchy to represent the whole object hierarchy. Each trie node represents a NamedObject, and I also create id_trie dictionary to reverse-lookup the trie node of each NamedObject.
-
Then filter all Components and translate them one by one. Note that each component will be translated into a module and child components of a component will be just instantiation.
For each component:
-
Figure out reg/wire type of each signal/port. To make the translated verilog code synthesizable, signals and ports are declared before references to them in always blocks and assignments. However, it is important to figure out whether a signal is of type wire/reg before we declare them. "schedule", "all_write_upblks" and "all_read_upblks" are the variable to look at. If a signal is read in upblk of the first batch and written in upblk of other batches, it is a reg (RD(x) < WR(x)).
-
Create declarations of the module. Directly translate input port and output port to input/output of the module. For parameters, we might need some metaclass to make the python class highly parametrizable yet translatable to verilog. We might just go with v2's approach, which is to hash the parameters for reuse and remove the parameter part of the verilog module declaration. Nevertheless, caching might have problem due to the hash of arbitrary python objects.
-
Instantiate different functions for the same python function based on each call. Need to infer the types of parameters of each call to the same function.
-
Declare local wire/regs and parameters. Note that We also need to declare wires to hook up child's input port because we can access s.x.y in python but in verilog we can only do x_y and pass x_y into x.
-
Source-to-source translation for update blocks. If an update block only contains reads to wires and writes to wires, it is an always_comb block. If an update block only contains reads to regs and writes to wires, it is an always_posedge block with non-blocking assignments. Note that a special case is that an update block contains a sequential part and some subsequent combinational logic. In this case, we need to separate the sequential part to another block by double-buffering regs and turn this update block into an always_comb block.