Register Aggregation for Hardware Decompilation
Varun Rao Zachary D. Sisco




Up, Up, and Away: Winds and Dynamical Structure as a Function of Altitude in the Ultra-Hot Jupiter WASP-76b
    [
    September 2024
===========================================================================================================

empty
empty




§ ABSTRACT

Hardware decompilation reverses logic synthesis, converting a gate-level digital electronic design, or netlist, back into hardware description language (HDL) code. Existing techniques decompile data-oriented features but do not address memory elements, which pose difficulty due to their deconstruction into data flip-flops in netlists and the cycles they form. Recovering multi-bit registers and memory blocks expands the applications of hardware decompilation, notably towards retargeting technologies (ie FPGAs to ASICs) and decompiling processor memories. We devise a method for register aggregation, to identify relationships between the flip-flops in a netlist, categorize them into registers and memory blocks, and output HDL code instantiating these memory elements. We group flip-flops by common enable pins and derive register bit-orders using functional dependencies, scaling up similarly to two-dimensional memory blocks. We evaluate our technique over a dataset of netlists, comparing the quantity and widths of the recovered registers and memory blocks with the netlist source code. The technique successfully recovers memory elements in all of the tested circuits, even aggregating beyond the source code expectation. In 10/13 circuits, all source code memory elements are accounted for, and we are able to compact up to 2048 disjoint bits into a single memory block. 


Keywords: hardware decompilation, hardware description languages, sequential logic, electronic design automation



§ INTRODUCTION

Hardware description languages (HDLs) are often employed for designing large-scale digital electronics, given their large inventory of abstractions. They take advantage of logic synthesis tools to produce netlists, which are graphical circuit representations consisting of individual logical gates that prove useful during manufacturing. Since synthesis distills HDL code down into gates, the semantic meaning of the code gets lost in the synthesized netlist, making these netlists quite difficult to analyze and interpret. Synthesis is also nondeterministic, involving numerous optimizations, incompleteness, and occasional errors [<ref>–<ref>]. Therefore, hardware designers must run extensive simulations to confirm that their HDL code and the netlist synthesized from it have identical behavior [<ref>].

The problem of hardware decompilation has been proposed to reduce this simulation time and compress netlists for easier use. Hardware decompilation produces HDL code deterministically from a netlist by recognizing various abstractions and collapsing them into code. Netlists are much larger artifacts than their code counterparts [<ref>], so decompiling a netlist into HDL code allows it to be simulated much faster. Beyond improving simulation speed, hardware decompilation has a number of other applications, such as netlist compaction, transpilation between HDLs, and propagating netlist edits back up to code. Existing work in hardware decompilation deals with features like loops [<ref>] and modules [<ref>], which are more prevalent in combinational logic. However, these methods can only decompile sequential circuits in certain contexts, lacking a robust way to deal with larger memory elements.

In this paper, we address hardware decompilation with respect to the defining feature of sequential logic: registers and memory blocks. The gates forming a netlist are bitwise operators, so any registers present in the netlist must be split into single-bit data flip-flops (DFFs) during synthesis, losing their connection to one another. 

We aim to perform register aggregation, to recover the multi-bit registers and memory blocks in a netlist which were originally instantiated in the equivalent HDL code.  This contributes significantly to processor decompilation given that registers, and especially memory blocks, are key elements of almost all processors. Additionally, register aggregation allows for technology retargeting, such as from field-programmable gate arrays (FPGAs) to application-specific integrated circuits (ASICs), once memory blocks can be manipulated as cohesive pieces of the netlist. There is existing work in forming multi-bit registers in a netlist, but this is applied to reverse engineering instead of decompilation and focuses primarily on data flow, neglecting bit-order and not outputting HDL code [<ref>]. It also does not support memory block recovery. Other reverse engineering work does produce HDL code but does not target memory elements [<ref>-<ref>].

We propose a two-step approach to aggregate the DFFs in a netlist into registers: first dividing them into groups and then ordering each group to form a multi-bit register. We follow a similar approach in the second dimension for memory blocks. We group DFFs by enable, and derive their relative order according to functional relationships between DFFs. This is inspired by a characterization of sequential logic using linearly inductive boolean functions [<ref>]. The key contributions of this paper are:


  * We describe and implement a technique to group and order DFFs into registers (Section 2A).

  * We describe and implement a technique to scale our work up to two-dimensional memory blocks by aggregating multi-bit registers (Section 2B).

  * We evaluate our technique on a set of benchmark netlists with known memory elements by comparing the sizes and quantities found (Section 3). 




§ METHODS




 §.§ Aggregating Registers 

We begin by partitioning the set of  netlist DFFs into register groups, which we accomplish through grouping DFFs by their enable inputs. This is motivated by the idea that DFFs belonging to the same multi-bit register should all be activated by the same enable signal. Grouping by enable limits our algorithm to considering only DFFs with an enable pin, which is reasonable given that hardware decompilation is an inherently heuristic process. 

Once the partition has been made, we search for an ordering of each register group. We do this by finding the register transfer arc of each DFF in the group, which is the set of nodes in the netlist that are ancestors of that particular DFF. We find the transfer arc of a DFF by performing an upward depth-first search from it on the netlist, searching specifically for other DFFs in the register group to find functional dependencies. A dependency relationship expresses that the input into one DFF is predicated upon the output from another. 

Key to the depth-first search is dismantling the cycles formed by the DFFs, which we do by separating each DFF into two nodes: one for its current value and one for the next value to be stored. We organize the dependencies into a directed graph, where the nodes are the DFFs in the register group, and each directed edge represents that one DFF is dependent upon another. Note how DFF y being dependent on DFF x (denoted x < y) is directly transferable to x coming before y in the register’s ordering. This is because if there is no dependency between two bits in a register, their pairwise bit-order does not actually affect the circuit’s functionality; a dependency between two DFFs shows that the circuit relies on their pairwise order in the register. This means that our graph of < dependencies is essentially a graph of pairwise orders. 

In the graph, a valid ordering consists of a sequence of the DFFs that obeys as many < dependencies as possible (ideally all). This is a classic scenario where we can employ topological sort. Since the <’s form directed edges in the graph, a topological sort would naturally build an ordering that resolves all of the < operations if such an ordering exists, or pick one which resolves as many <’s as possible. Oftentimes, there are not enough dependencies to leave the topological sort with only one valid ordering, leading it to pick a different ordering from the originally intended one. However, this is because the intended ordering was chosen in a partially arbitrary manner, so the modified portion of the computed ordering does not actually matter to the circuit’s output. Thus, we have an algorithm to partition the registers in a circuit and order each grouping, addressing our twofold goal for multi-bit registers. 



To illustrate our method, we provide an example visualizing the ordering technique on a 3-bit counter netlist in Figure 1, assuming the partition has already identified the 3 DFFs as a register group. The register transfer arcs for the 3-bit counter can be formalized into equations, with respect to a high enable bit.
If the superscript n denotes the nth clock cycle, we have

    [                                                           r_0^n =  r_0^n-1,;                                                   r_1^n = r_0^n-1⊕ r_1^n-1,;                                       r_2^n = ( r_0^n-1 r_1^n-1 ) ⊕ r_2^n-1 ]


Equation <ref> can be generalized to an inductive formula [8] for all counter DFFs:


    [                                                                                      r_i^0 = 0,;                                                 r_i^n = (r_0^n-1 r_1^n-1… r_i-1^n-1 ) ⊕ r_i^n-1 ]


Extracting functional relationships between DFFs from equation <ref> yields:


    r^n_0(r^n-1_0), r^n_1(r^n-1_0, r^n-1_1), r^n_2(r^n-1_0, r^n-1_1, r^n-1_2)




The relationships stated in <ref> are then used to form a directed graph of dependencies between the DFFs, upon which topological sort is performed, creating the final ordering, as shown in Figure 2a. The dependency graph for a counter shows each DFF being dependent on all of the DFFs coming after it in the register, but for other small building blocks we find different dependency relationships. For example, the dependencies in a shifter (Figure 2b) create relationships only between consecutive bits instead. For a circuit where the register value is updated using a bitwise operation with an input wire, each of the DFFs is completely independent, so all possible orderings are valid and will preserve the integrity of the circuit  (Figure 2c). 



 §.§ Aggregating Memory Blocks 

After aggregating multi-bit registers, we wish to form memory blocks by grouping suitable registers of the same width into a matrix of DFFs. Under the assumption that the memory blocks support enabled writes, we can begin with grouping by enable again, this time by searching for a wire which is a common input into the respective enables of each multi-bit register in the memory group. Unlike the register scenario, there is no need to find a vertical ordering between the registers in the memory group because they will be deleted and replaced with the memory block instantiation. However, memory blocks are more abstract than multi-bit registers, with defined ports beyond just an organized collection of DFFs. To form a complete memory block, we also need to identify and bundle the wires corresponding to each port by searching around the registers in the memory group. We consider memory blocks with the following four ports: one read port, one write port, one address, and an enable. 

We have already described how to find the enable. To find the address, we move upward on the netlist from the registers in search of the logic that selects which register to write to based on the bits of the address. This logic is recognizable in the netlist because it consists solely of ands and nots in terms of the single-bit wires corresponding to each address bit: these are the wires that must be bundled into the address. Since each register has a unique address, some unique subset of the address bits is negated and conjuncted with the rest of the address bits to form the address. We leverage the fact that every register in the memory group will have these ands and nots in terms of the same address bits, allowing us to find the address reliably. 

Since we assume that there is only one write port, all the registers will have their DFF data inputs updated by the same set of wires when the enable is high, which is the write port. Finally, for the read port, we follow a multiplexer tree stemming from the registers until it stops at a series of n wires, where n is the width of each register. The multiplexers are synthesized due to choosing which register is read from given the address, with the select pin of each multiplexer being an address bit. We infer the depth of the multiplexer tree from the address width to find the read port wires. Bundling all the ports completes memory block aggregation: we can now instantiate memory blocks using the widths of their registers and addresses, as well as their bundled input and output ports.

We implement our full aggregation technique entirely in Python, inputting netlists as Berkeley Logic Interchange Format (BLIF) [<ref>] files and then parsing them to a more usable format in the PyRTL HDL [<ref>]. Our output modifies the PyRTL working block, which stores all the netlist wires and gates, for easy translation to code. 



§ EVALUATION 




We test our method on a set of 13 benchmark circuits designed in Verilog (mainly from the Basejump STL [<ref>]) and PyRTL [<ref>] with known registers and memory blocks, described in Figure 3. The benchmarks consist mainly of circuit building blocks, like arithmetic operations, caches, and register buffers, but they also include two CPUs: nerv (a RISC V processor) and opdb_pico from OPDB [<ref>].

We compare the source code registers and memory blocks to the technique’s outputted predictions for each benchmark (see Figure 4 for an overview across benchmarks).  



 §.§ Categorization of the Main Study 


We find that the results can be categorized into groups. For the alu, fifo (Figure 5a), bsg_fifo, and bsg_lfsr benchmarks, our technique performs perfect aggregation, or behaves exactly as expected. It recovers all the enabled registers and memory blocks originally present in the source code with identical dimensions. Notably, we use bsg_strobe as a negative example, where there are only DFFs without enables; the technique does not produce any false positives and outputs zero registers. 





For the piso, bsg_assembler, and bsg_idiv (Figure 5b) examples, the technique not only recovers all the source code registers as desired, but it additionally aggregates subsets of them into larger registers. Each such subset shares an enable and can therefore be concatenated into a larger register without affecting the circuit’s behavior. We verify that the technique is indeed accurate by confirming the presence of common enable pins between multi-bit registers instantiated in the source code. This additional register aggregation occurs more dramatically in the bsg_multiply (Figure 5c) and bsg_multiply_array examples, where all the source code registers get combined into one register because they all share one common enable. The additional register aggregation performed by our technique shows that our work can improve the register groupings in a netlist beyond what is originally in the source code, suggesting organizational and compaction capabilities. These capabilities open up potential new applications for decompilation, such optimizing register and memory block usage in existing HDL code.

Additional register aggregation seems to occur with the opdb_pico example as well, but the scale is too large to identify where each source code register ends up in the register predictions. Moreover, opdb_pico also has two memory blocks, and the technique successfully recovers the 32× 32  memory block but fails on the 4 × 32 one, outputting it as 4 32-bit registers instead. Here, the neglected memory block is still compacted into 4 registers from 128 DFFs present after synthesis, so some organizational information is retained.

For the nerv example, the technique groups all the registers correctly, additionally aggregating two 5-bit registers into a 10-bit one. However, it fails to recover the 32 × 32 memory block, instead outputting it as a 32 32-bit registers. The reason that this memory block is not recovered is because it has multiple read ports and addresses, breaking our memory block port assumptions and preventing the technique from finding a single address. In investigating this failure, we find that the technique does group the 32 registers with an enable before quitting the aggregation process after a unique address is not found. 

Finally, for the bsg_cache example, additional aggregation is extended even further, with 8 memory blocks being created out of source code without any memory block instantiations. This is of great significance because 16384 DFFs after synthesis are not only recovered into 2048 8-bit registers in the source code, but compacted even further into 8 8 × 256 memory blocks. This compaction from 2048 source code artifacts down to 8 artifacts does not affect the circuit's behavior while improving its organization tremendously. 

Given limitations of our current technique, such as reliance on PyRTL and memory block port assumptions, future directions include building a more language-agnostic technique and supporting multiple read or write ports in memory blocks. Moreover, additional aggregation, particularly its extension to forming new memory blocks as observed in bsg_cache, illuminates the possibility of utilizing hardware decompilation to build implicit memory blocks from existing HDL code. To further affirm the real-world relevance of register aggregation, previously stated applications such as technology retargeting could be tested in later work.



 §.§ Runtime Analysis



To analyze the time complexity of our aggregation technique, we measure its runtime on the alu example, varying the dimensions of the alu memory block to change the size of the netlist. As the number of gates increases, the runtime scales at a cubic rate, as seen in Figure 5a. The cubic complexity is due to the graph traversals necessary to find dependencies for each located DFF. It turns out that the number of DFFs in a netlist is roughly linear with respect to the netlist's size. As a result, register aggregation is necessarily polynomial time, meaning our current technique is reasonably fast.

We also investigate the sub processes inside the aggregation technique to see which tasks consume the most time. Given that most of the steps are linear time operations, we only measure three main graph traversal tasks. The first task is optimize, which is a method outside of the to the actual aggregation that eliminates redundancies in the netlist. The other two tasks are finding register dependencies and memory addresses, as described in Sections 2a and 2b.
    
In benchmarks with fewer netlist gates, optimize is the slowest task, but the other two tasks take up all most all of the time for larger benchmarks. This elucidates that optimize performs its necessary graph traversal operations in less than cubic time relative to the netlist size, revealing the potential to speed up other graph traversal operations in the technique to be on par with optimize. Moreover, when there are memory blocks found, memory address computation quickly outweighs finding register dependencies. This is because memory blocks are two-dimensional while registers are one-dimensional, so they have an extra factor of time complexity.



§ CONCLUSION

Our research presents and evaluates a technique to perform register aggregation for hardware decompilation, a facet of the problem which has not been targeted before. We find that our technique is largely successful at the task of register aggregation, recovering memory elements in all 13 examples and producing accurate dimensions in 10 of them. It is also relatively efficient given its predicted cubic time complexity. Our register aggregation work expands the scope of hardware decompilation, paving the way towards applications like technology retargeting and decompiling large processors.



§ ACKNOWLEDGEMENTS


We would like to thank Prof. Jonathan Balkind for guidance and discussions about the research, as well as Pranjali Jain for many tips and helpful feedback. We thank Dr. Lina Kim for instruction and making this research possible through the UCSB Research Mentorship Program.



§ REFERENCES





  *  Y. Herklotz and J. Wickerson, “Finding and understanding bugs in FPGA synthesis tools,” Proceedings of the 2020 ACM/SIGDA International Symposium on Field-Programmable Gate Arrays, Feb. 2020. doi:10.1145/3373087.3375310

  *  R. Nigam et al., “Predictable accelerator design with time-sensitive affine types,” Proceedings of the 41st ACM SIGPLAN Conference on Programming Language Design and Implementation, Jun. 2020. doi:10.1145/3385412.3385974

  *  G. H. Smith et al., “FPGA technology mapping using sketch-guided program synthesis,” Proceedings of the 29th ACM International Conference on Architectural Support for Programming Languages and Operating Systems, Volume 2, Apr. 2024. doi:10.1145/3620665.3640387

  *  S. Beamer, “A case for accelerating software RTL simulation,” IEEE Micro, vol. 40, no. 4, pp. 112–119, Jul. 2020. doi:10.1109/mm.2020.2997639

  *  M. Ganai and A. Kuehlmann, “On-the-Fly Compression of Logical Circuits,” International Workshop on Logic Synthesis, Jul. 2000.

  *  Z. D. Sisco, J. Balkind, T. Sherwood, and B. Hardekopf, “Loop rerolling for hardware decompilation,” Proceedings of the ACM on Programming Languages, vol. 7, no. PLDI, pp. 420–442, Jun. 2023. doi:10.1145/3591237

  *  G. H. Smith et al., “There and Back Again: A Netlist's Tale with Much Egraphin',” arXiv:2404.00786 [cs.AR], Mar. 2024. 
 doi:10.46586/tches.v2020.i4.309-336 

  *  N. Albartus, M. Hoffmann, S. Temme, L. Azriel, and C. Paar, “Dana Universal Dataflow Analysis for gate-level netlist reverse engineering,” IACR Transactions on Cryptographic Hardware and Embedded Systems, pp. 309–336, Aug. 2020. doi:10.46586/tches.v2020.i4.309-336

  *  J. Portillo, T. Meade, J. Hacker, S. Zhang, and Y. Jin, “RERTL: Finite State Transducer Logic Recovery at Register Transfer Level,” 2019 Asian Hardware Oriented Security and Trust Symposium (AsianHOST), Dec. 2019. doi:10.1109/asianhost47458.2019.9006699

  *  T. Zhang, J. Wang, S. Guo, and Z. Chen, “A comprehensive FPGA reverse engineering tool-chain: From bitstream to RTL code,” IEEE Access, vol. 7, pp. 38379–38389, 2019. doi:10.1109/access.2019.2901949

  *  A. Gupta and A. L. Fisher, "Representation and symbolic manipulation of linearly inductive Boolean functions," Proceedings of 1993 International Conference on Computer Aided Design (ICCAD), Santa Clara, CA, USA, 1993, pp. 192-199.
 doi: 10.1109/ICCAD.1993.580055

  *  UC Berkeley. Berkeley logic interchange format (BLIF). Oct Tools Distribution, vol. 2, pp. 197–247, Jul. 1992.

  *  J. Clow et al., “A pythonic approach for Rapid Hardware Prototyping and instrumentation,” 2017 27th International Conference on Field Programmable Logic and Applications (FPL), Sep. 2017. doi:10.23919/fpl.2017.8056860

  *  M. B. Taylor, “Basejump STL: Systemverilog needs a standard template library for hardware design,” Proceedings of the 55th Annual Design Automation Conference, Jun. 2018. doi:10.1145/3195970.3199848

  *  G. Tziantzioulis et al., “OPDB: A Scalable and Modular Design Benchmark,” IEEE Transactions on Computer-Aided Design of Integrated Circuits and Systems, vol. 41, no. 6, pp. 1878–1887, Jun. 2022. doi:10.1109/tcad.2021.3096794





