diff --git a/docker/Dockerfile.finn b/docker/Dockerfile.finn index dbafba247679895bcbaf385f0d33946c3f810945..f823d3c42b8f958d0d7961c0bdeb931686999fce 100644 --- a/docker/Dockerfile.finn +++ b/docker/Dockerfile.finn @@ -88,8 +88,6 @@ RUN pip install jupyter==1.0.0 --ignore-installed RUN pip install markupsafe==2.0.1 RUN pip install matplotlib==3.3.1 --ignore-installed RUN pip install pytest-dependency==0.5.1 -RUN pip install sphinx==5.0.2 -RUN pip install sphinx_rtd_theme==0.5.0 RUN pip install pytest-xdist[setproctitle]==2.4.0 RUN pip install pytest-parallel==0.1.0 RUN pip install "netron>=5.0.0" diff --git a/docker/jenkins/Jenkinsfile b/docker/jenkins/Jenkinsfile index e3e5b5f7f93c312269f2c96942e44318875903e0..2954877c2a950cd865c7aab2d3a4f80115972037 100644 --- a/docker/jenkins/Jenkinsfile +++ b/docker/jenkins/Jenkinsfile @@ -5,8 +5,8 @@ node { checkout scm } withEnv([ - "FINN_XILINX_PATH=/proj/xbuilds/SWIP/2022.1_0420_0327/installs/lin64", - "FINN_XILINX_VERSION=2022.1", + "FINN_XILINX_PATH=/proj/xbuilds/SWIP/2022.2_1014_8888/installs/lin64", + "FINN_XILINX_VERSION=2022.2", "FINN_DOCKER_TAG=xilinx/finn:jenkins", "FINN_HOST_BUILD_DIR=/scratch/users/finn_ci", "PLATFORM_REPO_PATHS=/opt/xilinx/platforms" diff --git a/docker/quicktest.sh b/docker/quicktest.sh index b4ad37232fa69754a86e9064d7592d7474e8617e..466fcfb09dcf782c8c0f431555439286bcc351d5 100755 --- a/docker/quicktest.sh +++ b/docker/quicktest.sh @@ -6,7 +6,7 @@ cd $FINN_ROOT # check if command line argument is empty or not present if [ -z $1 ]; then echo "Running quicktest: not (vivado or slow or board) with pytest-xdist" - python setup.py test --addopts "-m 'not (vivado or slow or vitis or board)' --dist=loadfile -n $PYTEST_PARALLEL" + python setup.py test --addopts "-m 'not (vivado or slow or vitis or board or notebooks)' --dist=loadfile -n $PYTEST_PARALLEL" elif [ $1 = "main" ]; then echo "Running main test suite: not (rtlsim or end2end) with pytest-xdist" python setup.py test --addopts "-k 'not (rtlsim or end2end)' --dist=loadfile -n $PYTEST_PARALLEL" diff --git a/docs/finn/getting_started.rst b/docs/finn/getting_started.rst index 9b3111b70eae97a3644e1de23c368bd5b09f7927..c575ca7e3b3b04eb651fb1135d949baad038f1ad 100644 --- a/docs/finn/getting_started.rst +++ b/docs/finn/getting_started.rst @@ -107,9 +107,6 @@ These are summarized below: * (optional) ``LOCALHOST_URL`` (default localhost) sets the base URL for accessing e.g. Netron from inside the container. Useful when running FINN remotely. * (optional) ``NETRON_PORT`` (default 8081) changes the port for Netron inside Docker * (optional) ``PYNQ_BOARD`` or ``ALVEO_BOARD`` specifies the type of PYNQ/Alveo board used (see "supported hardware" below) for the test suite -* (optional) ``PYNQ_IP`` and ``PYNQ_PORT`` (or ``ALVEO_IP`` and ``ALVEO_PORT``) specify ip address and port number to access the PYNQ board / Alveo target -* (optional) ``PYNQ_USERNAME`` and ``PYNQ_PASSWORD`` (or ``ALVEO_USERNAME`` and ``ALVEO_PASSWORD``) specify the PYNQ board / Alveo host access credentials for the test suite. For PYNQ, password is always needed to run as sudo. For Alveo, you can leave the password empty and place your ssh private key in the ``finn/ssh_keys`` folder to use keypair authentication. -* (optional) ``PYNQ_TARGET_DIR`` (or ``ALVEO_TARGET_DIR``) specifies the target dir on the PYNQ board / Alveo host for the test suite * (optional) ``IMAGENET_VAL_PATH`` specifies the path to the ImageNet validation directory for tests. * (optional) ``FINN_DOCKER_PREBUILT`` (default 0) if set to 1 then skip Docker image building and use the image tagged with ``FINN_DOCKER_TAG``. * (optional) ``FINN_DOCKER_TAG`` (autogenerated) specifies the Docker image tag to use. diff --git a/docs/finn/img/rtl_swg_impl_styles.png b/docs/finn/img/rtl_swg_impl_styles.png new file mode 100644 index 0000000000000000000000000000000000000000..265ff9b915e79f8e93ca4f987bb49e57f8a2bd3e Binary files /dev/null and b/docs/finn/img/rtl_swg_impl_styles.png differ diff --git a/docs/finn/internals.rst b/docs/finn/internals.rst index add70d649c773061c5b9e1d91dcaa852dcc4cbac..d0c4cd20650a7cb1ef63f68ff559bebbba93ae05 100644 --- a/docs/finn/internals.rst +++ b/docs/finn/internals.rst @@ -205,3 +205,84 @@ Disadvantages: How to set *mem_mode* --------------------- When the nodes in the network are converted to HLS layers, the *mem_mode* can be passed. More detailed information about the transformations that prepare the network and the transformation that performs the conversion to HLS layers can be found in chapter :ref:`nw_prep`. The *mem_mode* is passed as argument. Note that if no argument is passed, the default is *const*. + +RTL ConvolutionInputGenerator +============================= + +FINN implements convolution operations by pairing a ConvolutionInputGenerator (or "sliding window generator (SWG)") with an MVAU or VVAU (for depthwise convolution). +This RTL version is an alternative to the original `HLS implementation <https://github.com/Xilinx/finn-hlslib/blob/master/slidingwindow.h>`_ and aims to improve on it in the following ways: + +* Support a wider range of hyperparameters without the fragmentation into 16+ separate HLS functions + +* Support additional degrees of parallelism (i.e., across the output window or multiple input samples) that are difficult to implement in HLS + +* Support additional features, such as dynamic feature map sizing + +* Improve resource efficiency + + +The component is implemented by generating (System-)Verilog code for each individual instance, realized via the template + replacement dictionary mechanism found in other FINN components. +Despite the HDL implementation, the component is managed by its own HLSCustomOp (!) named "ConvolutionInputGenerator_rtl". Naturally, HLS simulation & synthesis are not supported. + +The RTL SWG is currently disabled by default and can be enabled either in the corresponding HLS conversion transformation (:py:mod:`finn.transformation.fpgadataflow.convert_to_hls_layers.InferConvInpGen`) with `use_rtl_variant=True` or in the build configuration (:py:mod:`finn.builder.build_dataflow_config.DataflowBuildConfig.force_rtl_conv_inp_gen` set to True). + +Implementation styles +--------------------- +Depending on the amount of parallelism requested, one of two implementation styles is selected. The following table defines folding parameters (marked in bold text) and supported configurations. + +.. list-table:: Parallelism configurations + + * - **SIMD** + - **parallel_window** + - **M** + - MMV_in + - MMV_out + - Style + - Notes + * - < C + - 0 + - 1 + - 1 + - 1 + - default + - depthwise-aware + * - C + - 0 + - 1 + - 1 + - 1 + - default + - depthwise-agnostic + * - C + - 1 + - 1 + - 1 + - K + - parallel + - depthwise-agnostic + * - C + - 1 + - M + - M + - M*K + - parallel + - Currently unsupported + +(With C = #Channels, MMV_in = input samples (or "pixels") per cycle, MMV_out = output samples (or "pixels") per cycle, K = kernel_width * kernel_height.) + +The following diagram shows the operating principle of both styles, the "parallel" variant is pictured for a 2x2 kernel without dilation. + +.. image:: img/rtl_swg_impl_styles.png + :align: center + +The main difference lies in the buffer structure. If the output width is equal to the input width ("default mode"), an addressable circular buffer is used, which can be implemented either in LUTRAM, BRAM, or URAM resources. If parallel access to multiple window elements is required ("parallel mode"), the SWG generates a fixed structure of registers and line buffers to avoid memory port limitations and exploding multiplexing logic, while still featuring LUT-saving BRAM/URAM implementation for the line buffers. + +The "default" style also supports a dynamic mode, which provides an interface to change feature map dimensions, stride, or dilation at run-time. See `this pull request <https://github.com/Xilinx/finn/pull/688>`_ description for more information. + +Folding +------- +The RTL SWG is supported by the basic automatic folding algorithm in FINN (:py:mod:`finn.transformation.fpgadataflow.set_folding.SetFolding`). Consider the following implications: + +**MVAU:** Although it is recommended to unfold SIMD first, SIMD and PE can be set independently. Full (and balanced) parallelism is achieved by using the SWG in parallel window mode and setting MVAU SIMD and PE to their maximum values (SIMD = MW = C_in * K, PE = MH = C_out). + +**VVAU:** While the VVAU HLS component supports SIMD unfolding independently from PE, the RTL SWG requires full unfolding across the channel dimension (SIMD of the SWG = PE of the VVAU) before enabling window-parallelism. Unlike the MVAU, the VVAU can't accept datawidth-converted input from a fully-parallel SWG in this case due to the depthwise data layout. As a result, the VVAU should be unfolded by PE first (up to PE = C), followed by SIMD (up to SIMD = K). diff --git a/docs/finn/source_code/finn.core.rst b/docs/finn/source_code/finn.core.rst index afa1ecffa08213db6a282076c6fdf59694f9e13e..28cb47eaf70cade96a1146559cbbd92248923a34 100644 --- a/docs/finn/source_code/finn.core.rst +++ b/docs/finn/source_code/finn.core.rst @@ -54,14 +54,6 @@ finn.core.onnx\_exec :undoc-members: :show-inheritance: -finn.core.remote\_exec ------------------------------ - -.. automodule:: finn.core.remote_exec - :members: - :undoc-members: - :show-inheritance: - finn.core.rtlsim\_exec ----------------------------- diff --git a/docs/finn/source_code/finn.transformation.fpgadataflow.rst b/docs/finn/source_code/finn.transformation.fpgadataflow.rst index 9f8ec079309f16daa022e14317ebddfd7758d639..f7137ae347486692938a23acb1e1fb2798559b33 100644 --- a/docs/finn/source_code/finn.transformation.fpgadataflow.rst +++ b/docs/finn/source_code/finn.transformation.fpgadataflow.rst @@ -173,6 +173,15 @@ finn.transformation.fpgadataflow.minimize\_accumulator\_width :show-inheritance: +finn.transformation.fpgadataflow.minimize\_weight\_bit\_width +-------------------------------------------------------------- + +.. automodule:: finn.transformation.fpgadataflow.minimize_weight_bit_width + :members: + :undoc-members: + :show-inheritance: + + finn.transformation.fpgadataflow.prepare\_cppsim ------------------------------------------------------- diff --git a/docs/finn/source_code/finn.util.rst b/docs/finn/source_code/finn.util.rst index 7ba3b252abfa0086a8c0281eb9a792fb239d6ec3..aebd0604f4c555f2b1bc637bc4c3d94b35309722 100644 --- a/docs/finn/source_code/finn.util.rst +++ b/docs/finn/source_code/finn.util.rst @@ -99,14 +99,6 @@ finn.util.fpgadataflow :undoc-members: :show-inheritance: -finn.util.gdrive ------------------------------ - -.. automodule:: finn.util.gdrive - :members: - :undoc-members: - :show-inheritance: - finn.util.hls --------------- diff --git a/docs/img/finn-examples-header.png b/docs/img/finn-examples-header.png deleted file mode 100644 index 50f8fa7761e10a958ed3567f268ef675cf1814f7..0000000000000000000000000000000000000000 Binary files a/docs/img/finn-examples-header.png and /dev/null differ diff --git a/docs/img/imagenet.jpg b/docs/img/imagenet.jpg deleted file mode 100644 index 5cdd5aa303d9add5fbe6270936da0e152eca0135..0000000000000000000000000000000000000000 Binary files a/docs/img/imagenet.jpg and /dev/null differ diff --git a/fetch-repos.sh b/fetch-repos.sh index 5b060f5bc83b9d9709fc7e34e855543b4690af5a..e039ca9144fbb45e39331045b1fcbc209825ba0d 100755 --- a/fetch-repos.sh +++ b/fetch-repos.sh @@ -27,12 +27,12 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -QONNX_COMMIT="dd35a8ff49d7225a07ffceeebe25a6361df48349" -FINN_EXP_COMMIT="9cbd2787b5160e2b44e0e8164a0df1457dbd5366" -BREVITAS_COMMIT="a5b71d6de1389d3e7db898fef72e014842670f03" +QONNX_COMMIT="20a34289cf2297d2b2bbbe75d6ac152ece86e3b4" +FINN_EXP_COMMIT="0aa7e1c44b20cf085b6fe42cff360f0a832afd2c" +BREVITAS_COMMIT="c65f9c13dc124971f14739349531bbcda5c2a4aa" PYVERILATOR_COMMIT="766e457465f5c0dd315490d7b9cc5d74f9a76f4f" CNPY_COMMIT="4e8810b1a8637695171ed346ce68f6984e585ef4" -HLSLIB_COMMIT="d27f6b6c5d8f1bb208db395659389603f63ad4be" +HLSLIB_COMMIT="c17aa478ae574971d115afa9fa4d9c215857d1ac" OMX_COMMIT="d1065a788219ca0eb54d5e57600b1f9d7f67d4cc" AVNET_BDF_COMMIT="2d49cfc25766f07792c0b314489f21fe916b639b" XIL_BDF_COMMIT="8cf4bb674a919ac34e3d99d8d71a9e60af93d14e" diff --git a/finn-rtllib/axi_info/component.xml b/finn-rtllib/axi_info/component.xml index d22637534ff1fac1e3659c5dca6620d2f044ab87..c7632e29156fbd9dcbe554089dcc89cc6440f505 100644 --- a/finn-rtllib/axi_info/component.xml +++ b/finn-rtllib/axi_info/component.xml @@ -197,6 +197,10 @@ <spirit:name>ASSOCIATED_BUSIF</spirit:name> <spirit:value spirit:id="BUSIFPARAM_VALUE.AP_CLK.ASSOCIATED_BUSIF">s_axi</spirit:value> </spirit:parameter> + <spirit:parameter> + <spirit:name>FREQ_TOLERANCE_HZ</spirit:name> + <spirit:value spirit:id="BUSIFPARAM_VALUE.AP_CLK.FREQ_TOLERANCE_HZ">-1</spirit:value> + </spirit:parameter> </spirit:parameters> </spirit:busInterface> </spirit:busInterfaces> @@ -228,7 +232,7 @@ <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>7d682dfc</spirit:value> + <spirit:value>c9da9874</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> @@ -244,7 +248,7 @@ <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>7d682dfc</spirit:value> + <spirit:value>c9da9874</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> @@ -258,7 +262,7 @@ <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>e11f9727</spirit:value> + <spirit:value>1e654f67</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> @@ -607,7 +611,7 @@ <spirit:file> <spirit:name>hdl/axi_info_top.sv</spirit:name> <spirit:fileType>systemVerilogSource</spirit:fileType> - <spirit:userFileType>CHECKSUM_ec9ff0da</spirit:userFileType> + <spirit:userFileType>CHECKSUM_db6ccc10</spirit:userFileType> </spirit:file> </spirit:fileSet> <spirit:fileSet> @@ -692,17 +696,22 @@ </xilinx:taxonomies> <xilinx:displayName>axi_info_top_v1_0</xilinx:displayName> <xilinx:definitionSource>package_project</xilinx:definitionSource> - <xilinx:coreRevision>5</xilinx:coreRevision> - <xilinx:coreCreationDateTime>2022-05-30T14:16:13Z</xilinx:coreCreationDateTime> + <xilinx:coreRevision>6</xilinx:coreRevision> + <xilinx:coreCreationDateTime>2023-05-24T06:36:33Z</xilinx:coreCreationDateTime> </xilinx:coreExtensions> <xilinx:packagingInfo> - <xilinx:xilinxVersion>2022.1</xilinx:xilinxVersion> - <xilinx:checksum xilinx:scope="busInterfaces" xilinx:value="919b2cd5"/> + <xilinx:xilinxVersion>2022.2</xilinx:xilinxVersion> + <xilinx:checksum xilinx:scope="busInterfaces" xilinx:value="3233b5e1"/> <xilinx:checksum xilinx:scope="memoryMaps" xilinx:value="c930e363"/> - <xilinx:checksum xilinx:scope="fileGroups" xilinx:value="5ec5459d"/> + <xilinx:checksum xilinx:scope="fileGroups" xilinx:value="28f3ac69"/> <xilinx:checksum xilinx:scope="ports" xilinx:value="bd3646cb"/> <xilinx:checksum xilinx:scope="hdlParameters" xilinx:value="eab94b69"/> <xilinx:checksum xilinx:scope="parameters" xilinx:value="ba692e87"/> + <xilinx:targetDRCs> + <xilinx:targetDRC xilinx:tool="ipi"> + <xilinx:targetDRCOption xilinx:name="ignore_freq_hz" xilinx:value="true"/> + </xilinx:targetDRC> + </xilinx:targetDRCs> </xilinx:packagingInfo> </spirit:vendorExtensions> </spirit:component> diff --git a/finn-rtllib/axi_info/hdl/axi_info_top.sv b/finn-rtllib/axi_info/hdl/axi_info_top.sv index ab2cfc8bed3fcb2308c85a81cd687f56780150cb..74aebe3ec717db7a3f9c485d44a2c883499ff038 100644 --- a/finn-rtllib/axi_info/hdl/axi_info_top.sv +++ b/finn-rtllib/axi_info/hdl/axi_info_top.sv @@ -38,7 +38,10 @@ module axi_info_top #( bit [31:0] CHECKSUM_COUNT )( //- Global Control ------------------ + (* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF s_axi, ASSOCIATED_RESET ap_rst_n" *) + (* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 ap_clk CLK" *) input logic ap_clk, + (* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) input logic ap_rst_n, //- AXI Lite ------------------------ diff --git a/finn-rtllib/memstream/component.xml b/finn-rtllib/memstream/component.xml index 63a8540a76a100201c67d7a1dcbaec15f10e1c0e..722da1d803f089d04af8a8ecb2b2f9eb6479364a 100644 --- a/finn-rtllib/memstream/component.xml +++ b/finn-rtllib/memstream/component.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <spirit:component xmlns:xilinx="http://www.xilinx.com" xmlns:spirit="http://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <spirit:vendor>xilinx.com</spirit:vendor> - <spirit:library>user</spirit:library> + <spirit:vendor>amd.com</spirit:vendor> + <spirit:library>finn</spirit:library> <spirit:name>memstream</spirit:name> <spirit:version>1.0</spirit:version> <spirit:busInterfaces> @@ -37,201 +37,6 @@ </spirit:portMap> </spirit:portMaps> </spirit:busInterface> - <spirit:busInterface> - <spirit:name>m_axis_1</spirit:name> - <spirit:busType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis" spirit:version="1.0"/> - <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis_rtl" spirit:version="1.0"/> - <spirit:master/> - <spirit:portMaps> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TDATA</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_1_tdata</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TVALID</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_1_tvalid</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TREADY</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_1_tready</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - </spirit:portMaps> - <spirit:vendorExtensions> - <xilinx:busInterfaceInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="BUSIF_ENABLEMENT.m_axis_1" xilinx:dependency="$NSTREAMS>=2">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:busInterfaceInfo> - </spirit:vendorExtensions> - </spirit:busInterface> - <spirit:busInterface> - <spirit:name>m_axis_2</spirit:name> - <spirit:busType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis" spirit:version="1.0"/> - <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis_rtl" spirit:version="1.0"/> - <spirit:master/> - <spirit:portMaps> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TDATA</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_2_tdata</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TVALID</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_2_tvalid</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TREADY</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_2_tready</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - </spirit:portMaps> - <spirit:vendorExtensions> - <xilinx:busInterfaceInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="BUSIF_ENABLEMENT.m_axis_2" xilinx:dependency="$NSTREAMS>=3">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:busInterfaceInfo> - </spirit:vendorExtensions> - </spirit:busInterface> - <spirit:busInterface> - <spirit:name>m_axis_3</spirit:name> - <spirit:busType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis" spirit:version="1.0"/> - <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis_rtl" spirit:version="1.0"/> - <spirit:master/> - <spirit:portMaps> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TDATA</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_3_tdata</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TVALID</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_3_tvalid</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TREADY</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_3_tready</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - </spirit:portMaps> - <spirit:vendorExtensions> - <xilinx:busInterfaceInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="BUSIF_ENABLEMENT.m_axis_3" xilinx:dependency="$NSTREAMS>=4">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:busInterfaceInfo> - </spirit:vendorExtensions> - </spirit:busInterface> - <spirit:busInterface> - <spirit:name>m_axis_4</spirit:name> - <spirit:busType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis" spirit:version="1.0"/> - <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis_rtl" spirit:version="1.0"/> - <spirit:master/> - <spirit:portMaps> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TDATA</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_4_tdata</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TVALID</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_4_tvalid</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TREADY</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_4_tready</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - </spirit:portMaps> - <spirit:vendorExtensions> - <xilinx:busInterfaceInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="BUSIF_ENABLEMENT.m_axis_4" xilinx:dependency="$NSTREAMS>=5">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:busInterfaceInfo> - </spirit:vendorExtensions> - </spirit:busInterface> - <spirit:busInterface> - <spirit:name>m_axis_5</spirit:name> - <spirit:busType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis" spirit:version="1.0"/> - <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="axis_rtl" spirit:version="1.0"/> - <spirit:master/> - <spirit:portMaps> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TDATA</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_5_tdata</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TVALID</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_5_tvalid</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - <spirit:portMap> - <spirit:logicalPort> - <spirit:name>TREADY</spirit:name> - </spirit:logicalPort> - <spirit:physicalPort> - <spirit:name>m_axis_5_tready</spirit:name> - </spirit:physicalPort> - </spirit:portMap> - </spirit:portMaps> - <spirit:vendorExtensions> - <xilinx:busInterfaceInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="BUSIF_ENABLEMENT.m_axis_5" xilinx:dependency="$NSTREAMS = 6">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:busInterfaceInfo> - </spirit:vendorExtensions> - </spirit:busInterface> <spirit:busInterface> <spirit:name>s_axilite</spirit:name> <spirit:busType spirit:vendor="xilinx.com" spirit:library="interface" spirit:name="aximm" spirit:version="1.0"/> @@ -393,16 +198,9 @@ </spirit:physicalPort> </spirit:portMap> </spirit:portMaps> - <spirit:vendorExtensions> - <xilinx:busInterfaceInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="BUSIF_ENABLEMENT.s_axilite" xilinx:dependency="$CONFIG_EN = 1">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:busInterfaceInfo> - </spirit:vendorExtensions> </spirit:busInterface> <spirit:busInterface> - <spirit:name>aresetn</spirit:name> + <spirit:name>ap_rst_n</spirit:name> <spirit:busType spirit:vendor="xilinx.com" spirit:library="signal" spirit:name="reset" spirit:version="1.0"/> <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="signal" spirit:name="reset_rtl" spirit:version="1.0"/> <spirit:slave/> @@ -412,19 +210,19 @@ <spirit:name>RST</spirit:name> </spirit:logicalPort> <spirit:physicalPort> - <spirit:name>aresetn</spirit:name> + <spirit:name>ap_rst_n</spirit:name> </spirit:physicalPort> </spirit:portMap> </spirit:portMaps> <spirit:parameters> <spirit:parameter> <spirit:name>POLARITY</spirit:name> - <spirit:value spirit:id="BUSIFPARAM_VALUE.ARESETN.POLARITY" spirit:choiceRef="choice_list_9d8b0d81">ACTIVE_LOW</spirit:value> + <spirit:value spirit:id="BUSIFPARAM_VALUE.AP_RST_N.POLARITY" spirit:choiceRef="choice_list_9d8b0d81">ACTIVE_LOW</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:busInterface> <spirit:busInterface> - <spirit:name>aclk</spirit:name> + <spirit:name>ap_clk</spirit:name> <spirit:busType spirit:vendor="xilinx.com" spirit:library="signal" spirit:name="clock" spirit:version="1.0"/> <spirit:abstractionType spirit:vendor="xilinx.com" spirit:library="signal" spirit:name="clock_rtl" spirit:version="1.0"/> <spirit:slave/> @@ -434,18 +232,22 @@ <spirit:name>CLK</spirit:name> </spirit:logicalPort> <spirit:physicalPort> - <spirit:name>aclk</spirit:name> + <spirit:name>ap_clk</spirit:name> </spirit:physicalPort> </spirit:portMap> </spirit:portMaps> <spirit:parameters> + <spirit:parameter> + <spirit:name>ASSOCIATED_RESET</spirit:name> + <spirit:value spirit:id="BUSIFPARAM_VALUE.AP_CLK.ASSOCIATED_RESET">ap_rst_n</spirit:value> + </spirit:parameter> <spirit:parameter> <spirit:name>ASSOCIATED_BUSIF</spirit:name> - <spirit:value spirit:id="BUSIFPARAM_VALUE.ACLK.ASSOCIATED_BUSIF">m_axis_0:m_axis_1:m_axis_2:m_axis_3:m_axis_4:m_axis_5:s_axilite</spirit:value> + <spirit:value spirit:id="BUSIFPARAM_VALUE.AP_CLK.ASSOCIATED_BUSIF">m_axis_0:s_axilite</spirit:value> </spirit:parameter> <spirit:parameter> - <spirit:name>ASSOCIATED_RESET</spirit:name> - <spirit:value spirit:id="BUSIFPARAM_VALUE.ACLK.ASSOCIATED_RESET">aresetn</spirit:value> + <spirit:name>FREQ_TOLERANCE_HZ</spirit:name> + <spirit:value spirit:id="BUSIFPARAM_VALUE.AP_CLK.FREQ_TOLERANCE_HZ">-1</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:busInterface> @@ -453,11 +255,13 @@ <spirit:memoryMaps> <spirit:memoryMap> <spirit:name>interface_aximm</spirit:name> + <spirit:displayName>interface_aximm</spirit:displayName> <spirit:addressBlock> <spirit:name>reg0</spirit:name> - <spirit:baseAddress spirit:format="bitString" spirit:resolve="user" spirit:bitStringLength="32">0</spirit:baseAddress> - <spirit:range spirit:format="long" spirit:resolve="dependent" spirit:dependency="pow(2,(spirit:decode(id('MODELPARAM_VALUE.AXILITE_ADDR_WIDTH')) - 1) + 1)" spirit:minimum="4096" spirit:rangeType="long">65536</spirit:range> - <spirit:width spirit:format="long" spirit:resolve="user">32</spirit:width> + <spirit:displayName>reg0</spirit:displayName> + <spirit:baseAddress spirit:format="bitString" spirit:bitStringLength="1">0x0</spirit:baseAddress> + <spirit:range spirit:format="long" spirit:resolve="dependent" spirit:dependency="pow(2,(spirit:decode(id('MODELPARAM_VALUE.AXILITE_ADDR_WIDTH')) - 1) - 0 + 1)" spirit:minimum="4096" spirit:rangeType="long">4096</spirit:range> + <spirit:width spirit:format="long">32</spirit:width> <spirit:usage>register</spirit:usage> </spirit:addressBlock> </spirit:memoryMap> @@ -468,15 +272,15 @@ <spirit:name>xilinx_anylanguagesynthesis</spirit:name> <spirit:displayName>Synthesis</spirit:displayName> <spirit:envIdentifier>:vivado.xilinx.com:synthesis</spirit:envIdentifier> - <spirit:language>Verilog</spirit:language> - <spirit:modelName>memstream</spirit:modelName> + <spirit:language>SystemVerilog</spirit:language> + <spirit:modelName>memstream_axi_wrapper</spirit:modelName> <spirit:fileSetRef> <spirit:localName>xilinx_anylanguagesynthesis_view_fileset</spirit:localName> </spirit:fileSetRef> <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>1fc5a310</spirit:value> + <spirit:value>04464096</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> @@ -484,15 +288,27 @@ <spirit:name>xilinx_anylanguagebehavioralsimulation</spirit:name> <spirit:displayName>Simulation</spirit:displayName> <spirit:envIdentifier>:vivado.xilinx.com:simulation</spirit:envIdentifier> - <spirit:language>Verilog</spirit:language> - <spirit:modelName>memstream</spirit:modelName> + <spirit:language>SystemVerilog</spirit:language> + <spirit:modelName>memstream_axi_wrapper</spirit:modelName> <spirit:fileSetRef> <spirit:localName>xilinx_anylanguagebehavioralsimulation_view_fileset</spirit:localName> </spirit:fileSetRef> <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>d02d9990</spirit:value> + <spirit:value>9e058959</spirit:value> + </spirit:parameter> + </spirit:parameters> + </spirit:view> + <spirit:view> + <spirit:name>xilinx_implementation</spirit:name> + <spirit:displayName>Implementation</spirit:displayName> + <spirit:envIdentifier>:vivado.xilinx.com:implementation</spirit:envIdentifier> + <spirit:modelName>memstream_axi_wrapper</spirit:modelName> + <spirit:parameters> + <spirit:parameter> + <spirit:name>viewChecksum</spirit:name> + <spirit:value>cd434062</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> @@ -506,7 +322,7 @@ <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>f960907f</spirit:value> + <spirit:value>6c92393d</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> @@ -520,14 +336,14 @@ <spirit:parameters> <spirit:parameter> <spirit:name>viewChecksum</spirit:name> - <spirit:value>d2aad2c5</spirit:value> + <spirit:value>923e7b90</spirit:value> </spirit:parameter> </spirit:parameters> </spirit:view> </spirit:views> <spirit:ports> <spirit:port> - <spirit:name>aclk</spirit:name> + <spirit:name>ap_clk</spirit:name> <spirit:wire> <spirit:direction>in</spirit:direction> <spirit:wireTypeDefs> @@ -540,7 +356,7 @@ </spirit:wire> </spirit:port> <spirit:port> - <spirit:name>aresetn</spirit:name> + <spirit:name>ap_rst_n</spirit:name> <spirit:wire> <spirit:direction>in</spirit:direction> <spirit:wireTypeDefs> @@ -582,11 +398,11 @@ </spirit:wire> </spirit:port> <spirit:port> - <spirit:name>awaddr</spirit:name> + <spirit:name>awprot</spirit:name> <spirit:wire> <spirit:direction>in</spirit:direction> <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="(spirit:decode(id('MODELPARAM_VALUE.AXILITE_ADDR_WIDTH')) - 1)">15</spirit:left> + <spirit:left spirit:format="long">2</spirit:left> <spirit:right spirit:format="long">0</spirit:right> </spirit:vector> <spirit:wireTypeDefs> @@ -602,11 +418,11 @@ </spirit:wire> </spirit:port> <spirit:port> - <spirit:name>awprot</spirit:name> + <spirit:name>awaddr</spirit:name> <spirit:wire> <spirit:direction>in</spirit:direction> <spirit:vector> - <spirit:left spirit:format="long">2</spirit:left> + <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="(spirit:decode(id('MODELPARAM_VALUE.AXILITE_ADDR_WIDTH')) - 1)">10</spirit:left> <spirit:right spirit:format="long">0</spirit:right> </spirit:vector> <spirit:wireTypeDefs> @@ -766,11 +582,11 @@ </spirit:wire> </spirit:port> <spirit:port> - <spirit:name>araddr</spirit:name> + <spirit:name>arprot</spirit:name> <spirit:wire> <spirit:direction>in</spirit:direction> <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="(spirit:decode(id('MODELPARAM_VALUE.AXILITE_ADDR_WIDTH')) - 1)">15</spirit:left> + <spirit:left spirit:format="long">2</spirit:left> <spirit:right spirit:format="long">0</spirit:right> </spirit:vector> <spirit:wireTypeDefs> @@ -786,11 +602,11 @@ </spirit:wire> </spirit:port> <spirit:port> - <spirit:name>arprot</spirit:name> + <spirit:name>araddr</spirit:name> <spirit:wire> <spirit:direction>in</spirit:direction> <spirit:vector> - <spirit:left spirit:format="long">2</spirit:left> + <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="(spirit:decode(id('MODELPARAM_VALUE.AXILITE_ADDR_WIDTH')) - 1)">10</spirit:left> <spirit:right spirit:format="long">0</spirit:right> </spirit:vector> <spirit:wireTypeDefs> @@ -868,29 +684,6 @@ </spirit:wireTypeDefs> </spirit:wire> </spirit:port> - <spirit:port> - <spirit:name>m_axis_0_afull</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">0</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - <spirit:vendorExtensions> - <xilinx:portInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="PORT_ENABLEMENT.m_axis_0_afull" xilinx:dependency="$NSTREAMS>2">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:portInfo> - </spirit:vendorExtensions> - </spirit:port> <spirit:port> <spirit:name>m_axis_0_tready</spirit:name> <spirit:wire> @@ -925,352 +718,7 @@ <spirit:wire> <spirit:direction>out</spirit:direction> <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.STRM0_WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> - <spirit:right spirit:format="long">0</spirit:right> - </spirit:vector> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic_vector</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_1_afull</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">0</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - <spirit:vendorExtensions> - <xilinx:portInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="PORT_ENABLEMENT.m_axis_1_afull" xilinx:dependency="$NSTREAMS>2">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:portInfo> - </spirit:vendorExtensions> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_1_tready</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">1</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_1_tvalid</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_1_tdata</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.STRM1_WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> - <spirit:right spirit:format="long">0</spirit:right> - </spirit:vector> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic_vector</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_2_afull</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">0</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - <spirit:vendorExtensions> - <xilinx:portInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="PORT_ENABLEMENT.m_axis_2_afull" xilinx:dependency="$NSTREAMS>=3">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:portInfo> - </spirit:vendorExtensions> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_2_tready</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">1</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_2_tvalid</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_2_tdata</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.STRM2_WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> - <spirit:right spirit:format="long">0</spirit:right> - </spirit:vector> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic_vector</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_3_afull</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">0</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - <spirit:vendorExtensions> - <xilinx:portInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="PORT_ENABLEMENT.m_axis_3_afull" xilinx:dependency="$NSTREAMS>=4">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:portInfo> - </spirit:vendorExtensions> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_3_tready</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">1</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_3_tvalid</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_3_tdata</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.STRM3_WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> - <spirit:right spirit:format="long">0</spirit:right> - </spirit:vector> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic_vector</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_4_afull</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">0</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - <spirit:vendorExtensions> - <xilinx:portInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="PORT_ENABLEMENT.m_axis_4_afull" xilinx:dependency="$NSTREAMS>=5">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:portInfo> - </spirit:vendorExtensions> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_4_tready</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">1</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_4_tvalid</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_4_tdata</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.STRM4_WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> - <spirit:right spirit:format="long">0</spirit:right> - </spirit:vector> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic_vector</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_5_afull</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">0</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - <spirit:vendorExtensions> - <xilinx:portInfo> - <xilinx:enablement> - <xilinx:isEnabled xilinx:resolve="dependent" xilinx:id="PORT_ENABLEMENT.m_axis_5_afull" xilinx:dependency="$NSTREAMS = 6">true</xilinx:isEnabled> - </xilinx:enablement> - </xilinx:portInfo> - </spirit:vendorExtensions> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_5_tready</spirit:name> - <spirit:wire> - <spirit:direction>in</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - <spirit:driver> - <spirit:defaultValue spirit:format="long">1</spirit:defaultValue> - </spirit:driver> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_5_tvalid</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:wireTypeDefs> - <spirit:wireTypeDef> - <spirit:typeName>std_logic</spirit:typeName> - <spirit:viewNameRef>xilinx_anylanguagesynthesis</spirit:viewNameRef> - <spirit:viewNameRef>xilinx_anylanguagebehavioralsimulation</spirit:viewNameRef> - </spirit:wireTypeDef> - </spirit:wireTypeDefs> - </spirit:wire> - </spirit:port> - <spirit:port> - <spirit:name>m_axis_5_tdata</spirit:name> - <spirit:wire> - <spirit:direction>out</spirit:direction> - <spirit:vector> - <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.STRM5_WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> + <spirit:left spirit:format="long" spirit:resolve="dependent" spirit:dependency="((((spirit:decode(id('MODELPARAM_VALUE.WIDTH')) + 7) / 8) * 8) - 1)">31</spirit:left> <spirit:right spirit:format="long">0</spirit:right> </spirit:vector> <spirit:wireTypeDefs> @@ -1285,129 +733,29 @@ </spirit:ports> <spirit:modelParameters> <spirit:modelParameter xsi:type="spirit:nameValueTypeType" spirit:dataType="integer"> - <spirit:name>CONFIG_EN</spirit:name> - <spirit:displayName>Config En</spirit:displayName> - <spirit:value spirit:format="bool" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.CONFIG_EN">true</spirit:value> + <spirit:name>DEPTH</spirit:name> + <spirit:displayName>Depth</spirit:displayName> + <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.DEPTH">512</spirit:value> </spirit:modelParameter> <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>NSTREAMS</spirit:name> - <spirit:displayName>Nstreams</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.NSTREAMS">6</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>MEM_DEPTH</spirit:name> - <spirit:displayName>Mem Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.MEM_DEPTH">13824</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>MEM_WIDTH</spirit:name> - <spirit:displayName>Mem Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.MEM_WIDTH">32</spirit:value> + <spirit:name>WIDTH</spirit:name> + <spirit:displayName>Width</spirit:displayName> + <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.WIDTH">32</spirit:value> </spirit:modelParameter> <spirit:modelParameter spirit:dataType="string"> - <spirit:name>MEM_INIT</spirit:name> - <spirit:displayName>Mem Init</spirit:displayName> - <spirit:value spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.MEM_INIT">./</spirit:value> + <spirit:name>INIT_FILE</spirit:name> + <spirit:displayName>Init File</spirit:displayName> + <spirit:value spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.INIT_FILE"/> </spirit:modelParameter> <spirit:modelParameter spirit:dataType="string"> <spirit:name>RAM_STYLE</spirit:name> <spirit:displayName>Ram Style</spirit:displayName> <spirit:value spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.RAM_STYLE">auto</spirit:value> </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM0_WIDTH</spirit:name> - <spirit:displayName>Strm0 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM0_WIDTH">32</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM1_WIDTH</spirit:name> - <spirit:displayName>Strm1 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM1_WIDTH">32</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM2_WIDTH</spirit:name> - <spirit:displayName>Strm2 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM2_WIDTH">32</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM3_WIDTH</spirit:name> - <spirit:displayName>Strm3 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM3_WIDTH">32</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM4_WIDTH</spirit:name> - <spirit:displayName>Strm4 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM4_WIDTH">32</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM5_WIDTH</spirit:name> - <spirit:displayName>Strm5 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM5_WIDTH">32</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM0_DEPTH</spirit:name> - <spirit:displayName>Strm0 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM0_DEPTH">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM1_DEPTH</spirit:name> - <spirit:displayName>Strm1 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM1_DEPTH">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM2_DEPTH</spirit:name> - <spirit:displayName>Strm2 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM2_DEPTH">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM3_DEPTH</spirit:name> - <spirit:displayName>Strm3 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM3_DEPTH">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM4_DEPTH</spirit:name> - <spirit:displayName>Strm4 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM4_DEPTH">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM5_DEPTH</spirit:name> - <spirit:displayName>Strm5 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM5_DEPTH">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM0_OFFSET</spirit:name> - <spirit:displayName>Strm0 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM0_OFFSET">0</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM1_OFFSET</spirit:name> - <spirit:displayName>Strm1 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM1_OFFSET">2304</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM2_OFFSET</spirit:name> - <spirit:displayName>Strm2 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM2_OFFSET">4608</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM3_OFFSET</spirit:name> - <spirit:displayName>Strm3 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM3_OFFSET">6912</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM4_OFFSET</spirit:name> - <spirit:displayName>Strm4 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM4_OFFSET">9216</spirit:value> - </spirit:modelParameter> - <spirit:modelParameter spirit:dataType="integer"> - <spirit:name>STRM5_OFFSET</spirit:name> - <spirit:displayName>Strm5 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.STRM5_OFFSET">11520</spirit:value> - </spirit:modelParameter> <spirit:modelParameter spirit:dataType="integer"> <spirit:name>AXILITE_ADDR_WIDTH</spirit:name> <spirit:displayName>Axilite Addr Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.AXILITE_ADDR_WIDTH" spirit:dependency="(2 + spirit:ceil(spirit:log(2,(spirit:decode(id('MODELPARAM_VALUE.MEM_DEPTH')) * (1 << spirit:ceil(spirit:log(2,((spirit:decode(id('MODELPARAM_VALUE.MEM_WIDTH')) + 31) / 32))))))))">16</spirit:value> + <spirit:value spirit:format="long" spirit:resolve="generated" spirit:id="MODELPARAM_VALUE.AXILITE_ADDR_WIDTH" spirit:dependency="(spirit:ceil(spirit:log(2,(spirit:decode(id('MODELPARAM_VALUE.DEPTH')) * (2 ** spirit:ceil(spirit:log(2,((spirit:decode(id('MODELPARAM_VALUE.WIDTH')) + 31) / 32))))))) + 2)">11</spirit:value> </spirit:modelParameter> </spirit:modelParameters> </spirit:model> @@ -1417,13 +765,6 @@ <spirit:enumeration>ACTIVE_HIGH</spirit:enumeration> <spirit:enumeration>ACTIVE_LOW</spirit:enumeration> </spirit:choice> - <spirit:choice> - <spirit:name>choice_list_e2bd1cd0</spirit:name> - <spirit:enumeration>auto</spirit:enumeration> - <spirit:enumeration>distributed</spirit:enumeration> - <spirit:enumeration>block</spirit:enumeration> - <spirit:enumeration>ultra</spirit:enumeration> - </spirit:choice> </spirit:choices> <spirit:fileSets> <spirit:fileSet> @@ -1433,71 +774,41 @@ <spirit:fileType>verilogSource</spirit:fileType> </spirit:file> <spirit:file> - <spirit:name>hdl/memstream.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> + <spirit:name>hdl/memstream.sv</spirit:name> + <spirit:fileType>systemVerilogSource</spirit:fileType> </spirit:file> <spirit:file> - <spirit:name>hdl/memstream_multiblock.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> + <spirit:name>hdl/memstream_axi.sv</spirit:name> + <spirit:fileType>systemVerilogSource</spirit:fileType> </spirit:file> <spirit:file> - <spirit:name>hdl/memstream_singleblock.v</spirit:name> + <spirit:name>hdl/memstream_axi_wrapper.v</spirit:name> <spirit:fileType>verilogSource</spirit:fileType> - </spirit:file> - <spirit:file> - <spirit:name>hdl/mux.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> - </spirit:file> - <spirit:file> - <spirit:name>hdl/ramb18_sdp.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> - </spirit:file> - <spirit:file> - <spirit:name>hdl/ramb18_wf_dualport.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> - <spirit:userFileType>CHECKSUM_9425c051</spirit:userFileType> + <spirit:userFileType>CHECKSUM_7caabca7</spirit:userFileType> </spirit:file> </spirit:fileSet> <spirit:fileSet> <spirit:name>xilinx_anylanguagebehavioralsimulation_view_fileset</spirit:name> <spirit:file> - <spirit:name>hdl/memstream.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> + <spirit:name>hdl/memstream.sv</spirit:name> + <spirit:fileType>systemVerilogSource</spirit:fileType> <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> <spirit:logicalName>xil_defaultlib</spirit:logicalName> </spirit:file> <spirit:file> - <spirit:name>hdl/axilite_if.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> + <spirit:name>hdl/memstream_axi.sv</spirit:name> + <spirit:fileType>systemVerilogSource</spirit:fileType> <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> <spirit:logicalName>xil_defaultlib</spirit:logicalName> </spirit:file> <spirit:file> - <spirit:name>hdl/memstream_singleblock.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> - <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> - <spirit:logicalName>xil_defaultlib</spirit:logicalName> - </spirit:file> - <spirit:file> - <spirit:name>hdl/mux.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> - <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> - <spirit:logicalName>xil_defaultlib</spirit:logicalName> - </spirit:file> - <spirit:file> - <spirit:name>hdl/ramb18_wf_dualport.v</spirit:name> - <spirit:fileType>verilogSource</spirit:fileType> - <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> - <spirit:logicalName>xil_defaultlib</spirit:logicalName> - </spirit:file> - <spirit:file> - <spirit:name>hdl/memstream_multiblock.v</spirit:name> + <spirit:name>hdl/axilite_if.v</spirit:name> <spirit:fileType>verilogSource</spirit:fileType> <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> <spirit:logicalName>xil_defaultlib</spirit:logicalName> </spirit:file> <spirit:file> - <spirit:name>hdl/ramb18_sdp.v</spirit:name> + <spirit:name>hdl/memstream_axi_wrapper.v</spirit:name> <spirit:fileType>verilogSource</spirit:fileType> <spirit:userFileType>USED_IN_ipstatic</spirit:userFileType> <spirit:logicalName>xil_defaultlib</spirit:logicalName> @@ -1508,7 +819,7 @@ <spirit:file> <spirit:name>xgui/memstream_v1_0.tcl</spirit:name> <spirit:fileType>tclSource</spirit:fileType> - <spirit:userFileType>CHECKSUM_f960907f</spirit:userFileType> + <spirit:userFileType>CHECKSUM_32cad48d</spirit:userFileType> <spirit:userFileType>XGUI_VERSION_2</spirit:userFileType> </spirit:file> </spirit:fileSet> @@ -1520,132 +831,32 @@ </spirit:file> </spirit:fileSet> </spirit:fileSets> - <spirit:description>memstream_v1_0</spirit:description> + <spirit:description>memstream</spirit:description> <spirit:parameters> <spirit:parameter> - <spirit:name>CONFIG_EN</spirit:name> - <spirit:displayName>Config En</spirit:displayName> - <spirit:value spirit:format="bool" spirit:resolve="user" spirit:id="PARAM_VALUE.CONFIG_EN">true</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>NSTREAMS</spirit:name> - <spirit:displayName>Nstreams</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.NSTREAMS" spirit:minimum="1" spirit:maximum="6" spirit:rangeType="long">6</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>MEM_DEPTH</spirit:name> - <spirit:displayName>Mem Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.MEM_DEPTH">13824</spirit:value> + <spirit:name>DEPTH</spirit:name> + <spirit:displayName>Depth</spirit:displayName> + <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.DEPTH">512</spirit:value> </spirit:parameter> <spirit:parameter> - <spirit:name>MEM_WIDTH</spirit:name> - <spirit:displayName>Mem Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.MEM_WIDTH">32</spirit:value> + <spirit:name>WIDTH</spirit:name> + <spirit:displayName>Width</spirit:displayName> + <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.WIDTH">32</spirit:value> </spirit:parameter> <spirit:parameter> - <spirit:name>MEM_INIT</spirit:name> - <spirit:displayName>Mem Init</spirit:displayName> - <spirit:value spirit:resolve="user" spirit:id="PARAM_VALUE.MEM_INIT">./</spirit:value> + <spirit:name>INIT_FILE</spirit:name> + <spirit:displayName>Init File</spirit:displayName> + <spirit:value spirit:resolve="user" spirit:id="PARAM_VALUE.INIT_FILE"/> </spirit:parameter> <spirit:parameter> <spirit:name>RAM_STYLE</spirit:name> <spirit:displayName>Ram Style</spirit:displayName> - <spirit:value spirit:resolve="user" spirit:id="PARAM_VALUE.RAM_STYLE" spirit:choiceRef="choice_list_e2bd1cd0">auto</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM0_WIDTH</spirit:name> - <spirit:displayName>Strm0 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM0_WIDTH">32</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM1_WIDTH</spirit:name> - <spirit:displayName>Strm1 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM1_WIDTH">32</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM2_WIDTH</spirit:name> - <spirit:displayName>Strm2 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM2_WIDTH">32</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM3_WIDTH</spirit:name> - <spirit:displayName>Strm3 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM3_WIDTH">32</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM4_WIDTH</spirit:name> - <spirit:displayName>Strm4 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM4_WIDTH">32</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM5_WIDTH</spirit:name> - <spirit:displayName>Strm5 Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM5_WIDTH">32</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM0_DEPTH</spirit:name> - <spirit:displayName>Strm0 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM0_DEPTH">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM1_DEPTH</spirit:name> - <spirit:displayName>Strm1 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM1_DEPTH">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM2_DEPTH</spirit:name> - <spirit:displayName>Strm2 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM2_DEPTH">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM3_DEPTH</spirit:name> - <spirit:displayName>Strm3 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM3_DEPTH">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM4_DEPTH</spirit:name> - <spirit:displayName>Strm4 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM4_DEPTH">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM5_DEPTH</spirit:name> - <spirit:displayName>Strm5 Depth</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM5_DEPTH">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM0_OFFSET</spirit:name> - <spirit:displayName>Strm0 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM0_OFFSET">0</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM1_OFFSET</spirit:name> - <spirit:displayName>Strm1 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM1_OFFSET">2304</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM2_OFFSET</spirit:name> - <spirit:displayName>Strm2 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM2_OFFSET">4608</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM3_OFFSET</spirit:name> - <spirit:displayName>Strm3 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM3_OFFSET">6912</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM4_OFFSET</spirit:name> - <spirit:displayName>Strm4 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM4_OFFSET">9216</spirit:value> - </spirit:parameter> - <spirit:parameter> - <spirit:name>STRM5_OFFSET</spirit:name> - <spirit:displayName>Strm5 Offset</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.STRM5_OFFSET">11520</spirit:value> + <spirit:value spirit:resolve="user" spirit:id="PARAM_VALUE.RAM_STYLE">auto</spirit:value> </spirit:parameter> <spirit:parameter> <spirit:name>AXILITE_ADDR_WIDTH</spirit:name> <spirit:displayName>Axilite Addr Width</spirit:displayName> - <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.AXILITE_ADDR_WIDTH">16</spirit:value> + <spirit:value spirit:format="long" spirit:resolve="user" spirit:id="PARAM_VALUE.AXILITE_ADDR_WIDTH">11</spirit:value> <spirit:vendorExtensions> <xilinx:parameterInfo> <xilinx:enablement> @@ -1656,52 +867,40 @@ </spirit:parameter> <spirit:parameter> <spirit:name>Component_Name</spirit:name> - <spirit:value spirit:resolve="user" spirit:id="PARAM_VALUE.Component_Name" spirit:order="1">memstream_v1_0</spirit:value> + <spirit:value spirit:resolve="user" spirit:id="PARAM_VALUE.Component_Name" spirit:order="1">memstream_axi_wrapper_v1_0</spirit:value> </spirit:parameter> </spirit:parameters> <spirit:vendorExtensions> <xilinx:coreExtensions> - <xilinx:supportedFamilies> - <xilinx:family xilinx:lifeCycle="Beta">aartix7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Beta">akintex7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Beta">artix7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Beta">artix7l</xilinx:family> - <xilinx:family xilinx:lifeCycle="Beta">azynq</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">kintex7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">kintex7l</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">kintexu</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">kintexuplus</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">qkintex7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">qkintex7l</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">qvirtex7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">qzynq</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">qzynqplus</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">versal</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">versalprime</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">virtex7</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">virtexu</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">virtexuplus</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">virtexuplusHBM</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">virtexupluse58g</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">zynq</xilinx:family> - <xilinx:family xilinx:lifeCycle="Production">zynquplus</xilinx:family> - </xilinx:supportedFamilies> <xilinx:taxonomies> <xilinx:taxonomy>/UserIP</xilinx:taxonomy> </xilinx:taxonomies> - <xilinx:displayName>memstream_v1_0</xilinx:displayName> + <xilinx:displayName>memstream</xilinx:displayName> + <xilinx:autoFamilySupportLevel>level_1</xilinx:autoFamilySupportLevel> <xilinx:definitionSource>package_project</xilinx:definitionSource> + <xilinx:vendorDisplayName>AMD</xilinx:vendorDisplayName> <xilinx:coreRevision>5</xilinx:coreRevision> - <xilinx:coreCreationDateTime>2020-10-09T15:31:57Z</xilinx:coreCreationDateTime> + <xilinx:upgrades> + <xilinx:canUpgradeFrom>user.org:user:memstream_axi_wrapper:1.0</xilinx:canUpgradeFrom> + </xilinx:upgrades> + <xilinx:coreCreationDateTime>2023-05-24T06:34:57Z</xilinx:coreCreationDateTime> + <xilinx:tags> + <xilinx:tag xilinx:name="nopcore"/> + </xilinx:tags> </xilinx:coreExtensions> <xilinx:packagingInfo> - <xilinx:xilinxVersion>2020.1</xilinx:xilinxVersion> - <xilinx:checksum xilinx:scope="busInterfaces" xilinx:value="8f86a494"/> - <xilinx:checksum xilinx:scope="memoryMaps" xilinx:value="5a080bee"/> - <xilinx:checksum xilinx:scope="fileGroups" xilinx:value="d633e93f"/> - <xilinx:checksum xilinx:scope="ports" xilinx:value="2e562330"/> - <xilinx:checksum xilinx:scope="hdlParameters" xilinx:value="134f154d"/> - <xilinx:checksum xilinx:scope="parameters" xilinx:value="83e5a517"/> + <xilinx:xilinxVersion>2022.2</xilinx:xilinxVersion> + <xilinx:checksum xilinx:scope="busInterfaces" xilinx:value="aace24af"/> + <xilinx:checksum xilinx:scope="memoryMaps" xilinx:value="b683eac1"/> + <xilinx:checksum xilinx:scope="fileGroups" xilinx:value="7304ec2c"/> + <xilinx:checksum xilinx:scope="ports" xilinx:value="8c876e99"/> + <xilinx:checksum xilinx:scope="hdlParameters" xilinx:value="6488ba6f"/> + <xilinx:checksum xilinx:scope="parameters" xilinx:value="10eb550f"/> + <xilinx:targetDRCs> + <xilinx:targetDRC xilinx:tool="ipi"> + <xilinx:targetDRCOption xilinx:name="ignore_freq_hz" xilinx:value="true"/> + </xilinx:targetDRC> + </xilinx:targetDRCs> </xilinx:packagingInfo> </spirit:vendorExtensions> </spirit:component> diff --git a/finn-rtllib/memstream/gui/memstream_v1_0.gtcl b/finn-rtllib/memstream/gui/memstream_v1_0.gtcl index a68b85e1f5bdb3dc102f14eb4e3b8d82a86fa556..271f9df453265a7d21c36887300d1d60d3f4f4d0 100644 --- a/finn-rtllib/memstream/gui/memstream_v1_0.gtcl +++ b/finn-rtllib/memstream/gui/memstream_v1_0.gtcl @@ -1,2 +1,2 @@ # This file is automatically written. Do not modify. -proc gen_USERPARAMETER_AXILITE_ADDR_WIDTH_VALUE {MEM_DEPTH MEM_WIDTH } {expr 2+ceil(log($MEM_DEPTH*pow(2,ceil(log(($MEM_WIDTH+31)/32)/log(2))))/log(2))} +proc gen_USERPARAMETER_AXILITE_ADDR_WIDTH_VALUE {DEPTH WIDTH } {expr 2 + ceil(log($DEPTH*pow(2, ceil(log(($WIDTH+31)/32)/log(2))))/log(2))} diff --git a/finn-rtllib/memstream/hdl/memstream.sv b/finn-rtllib/memstream/hdl/memstream.sv new file mode 100644 index 0000000000000000000000000000000000000000..9cbef493a3e57f3c7e8caeb310d54281799ef6c9 --- /dev/null +++ b/finn-rtllib/memstream/hdl/memstream.sv @@ -0,0 +1,176 @@ +/** + * Copyright (c) 2023, Xilinx + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of FINN nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Thomas B. Preußer <thomas.preusser@amd.com> + */ + +module memstream #( + int unsigned DEPTH, + int unsigned WIDTH, + + parameter INIT_FILE = "", + parameter RAM_STYLE = "auto" +)( + input logic clk, + input logic rst, + + // Configuration and readback interface - compatible with ap_memory + input logic config_ce, + input logic config_we, + input logic [31 :0] config_address, + input logic [WIDTH-1:0] config_d0, + + output logic config_rack, + output logic [WIDTH-1:0] config_q0, + + // Continuous output stream + input logic ordy, + output logic ovld, + output logic [WIDTH-1:0] odat +); + + typedef logic [$clog2(DEPTH)-1:0] addr_t; + typedef logic [WIDTH -1:0] data_t; + + uwire en; // Pipeline enable + uwire rollback; // Rollback stream reads if backpressure would block read back + + // Counter with pre-computed last indication for val == DEPTH-1 + typedef struct { + addr_t val; + logic lst; + } ptr_t; + + // Counter history to facilitate pipeline rollback + ptr_t Ptr[3] = '{ + 0: '{ val: 0, lst: DEPTH<2 }, + default: '{ default: 'x } + }; + + //----------------------------------------------------------------------- + // Stage #0: Address & Op + logic Wr1 = 0; // Write + logic Rb1 = 0; // Read back + logic Rs1 = 0; // Read stream + data_t Data1 = 'x; + if(1) begin : blkStage1 + // Increment for wrapping DEPTH-1 back to zero + localparam int unsigned WRAP_INC = 2**$bits(addr_t) - DEPTH + 1; + + uwire ptr_t ptr_eff = rollback? Ptr[2] : Ptr[0]; + uwire ptr_t ptr_nxt; + assign ptr_nxt.val = ptr_eff.val + (config_ce? 0 : !ptr_eff.lst? 1 : WRAP_INC); + assign ptr_nxt.lst = + DEPTH < 2? 1 : + config_ce? ptr_eff.lst : + ptr_eff.lst? 0 : + /* else */ ptr_eff.val == DEPTH-2; + + always_ff @(posedge clk) begin + if(rst) Ptr[0] <= '{ val: 0, lst: DEPTH<2 }; + else if(en) Ptr[0] <= ptr_nxt; + end + + // Issue next Memory Operation + always_ff @(posedge clk) begin + if(rst) begin + Wr1 <= 0; + Rb1 <= 0; + Rs1 <= 0; + Ptr[1] <= '{ default : 'x }; + Data1 <= 'x; + end + else if(en) begin + Wr1 <= 0; + Rb1 <= 0; + Rs1 <= 0; + if(config_ce) begin + if(config_we) Wr1 <= 1; + else Rb1 <= 1; + Ptr[1] <= '{ val: config_address, lst: 'x }; + Data1 <= config_d0; + end + else begin + Rs1 <= 1; + Ptr[1] <= ptr_eff; + Data1 <= 'x; + end + end + end + end : blkStage1 + + //----------------------------------------------------------------------- + // Stage #2: Memory Access + logic Rb2 = 0; + logic Rs2 = 0; + data_t Data2 = 'x; + if(1) begin : blkStage2 + (* RAM_STYLE = RAM_STYLE *) + data_t Mem[DEPTH]; + + // Optional Memory Initialization + if(INIT_FILE != "") initial $readmemh(INIT_FILE, Mem); + + // Execute Memory Operation + uwire addr_t addr = Ptr[1].val; + always_ff @(posedge clk) begin + if(en) begin + if(Wr1) Mem[addr] <= Data1; + Data2 <= Mem[addr]; + end + end + + // Copy Output Designation + always_ff @(posedge clk) begin + if(rst) begin + Rb2 <= 0; + Rs2 <= 0; + Ptr[2] <= '{ default: 'x }; + end + else if(en) begin + Rb2 <= Rb1; + Rs2 <= Rs1 && !rollback; + Ptr[2] <= Ptr[1]; + end + end + end : blkStage2 + + //----------------------------------------------------------------------- + // Output Interfaces + assign config_rack = Rb2; + assign config_q0 = Data2; + + assign ovld = Rs2; + assign odat = Data2; + + uwire backpressure = Rs2 && !ordy; + assign rollback = backpressure && (Rb1 || config_ce); + assign en = !backpressure || Rb1 || config_ce; + +endmodule : memstream diff --git a/finn-rtllib/memstream/hdl/memstream.v b/finn-rtllib/memstream/hdl/memstream.v deleted file mode 100644 index 2cd955f8d1ff0dd5dfed8b004df7053573a43488..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/hdl/memstream.v +++ /dev/null @@ -1,327 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -module memstream -#( -//parameters to enable/disable axi-mm, set number of streams, set readmemh for memory, set per-stream offsets in memory, set per-stream widths - parameter CONFIG_EN = 1, - parameter NSTREAMS = 6,//1 up to 6 - - parameter MEM_DEPTH = 13824, - parameter MEM_WIDTH = 32, - parameter MEM_INIT = "./", - parameter RAM_STYLE = "auto", - - //widths per stream - parameter STRM0_WIDTH = 32, - parameter STRM1_WIDTH = 32, - parameter STRM2_WIDTH = 32, - parameter STRM3_WIDTH = 32, - parameter STRM4_WIDTH = 32, - parameter STRM5_WIDTH = 32, - - //depths per stream - parameter STRM0_DEPTH = 2304, - parameter STRM1_DEPTH = 2304, - parameter STRM2_DEPTH = 2304, - parameter STRM3_DEPTH = 2304, - parameter STRM4_DEPTH = 2304, - parameter STRM5_DEPTH = 2304, - - //offsets for each stream - parameter STRM0_OFFSET = 0, - parameter STRM1_OFFSET = 2304, - parameter STRM2_OFFSET = 4608, - parameter STRM3_OFFSET = 6912, - parameter STRM4_OFFSET = 9216, - parameter STRM5_OFFSET = 11520, - - parameter AXILITE_ADDR_WIDTH = 2+$clog2(MEM_DEPTH*(1<<$clog2((MEM_WIDTH+31)/32))) -) - -( - input aclk, - input aresetn, - - output awready, - input awvalid, - input [AXILITE_ADDR_WIDTH-1:0] awaddr, - input [2:0] awprot, - //write data - output wready, - input wvalid, - input [31:0] wdata, - input [3:0] wstrb, - //burst response - input bready, - output bvalid, - output [1:0] bresp, - - //Read channels - //read address - output arready, - input arvalid, - input [AXILITE_ADDR_WIDTH-1:0] araddr, - input [2:0] arprot, - //read data - input rready, - output rvalid, - output [1:0] rresp, - output [31:0] rdata, - - //multiple output AXI Streams, TDATA width rounded to multiple of 8 bits - input m_axis_0_afull, - input m_axis_0_tready, - output m_axis_0_tvalid, - output [((STRM0_WIDTH+7)/8)*8-1:0] m_axis_0_tdata, - - input m_axis_1_afull, - input m_axis_1_tready, - output m_axis_1_tvalid, - output [((STRM1_WIDTH+7)/8)*8-1:0] m_axis_1_tdata, - - input m_axis_2_afull, - input m_axis_2_tready, - output m_axis_2_tvalid, - output [((STRM2_WIDTH+7)/8)*8-1:0] m_axis_2_tdata, - - input m_axis_3_afull, - input m_axis_3_tready, - output m_axis_3_tvalid, - output [((STRM3_WIDTH+7)/8)*8-1:0] m_axis_3_tdata, - - input m_axis_4_afull, - input m_axis_4_tready, - output m_axis_4_tvalid, - output [((STRM4_WIDTH+7)/8)*8-1:0] m_axis_4_tdata, - - input m_axis_5_afull, - input m_axis_5_tready, - output m_axis_5_tvalid, - output [((STRM5_WIDTH+7)/8)*8-1:0] m_axis_5_tdata - - -); - -wire [31:0] config_address; -wire config_ce; -wire config_we; -wire config_rack; -wire [MEM_WIDTH-1:0] config_d0; -wire [MEM_WIDTH-1:0] config_q0; - -generate -if(NSTREAMS <= 2) begin: singleblock - - -memstream_singleblock -#( - .CONFIG_EN(CONFIG_EN), - .NSTREAMS(NSTREAMS), - .MEM_DEPTH(MEM_DEPTH), - .MEM_WIDTH(MEM_WIDTH), - .MEM_INIT(MEM_INIT), - .RAM_STYLE(RAM_STYLE), - - //widths per stream - .STRM0_WIDTH(STRM0_WIDTH), - .STRM1_WIDTH(STRM1_WIDTH), - - //depths per stream - .STRM0_DEPTH(STRM0_DEPTH), - .STRM1_DEPTH(STRM1_DEPTH), - - //offsets for each stream - .STRM0_OFFSET(STRM0_OFFSET), - .STRM1_OFFSET(STRM1_OFFSET) -) -mem -( - .aclk(aclk), - .aresetn(aresetn), - - .config_address(config_address), - .config_ce(config_ce), - .config_we(config_we), - .config_d0(config_d0), - .config_q0(config_q0), - .config_rack(config_rack), - - .m_axis_0_tready(m_axis_0_tready), - .m_axis_0_tvalid(m_axis_0_tvalid), - .m_axis_0_tdata(m_axis_0_tdata), - - .m_axis_1_tready(m_axis_1_tready), - .m_axis_1_tvalid(m_axis_1_tvalid), - .m_axis_1_tdata(m_axis_1_tdata) -); - -assign m_axis_2_tvalid = 0; -assign m_axis_2_tdata = 0; -assign m_axis_3_tvalid = 0; -assign m_axis_3_tdata = 0; -assign m_axis_4_tvalid = 0; -assign m_axis_4_tdata = 0; -assign m_axis_5_tvalid = 0; -assign m_axis_5_tdata = 0; - -end else begin: multiblock - - -memstream_multiblock -#( - .CONFIG_EN(CONFIG_EN), - .NSTREAMS(NSTREAMS), - .MEM_DEPTH(MEM_DEPTH), - .MEM_WIDTH(MEM_WIDTH), - .MEM_INIT(MEM_INIT), - .RAM_STYLE(RAM_STYLE), - - //widths per stream - .STRM0_WIDTH(STRM0_WIDTH), - .STRM1_WIDTH(STRM1_WIDTH), - .STRM2_WIDTH(STRM2_WIDTH), - .STRM3_WIDTH(STRM3_WIDTH), - .STRM4_WIDTH(STRM4_WIDTH), - .STRM5_WIDTH(STRM5_WIDTH), - - //depths per stream - .STRM0_DEPTH(STRM0_DEPTH), - .STRM1_DEPTH(STRM1_DEPTH), - .STRM2_DEPTH(STRM2_DEPTH), - .STRM3_DEPTH(STRM3_DEPTH), - .STRM4_DEPTH(STRM4_DEPTH), - .STRM5_DEPTH(STRM5_DEPTH), - - //offsets for each stream - .STRM0_OFFSET(STRM0_OFFSET), - .STRM1_OFFSET(STRM1_OFFSET), - .STRM2_OFFSET(STRM2_OFFSET), - .STRM3_OFFSET(STRM3_OFFSET), - .STRM4_OFFSET(STRM4_OFFSET), - .STRM5_OFFSET(STRM5_OFFSET) -) -mem -( - .aclk(aclk), - .aresetn(aresetn), - - .config_address(config_address), - .config_ce(config_ce), - .config_we(config_we), - .config_d0(config_d0), - .config_q0(config_q0), - - .m_axis_0_afull(m_axis_0_afull), - .m_axis_0_tready(m_axis_0_tready), - .m_axis_0_tvalid(m_axis_0_tvalid), - .m_axis_0_tdata(m_axis_0_tdata), - - .m_axis_1_afull(m_axis_1_afull), - .m_axis_1_tready(m_axis_1_tready), - .m_axis_1_tvalid(m_axis_1_tvalid), - .m_axis_1_tdata(m_axis_1_tdata), - - .m_axis_2_afull(m_axis_2_afull), - .m_axis_2_tready(m_axis_2_tready), - .m_axis_2_tvalid(m_axis_2_tvalid), - .m_axis_2_tdata(m_axis_2_tdata), - - .m_axis_3_afull(m_axis_3_afull), - .m_axis_3_tready(m_axis_3_tready), - .m_axis_3_tvalid(m_axis_3_tvalid), - .m_axis_3_tdata(m_axis_3_tdata), - - .m_axis_4_afull(m_axis_4_afull), - .m_axis_4_tready(m_axis_4_tready), - .m_axis_4_tvalid(m_axis_4_tvalid), - .m_axis_4_tdata(m_axis_4_tdata), - - .m_axis_5_afull(m_axis_5_afull), - .m_axis_5_tready(m_axis_5_tready), - .m_axis_5_tvalid(m_axis_5_tvalid), - .m_axis_5_tdata(m_axis_5_tdata) - -); - - -end -endgenerate - -axi4lite_if -#( - .ADDR_WIDTH(AXILITE_ADDR_WIDTH), - .DATA_WIDTH(32), - .IP_DATA_WIDTH(MEM_WIDTH) -) -config_if -( - //system signals - .aclk(aclk), - .aresetn(aresetn), - - //Write channels - //write address - .awready(awready), - .awvalid(awvalid), - .awaddr(awaddr), - .awprot(awprot), - //write data - .wready(wready), - .wvalid(wvalid), - .wdata(wdata), - .wstrb(wstrb), - //burst response - .bready(bready), - .bvalid(bvalid), - .bresp(bresp), - - //Read channels - //read address - .arready(arready), - .arvalid(arvalid), - .araddr(araddr), - .arprot(arprot), - //read data - .rready(rready), - .rvalid(rvalid), - .rresp(rresp), - .rdata(rdata), - - //IP-side interface - .ip_en(config_ce), - .ip_wen(config_we), - .ip_addr(config_address), - .ip_wdata(config_d0), - .ip_rack(config_rack), - .ip_rdata(config_q0) -); - -endmodule diff --git a/finn-rtllib/memstream/hdl/memstream_axi.sv b/finn-rtllib/memstream/hdl/memstream_axi.sv new file mode 100644 index 0000000000000000000000000000000000000000..136bcb1d7e5d28a56e84302346f97b4844ebf69a --- /dev/null +++ b/finn-rtllib/memstream/hdl/memstream_axi.sv @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2023, Xilinx + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of FINN nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Thomas B. Preußer <thomas.preusser@amd.com> + */ + +module memstream_axi #( + int unsigned DEPTH, + int unsigned WIDTH, + + parameter INIT_FILE = "", + parameter RAM_STYLE = "auto", + + localparam int unsigned AXILITE_ADDR_WIDTH = $clog2(DEPTH * (2**$clog2((WIDTH+31)/32))) + 2 +)( + // Global Control + input logic clk, + input logic rst, + + // AXI-lite Write + output logic awready, + input logic awvalid, + input logic [2:0] awprot, + input logic [AXILITE_ADDR_WIDTH-1:0] awaddr, + + output logic wready, + input logic wvalid, + input logic [31:0] wdata, + input logic [ 3:0] wstrb, + + input logic bready, + output logic bvalid, + output logic [1:0] bresp, + + // AXI-lite Read + output logic arready, + input logic arvalid, + input logic [2:0] arprot, + input logic [AXILITE_ADDR_WIDTH-1:0] araddr, + + input logic rready, + output logic rvalid, + output logic [ 1:0] rresp, + output logic [31:0] rdata, + + // Continuous output stream + input logic m_axis_0_tready, + output logic m_axis_0_tvalid, + output logic [((WIDTH+7)/8)*8-1:0] m_axis_0_tdata +); + + //----------------------------------------------------------------------- + // AXI-lite to ap_memory Adapter + uwire [31:0] config_address; + uwire config_ce; + uwire config_we; + uwire config_rack; + uwire [WIDTH-1:0] config_d0; + uwire [WIDTH-1:0] config_q0; + axi4lite_if #( + .ADDR_WIDTH(AXILITE_ADDR_WIDTH), + .DATA_WIDTH(32), + .IP_DATA_WIDTH(WIDTH) + ) config_if ( + .aclk(clk), .aresetn(!rst), + + // Write Channels + .awready, .awvalid, .awaddr, .awprot, + .wready, .wvalid, .wdata, .wstrb, + .bready, .bvalid, .bresp, + + // Read Channels + .arready, .arvalid, .araddr, .arprot, + .rready, .rvalid, .rresp, .rdata, + + // IP-side Interface + .ip_en(config_ce), + .ip_wen(config_we), + .ip_addr(config_address), + .ip_wdata(config_d0), + .ip_rack(config_rack), + .ip_rdata(config_q0) + ); + + //----------------------------------------------------------------------- + // Streaming Memory Backend + memstream #( + .DEPTH(DEPTH), + .WIDTH(WIDTH), + .INIT_FILE(INIT_FILE), + .RAM_STYLE(RAM_STYLE) + ) mem ( + .clk, .rst, + + .config_address, + .config_ce, + .config_we, + .config_d0, + .config_q0, + .config_rack, + + .ordy(m_axis_0_tready), + .ovld(m_axis_0_tvalid), + .odat(m_axis_0_tdata[WIDTH-1:0]) + ); + if($bits(m_axis_0_tdata) > WIDTH) begin + assign m_axis_0_tdata[$left(m_axis_0_tdata):WIDTH] = '0; + end + +endmodule : memstream_axi diff --git a/finn-rtllib/memstream/hdl/memstream_axi_wrapper.v b/finn-rtllib/memstream/hdl/memstream_axi_wrapper.v new file mode 100644 index 0000000000000000000000000000000000000000..13f5c82d6e1a3046ca9f2b227687f1c69204f24b --- /dev/null +++ b/finn-rtllib/memstream/hdl/memstream_axi_wrapper.v @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2023, Xilinx + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of FINN nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Thomas B. Preußer <thomas.preusser@amd.com> + */ + +module memstream_axi_wrapper #( + parameter DEPTH = 512, + parameter WIDTH = 32, + + parameter INIT_FILE = "", + parameter RAM_STYLE = "auto", + + parameter AXILITE_ADDR_WIDTH = $clog2(DEPTH * (2**$clog2((WIDTH+31)/32))) + 2 +)( + // Global Control + (* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF m_axis_0, ASSOCIATED_RESET ap_rst_n" *) + (* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 ap_clk CLK" *) + input ap_clk, + (* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) + input ap_rst_n, + + // AXI-lite Write + output awready, + input awvalid, + input [2:0] awprot, + input [AXILITE_ADDR_WIDTH-1:0] awaddr, + + output wready, + input wvalid, + input [31:0] wdata, + input [ 3:0] wstrb, + + input bready, + output bvalid, + output [1:0] bresp, + + // AXI-lite Read + output arready, + input arvalid, + input [2:0] arprot, + input [AXILITE_ADDR_WIDTH-1:0] araddr, + + input rready, + output rvalid, + output [ 1:0] rresp, + output [31:0] rdata, + + // Continuous output stream + input m_axis_0_tready, + output m_axis_0_tvalid, + output [((WIDTH+7)/8)*8-1:0] m_axis_0_tdata +); + + localparam INIT_FILTERED = +`ifdef SYNTHESIS + RAM_STYLE == "ultra"? "" : +`endif + INIT_FILE; + + memstream_axi #( + .DEPTH(DEPTH), .WIDTH(WIDTH), + .INIT_FILE(INIT_FILTERED), + .RAM_STYLE(RAM_STYLE) + ) core ( + .clk(ap_clk), .rst(!ap_rst_n), + + // AXI-lite Write + .awready(awready), + .awvalid(awvalid), + .awprot(awprot), + .awaddr(awaddr), + .wready(wready), + .wvalid(wvalid), + .wdata(wdata), + .wstrb(wstrb), + .bready(bready), + .bvalid(bvalid), + .bresp(bresp), + + // AXI-lite Read + .arready(arready), + .arvalid(arvalid), + .arprot(arprot), + .araddr(araddr), + .rready(rready), + .rvalid(rvalid), + .rresp(rresp), + .rdata(rdata), + + // Continuous output stream + .m_axis_0_tready(m_axis_0_tready), + .m_axis_0_tvalid(m_axis_0_tvalid), + .m_axis_0_tdata(m_axis_0_tdata) + ); + +endmodule : memstream_axi_wrapper diff --git a/finn-rtllib/memstream/hdl/memstream_multiblock.v b/finn-rtllib/memstream/hdl/memstream_multiblock.v deleted file mode 100644 index 4e6167132da119d9e19a5f4b6a378311e74311c3..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/hdl/memstream_multiblock.v +++ /dev/null @@ -1,474 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -module memstream_multiblock -#( -//parameters to enable/disable axi-mm, set number of streams, set readmemh for memory, set per-stream offsets in memory, set per-stream widths - parameter CONFIG_EN = 1, - parameter NSTREAMS = 6,//1 up to 6 - - parameter MEM_DEPTH = 13824, - parameter MEM_WIDTH = 32, - parameter MEM_INIT = "./", - parameter RAM_STYLE = "auto", - - //widths per stream - parameter STRM0_WIDTH = 32, - parameter STRM1_WIDTH = 32, - parameter STRM2_WIDTH = 32, - parameter STRM3_WIDTH = 32, - parameter STRM4_WIDTH = 32, - parameter STRM5_WIDTH = 32, - - //depths per stream - parameter STRM0_DEPTH = 2304, - parameter STRM1_DEPTH = 2304, - parameter STRM2_DEPTH = 2304, - parameter STRM3_DEPTH = 2304, - parameter STRM4_DEPTH = 2304, - parameter STRM5_DEPTH = 2304, - - //offsets for each stream - parameter STRM0_OFFSET = 0, - parameter STRM1_OFFSET = 2304, - parameter STRM2_OFFSET = 4608, - parameter STRM3_OFFSET = 6912, - parameter STRM4_OFFSET = 9216, - parameter STRM5_OFFSET = 11520 -) - -( - input aclk, - input aresetn, - - //optional configuration interface compatible with ap_memory - input [31:0] config_address, - input config_ce, - input config_we, - input [31:0] config_d0, - output [31:0] config_q0, - output config_rack, - - //multiple output AXI Streams, TDATA width rounded to multiple of 8 bits - input m_axis_0_afull, - input m_axis_0_tready, - output m_axis_0_tvalid, - output [((STRM0_WIDTH+7)/8)*8-1:0] m_axis_0_tdata, - - input m_axis_1_afull, - input m_axis_1_tready, - output m_axis_1_tvalid, - output [((STRM1_WIDTH+7)/8)*8-1:0] m_axis_1_tdata, - - input m_axis_2_afull, - input m_axis_2_tready, - output m_axis_2_tvalid, - output [((STRM2_WIDTH+7)/8)*8-1:0] m_axis_2_tdata, - - input m_axis_3_afull, - input m_axis_3_tready, - output m_axis_3_tvalid, - output [((STRM3_WIDTH+7)/8)*8-1:0] m_axis_3_tdata, - - input m_axis_4_afull, - input m_axis_4_tready, - output m_axis_4_tvalid, - output [((STRM4_WIDTH+7)/8)*8-1:0] m_axis_4_tdata, - - input m_axis_5_afull, - input m_axis_5_tready, - output m_axis_5_tvalid, - output [((STRM5_WIDTH+7)/8)*8-1:0] m_axis_5_tdata - - -); - -//calculate number of RAMB18 blocks we need depth-wise -localparam NMEMBLOCKS = (MEM_DEPTH+1023) / 1024; //ceil(MEM_DEPTH/1024) - -//calculate width of address for each block -localparam BLOCKADRWIDTH = NMEMBLOCKS > 1 ? 10 : $clog2(MEM_DEPTH); - -//determine whether a stream needs to multiplex between memory blocks -localparam STRM0_MUX = ((STRM0_OFFSET/1024) != ((STRM0_OFFSET+STRM0_DEPTH)/1024)); -localparam STRM1_MUX = ((STRM1_OFFSET/1024) != ((STRM1_OFFSET+STRM1_DEPTH)/1024)); -localparam STRM2_MUX = ((STRM2_OFFSET/1024) != ((STRM2_OFFSET+STRM2_DEPTH)/1024)); -localparam STRM3_MUX = ((STRM3_OFFSET/1024) != ((STRM3_OFFSET+STRM3_DEPTH)/1024)); -localparam STRM4_MUX = ((STRM4_OFFSET/1024) != ((STRM4_OFFSET+STRM4_DEPTH)/1024)); -localparam STRM5_MUX = ((STRM5_OFFSET/1024) != ((STRM5_OFFSET+STRM5_DEPTH)/1024)); - -//determine what the base block of each stream is -localparam STRM0_BLOCK = (STRM0_OFFSET/1024); -localparam STRM1_BLOCK = (STRM1_OFFSET/1024); -localparam STRM2_BLOCK = (STRM2_OFFSET/1024); -localparam STRM3_BLOCK = (STRM3_OFFSET/1024); -localparam STRM4_BLOCK = (STRM4_OFFSET/1024); -localparam STRM5_BLOCK = (STRM5_OFFSET/1024); - -//determine what the end block of each stream is -localparam STRM0_END_BLOCK = ((STRM0_OFFSET+STRM0_DEPTH-1)/1024); -localparam STRM1_END_BLOCK = ((STRM1_OFFSET+STRM1_DEPTH-1)/1024); -localparam STRM2_END_BLOCK = ((STRM2_OFFSET+STRM2_DEPTH-1)/1024); -localparam STRM3_END_BLOCK = ((STRM3_OFFSET+STRM3_DEPTH-1)/1024); -localparam STRM4_END_BLOCK = ((STRM4_OFFSET+STRM4_DEPTH-1)/1024); -localparam STRM5_END_BLOCK = ((STRM5_OFFSET+STRM5_DEPTH-1)/1024); - -//determine the number of blocks spanned by each stream -localparam STRM0_NBLOCKS = STRM0_END_BLOCK - STRM0_BLOCK + 1; -localparam STRM1_NBLOCKS = STRM1_END_BLOCK - STRM1_BLOCK + 1; -localparam STRM2_NBLOCKS = STRM2_END_BLOCK - STRM2_BLOCK + 1; -localparam STRM3_NBLOCKS = STRM3_END_BLOCK - STRM3_BLOCK + 1; -localparam STRM4_NBLOCKS = STRM4_END_BLOCK - STRM4_BLOCK + 1; -localparam STRM5_NBLOCKS = STRM5_END_BLOCK - STRM5_BLOCK + 1; - -//TODO: check that memory width is equal to the widest stream -//TODO: check that the stream depths and offsets make sense, and that the memory depth is sufficient (or calculate depth here?) -initial begin - if((NSTREAMS < 1) | (NSTREAMS > 6)) begin - $display("Invalid setting for NSTREAMS, please set in range [1,6]"); - $finish(); - end -end - -//invert reset -wire rst; -assign rst = ~aresetn; - -//WARNING: pipeline depth is larger than the number of streams per port so we have in-flight writes that may see not-ready when they get executed -//solution: use prog-full to make sure we have an equal number of free slots in the stream to the read pipeline depth - -reg [$clog2(MEM_DEPTH)-1:0] strm0_addr = STRM0_OFFSET; -reg [$clog2(MEM_DEPTH)-1:0] strm1_addr = STRM1_OFFSET; -reg [$clog2(MEM_DEPTH)-1:0] strm2_addr = STRM2_OFFSET; -reg [$clog2(MEM_DEPTH)-1:0] strm3_addr = STRM3_OFFSET; -reg [$clog2(MEM_DEPTH)-1:0] strm4_addr = STRM4_OFFSET; -reg [$clog2(MEM_DEPTH)-1:0] strm5_addr = STRM5_OFFSET; - -reg strm0_incr_en; -reg strm1_incr_en; -reg strm2_incr_en; -reg strm3_incr_en; -reg strm4_incr_en; -reg strm5_incr_en; - -wire strm0_rst; -wire strm1_rst; -wire strm2_rst; -wire strm3_rst; -wire strm4_rst; -wire strm5_rst; - -reg strm0_ready; -reg strm1_ready; -reg strm2_ready; -reg strm3_ready; -reg strm4_ready; -reg strm5_ready; - -//arbiter: work on one stream at a time -//multiplex each port between (up to) half of the streams -reg [1:0] current_stream_porta = 0; -reg [1:0] current_stream_portb = 0; - -always @(posedge aclk) begin - if(rst) - current_stream_porta <= 0; - else case(current_stream_porta) - 0: current_stream_porta <= strm2_ready ? 1 : strm4_ready ? 2 : 0; - 1: current_stream_porta <= strm4_ready ? 2 : strm0_ready ? 0 : 1; - 2: current_stream_porta <= strm0_ready ? 0 : strm2_ready ? 1 : 2; - endcase - if(rst) - current_stream_portb <= 0; - else case(current_stream_portb) - 0: current_stream_portb <= strm3_ready ? 1 : strm5_ready ? 2 : 0; - 1: current_stream_portb <= strm5_ready ? 2 : strm1_ready ? 0 : 1; - 2: current_stream_portb <= strm1_ready ? 0 : strm3_ready ? 1 : 2; - endcase -end - -always @(posedge aclk) begin - if(rst) begin - strm0_incr_en <= 0; - strm1_incr_en <= 0; - strm2_incr_en <= 0; - strm3_incr_en <= 0; - strm4_incr_en <= 0; - strm5_incr_en <= 0; - end else begin - strm0_incr_en <= (current_stream_porta == 0) & strm0_ready; - strm1_incr_en <= (current_stream_portb == 0) & strm1_ready; - strm2_incr_en <= (current_stream_porta == 1) & strm2_ready; - strm3_incr_en <= (current_stream_portb == 1) & strm3_ready; - strm4_incr_en <= (current_stream_porta == 2) & strm4_ready; - strm5_incr_en <= (current_stream_portb == 2) & strm5_ready; - end -end - -assign strm0_rst = strm0_incr_en & (strm0_addr == (STRM0_OFFSET + STRM0_DEPTH-1)); -assign strm1_rst = strm1_incr_en & (strm1_addr == (STRM1_OFFSET + STRM1_DEPTH-1)); -assign strm2_rst = strm2_incr_en & (strm2_addr == (STRM2_OFFSET + STRM2_DEPTH-1)); -assign strm3_rst = strm3_incr_en & (strm3_addr == (STRM3_OFFSET + STRM3_DEPTH-1)); -assign strm4_rst = strm4_incr_en & (strm4_addr == (STRM4_OFFSET + STRM4_DEPTH-1)); -assign strm5_rst = strm5_incr_en & (strm5_addr == (STRM5_OFFSET + STRM5_DEPTH-1)); - -always @(posedge aclk) begin - strm0_ready <= ~m_axis_0_afull; - strm1_ready <= ~m_axis_1_afull & (NSTREAMS >= 2); - strm2_ready <= ~m_axis_2_afull & (NSTREAMS >= 3); - strm3_ready <= ~m_axis_3_afull & (NSTREAMS >= 4); - strm4_ready <= ~m_axis_4_afull & (NSTREAMS >= 5); - strm5_ready <= ~m_axis_5_afull & (NSTREAMS >= 6); -end - -//one address counter per stream; more LUTs but keeps routing short and local -always @(posedge aclk) begin - if(strm0_rst | rst) - strm0_addr <= STRM0_OFFSET; - else if(strm0_incr_en) - strm0_addr <= strm0_addr + 1; - if(strm1_rst | rst) - strm1_addr <= STRM1_OFFSET; - else if(strm1_incr_en) - strm1_addr <= strm1_addr + 1; - if(strm2_rst | rst) - strm2_addr <= STRM2_OFFSET; - else if(strm2_incr_en) - strm2_addr <= strm2_addr + 1; - if(strm3_rst | rst) - strm3_addr <= STRM3_OFFSET; - else if(strm3_incr_en) - strm3_addr <= strm3_addr + 1; - if(strm4_rst | rst) - strm4_addr <= STRM4_OFFSET; - else if(strm4_incr_en) - strm4_addr <= strm4_addr + 1; - if(strm5_rst | rst) - strm5_addr <= STRM5_OFFSET; - else if(strm5_incr_en) - strm5_addr <= strm5_addr + 1; -end - -reg [$clog2(MEM_DEPTH)-1:0] addra; -wire [MEM_WIDTH*NMEMBLOCKS-1:0] rdqa; - -reg [$clog2(MEM_DEPTH)-1:0] addrb; -wire [MEM_WIDTH*NMEMBLOCKS-1:0] rdqb; - -wire [NMEMBLOCKS-1:0] we; - -reg [1:0] addr_select_porta; -reg [1:0] addr_select_portb; - -//multiplex addresses of various streams into address ports of memory -always @(posedge aclk) begin - addr_select_porta <= current_stream_porta; - case(addr_select_porta) - 0: addra <= strm0_addr; - 1: addra <= strm2_addr; - 2: addra <= strm4_addr; - endcase - addr_select_portb <= current_stream_portb; - case(addr_select_portb) - 0: addrb <= strm1_addr; - 1: addrb <= strm3_addr; - 2: addrb <= strm5_addr; - endcase -end - -genvar g; -generate for(g=0; g<NMEMBLOCKS; g=g+1) begin: blockports - -assign we[g] = (CONFIG_EN == 1) & config_ce & config_we & (config_address[31:BLOCKADRWIDTH] == g); - -ramb18_wf_dualport -#( - .ID(g), - .DWIDTH(MEM_WIDTH), - .AWIDTH(BLOCKADRWIDTH), - .MEM_INIT(MEM_INIT), - .RAM_STYLE(RAM_STYLE) -) -ram -( - .clk(aclk), - - .wea(we[g]), - .ena(1'b1), - .enqa(1'b1), - .addra(we[g] ? config_address[BLOCKADRWIDTH-1:0] : addra[BLOCKADRWIDTH-1:0]), - .wdataa(config_d0), - .rdqa(rdqa[(g+1)*MEM_WIDTH-1:g*MEM_WIDTH]), - - .web(1'b0), - .enb(1'b1), - .enqb(1'b1), - .addrb(addrb[BLOCKADRWIDTH-1:0]), - .wdatab('d0), - .rdqb(rdqb[(g+1)*MEM_WIDTH-1:g*MEM_WIDTH]) -); - -end -endgenerate - -integer i; - -generate if(NMEMBLOCKS > 1) begin: multiblock - -wire [MEM_WIDTH-1:0] rdqmux[5:0]; - -reg [$clog2(MEM_DEPTH)-BLOCKADRWIDTH-1:0] rdblocka[2:0]; -reg [$clog2(MEM_DEPTH)-BLOCKADRWIDTH-1:0] rdblockb[2:0]; - -always @(posedge aclk) begin - rdblocka[0] <= addra[$clog2(MEM_DEPTH)-1:BLOCKADRWIDTH]; - rdblockb[0] <= addrb[$clog2(MEM_DEPTH)-1:BLOCKADRWIDTH]; - for(i=0; i<2; i=i+1) begin - rdblocka[i+1] <= rdblocka[i]; - rdblockb[i+1] <= rdblockb[i]; - end -end - -if(NSTREAMS >= 1) begin: en_strm0 - if(STRM0_MUX == 1) begin: mux0 - mux #(STRM0_NBLOCKS, MEM_WIDTH) m(rdqa[(STRM0_BLOCK+STRM0_NBLOCKS)*MEM_WIDTH-1:STRM0_BLOCK*MEM_WIDTH],rdqmux[0],rdblocka[1] - STRM0_BLOCK); - end else begin: nomux0 - assign rdqmux[0] = rdqa[(STRM0_BLOCK+1)*MEM_WIDTH-1:STRM0_BLOCK*MEM_WIDTH]; - end - assign m_axis_0_tdata = rdqmux[0][STRM0_WIDTH-1:0]; -end - -if(NSTREAMS >= 2) begin: en_strm1 - if(STRM1_MUX == 1) begin: mux1 - mux #(STRM1_NBLOCKS, MEM_WIDTH) m(rdqb[(STRM1_BLOCK+STRM1_NBLOCKS)*MEM_WIDTH-1:STRM1_BLOCK*MEM_WIDTH],rdqmux[1],rdblockb[1] - STRM1_BLOCK); - end else begin: nomux1 - assign rdqmux[1] = rdqb[(STRM1_BLOCK+1)*MEM_WIDTH-1:STRM1_BLOCK*MEM_WIDTH]; - end - assign m_axis_1_tdata = rdqmux[1][STRM1_WIDTH-1:0]; -end - -if(NSTREAMS >= 3) begin: en_strm2 - if(STRM2_MUX == 1) begin: mux2 - mux #(STRM2_NBLOCKS, MEM_WIDTH) m(rdqa[(STRM2_BLOCK+STRM2_NBLOCKS)*MEM_WIDTH-1:STRM2_BLOCK*MEM_WIDTH],rdqmux[2],rdblocka[1] - STRM2_BLOCK); - end else begin: nomux2 - assign rdqmux[2] = rdqa[(STRM2_BLOCK+1)*MEM_WIDTH-1:STRM2_BLOCK*MEM_WIDTH]; - end - assign m_axis_2_tdata = rdqmux[2][STRM2_WIDTH-1:0]; -end - -if(NSTREAMS >= 4) begin: en_strm3 - if(STRM3_MUX == 1) begin: mux3 - mux #(STRM3_NBLOCKS, MEM_WIDTH) m(rdqb[(STRM3_BLOCK+STRM3_NBLOCKS)*MEM_WIDTH-1:STRM3_BLOCK*MEM_WIDTH],rdqmux[3],rdblockb[1] - STRM3_BLOCK); - end else begin: nomux3 - assign rdqmux[3] = rdqb[(STRM3_BLOCK+1)*MEM_WIDTH-1:STRM3_BLOCK*MEM_WIDTH]; - end - assign m_axis_3_tdata = rdqmux[3][STRM3_WIDTH-1:0]; -end - -if(NSTREAMS >= 5) begin: en_strm4 - if(STRM4_MUX == 1) begin: mux4 - mux #(STRM4_NBLOCKS, MEM_WIDTH) m(rdqa[(STRM4_BLOCK+STRM4_NBLOCKS)*MEM_WIDTH-1:STRM4_BLOCK*MEM_WIDTH],rdqmux[4],rdblocka[1] - STRM4_BLOCK); - end else begin: nomux4 - assign rdqmux[4] = rdqa[(STRM4_BLOCK+1)*MEM_WIDTH-1:STRM4_BLOCK*MEM_WIDTH]; - end - assign m_axis_4_tdata = rdqmux[4][STRM4_WIDTH-1:0]; -end - -if(NSTREAMS >= 6) begin: en_strm5 - if(STRM5_MUX == 1) begin: mux5 - mux #(STRM5_NBLOCKS, MEM_WIDTH) m(rdqb[(STRM5_BLOCK+STRM5_NBLOCKS)*MEM_WIDTH-1:STRM5_BLOCK*MEM_WIDTH],rdqmux[5],rdblockb[1] - STRM5_BLOCK); - end else begin: nomux5 - assign rdqmux[5] = rdqb[(STRM5_BLOCK+1)*MEM_WIDTH-1:STRM5_BLOCK*MEM_WIDTH]; - end - assign m_axis_5_tdata = rdqmux[5][STRM5_WIDTH-1:0]; -end - -end else begin: singleblock - -if(NSTREAMS >= 1) begin: en_strm0_direct - assign m_axis_0_tdata = rdqa[STRM0_WIDTH-1:0]; -end -if(NSTREAMS >= 2) begin: en_strm1_direct - assign m_axis_1_tdata = rdqb[STRM1_WIDTH-1:0]; -end -if(NSTREAMS >= 3) begin: en_strm2_direct - assign m_axis_2_tdata = rdqa[STRM2_WIDTH-1:0]; -end -if(NSTREAMS >= 4) begin: en_strm3_direct - assign m_axis_3_tdata = rdqb[STRM3_WIDTH-1:0]; -end -if(NSTREAMS >= 5) begin: en_strm4_direct - assign m_axis_4_tdata = rdqa[STRM4_WIDTH-1:0]; -end -if(NSTREAMS >= 6) begin: en_strm5_direct - assign m_axis_5_tdata = rdqb[STRM5_WIDTH-1:0]; -end - -end -endgenerate - -//output to AXI Streams -reg tvalid_pipe0[2:0]; -reg tvalid_pipe1[2:0]; -reg tvalid_pipe2[2:0]; -reg tvalid_pipe3[2:0]; -reg tvalid_pipe4[2:0]; -reg tvalid_pipe5[2:0]; - -assign m_axis_0_tvalid = tvalid_pipe0[2]; -assign m_axis_1_tvalid = tvalid_pipe1[2]; -assign m_axis_2_tvalid = tvalid_pipe2[2]; -assign m_axis_3_tvalid = tvalid_pipe3[2]; -assign m_axis_4_tvalid = tvalid_pipe4[2]; -assign m_axis_5_tvalid = tvalid_pipe5[2]; - - -always @(posedge aclk) begin - tvalid_pipe0[0] <= strm0_incr_en; - tvalid_pipe1[0] <= strm1_incr_en; - tvalid_pipe2[0] <= strm2_incr_en; - tvalid_pipe3[0] <= strm3_incr_en; - tvalid_pipe4[0] <= strm4_incr_en; - tvalid_pipe5[0] <= strm5_incr_en; - for(i=0; i<2; i=i+1) begin: srl - tvalid_pipe0[i+1] <= tvalid_pipe0[i]; - tvalid_pipe1[i+1] <= tvalid_pipe1[i]; - tvalid_pipe2[i+1] <= tvalid_pipe2[i]; - tvalid_pipe3[i+1] <= tvalid_pipe3[i]; - tvalid_pipe4[i+1] <= tvalid_pipe4[i]; - tvalid_pipe5[i+1] <= tvalid_pipe5[i]; - end -end - -//dummy read, for now -assign config_q0 = 0; -assign config_rack = config_ce & ~config_we; - -endmodule diff --git a/finn-rtllib/memstream/hdl/memstream_singleblock.v b/finn-rtllib/memstream/hdl/memstream_singleblock.v deleted file mode 100644 index c9b8770aaa58dc3355bc259e5c5fece702125490..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/hdl/memstream_singleblock.v +++ /dev/null @@ -1,246 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/* - Implements a lightweight streamer for up to 2 streams in a single block of memory -*/ - -module memstream_singleblock -#( - parameter CONFIG_EN = 1, - parameter NSTREAMS = 2,//1 up to 2 - - parameter MEM_DEPTH = 512, - parameter MEM_WIDTH = 32, - parameter MEM_INIT = "./", - parameter RAM_STYLE = "auto", - - //widths per stream - parameter STRM0_WIDTH = 32, - parameter STRM1_WIDTH = 32, - - //depths per stream - parameter STRM0_DEPTH = 256, - parameter STRM1_DEPTH = 256, - - //offsets for each stream - parameter STRM0_OFFSET = 0, - parameter STRM1_OFFSET = 256 -) - -( - input aclk, - input aresetn, - - //optional configuration interface compatible with ap_memory - input [31:0] config_address, - input config_ce, - input config_we, - input [MEM_WIDTH-1:0] config_d0, - output [MEM_WIDTH-1:0] config_q0, - output config_rack, - - //multiple output AXI Streams, TDATA width rounded to multiple of 8 bits - input m_axis_0_tready, - output m_axis_0_tvalid, - output [((STRM0_WIDTH+7)/8)*8-1:0] m_axis_0_tdata, - - input m_axis_1_tready, - output m_axis_1_tvalid, - output [((STRM1_WIDTH+7)/8)*8-1:0] m_axis_1_tdata - -); - - -//TODO: check that memory width is equal to the widest stream -//TODO: check that the stream depths and offsets make sense, and that the memory depth is sufficient (or calculate depth here?) -initial begin - if((NSTREAMS < 1) | (NSTREAMS > 2)) begin - $display("Invalid setting for NSTREAMS, please set in range [1,2]"); - $finish(); - end -end - -//invert reset -wire rst; -assign rst = ~aresetn; - -wire strm0_incr_en; -wire strm1_incr_en; - -assign strm0_incr_en = m_axis_0_tready | ~m_axis_0_tvalid; -assign strm1_incr_en = m_axis_1_tready | ~m_axis_1_tvalid; - -reg rack_shift[1:0]; - -generate -if(MEM_DEPTH > 1) begin: use_ram - -//calculate width of memory address, with a minimum of 1 bit -localparam BLOCKADRWIDTH = $clog2(MEM_DEPTH); - -reg [BLOCKADRWIDTH-1:0] strm0_addr = STRM0_OFFSET; -wire strm0_rst; -assign strm0_rst = strm0_incr_en & (strm0_addr == (STRM0_OFFSET + STRM0_DEPTH-1)); - -//one address counter per stream; more LUTs but keeps routing short and local -always @(posedge aclk) begin - if(strm0_rst | rst) - strm0_addr <= STRM0_OFFSET; - else if(strm0_incr_en) - strm0_addr <= strm0_addr + 1; -end - -if(NSTREAMS == 1) begin: sdp - -ramb18_sdp -#( - .ID(0), - .DWIDTH(MEM_WIDTH), - .AWIDTH(BLOCKADRWIDTH), - .DEPTH(MEM_DEPTH), - .MEM_INIT(MEM_INIT), - .RAM_STYLE(RAM_STYLE) -) -ram -( - .clk(aclk), - - .ena(config_ce), - .wea(config_we), - .addra(config_address[BLOCKADRWIDTH-1:0]), - .wdataa(config_d0), - - .enb(strm0_incr_en | config_ce), - .enqb(strm0_incr_en | rack_shift[0]), - .addrb(config_ce ? config_address[BLOCKADRWIDTH-1:0] : strm0_addr), - .rdqb(m_axis_0_tdata) -); - - -end else begin: tdp - -reg [BLOCKADRWIDTH-1:0] strm1_addr = STRM1_OFFSET; -wire strm1_rst; -assign strm1_rst = strm1_incr_en & (strm1_addr == (STRM1_OFFSET + STRM1_DEPTH-1)); - -always @(posedge aclk) begin - if(strm1_rst | rst) - strm1_addr <= STRM1_OFFSET; - else if(strm1_incr_en) - strm1_addr <= strm1_addr + 1; -end - -ramb18_wf_dualport -#( - .ID(0), - .DWIDTH(MEM_WIDTH), - .AWIDTH(BLOCKADRWIDTH), - .DEPTH(MEM_DEPTH), - .MEM_INIT(MEM_INIT), - .RAM_STYLE(RAM_STYLE) -) -ram -( - .clk(aclk), - - .wea(config_we), - .ena(strm0_incr_en | config_ce), - .enqa(strm0_incr_en | config_ce_r), - .addra(config_we ? config_address[BLOCKADRWIDTH-1:0] : strm0_addr), - .wdataa(config_d0), - .rdqa(m_axis_0_tdata), - - .web(1'b0), - .enb(strm1_incr_en), - .enqb(strm1_incr_en), - .addrb(strm1_addr), - .wdatab('d0), - .rdqb(m_axis_1_tdata) -); - -end - -end else begin: bypass - -reg [MEM_WIDTH-1:0] singleval[0:0]; -initial begin - `ifdef SYNTHESIS - $readmemh({MEM_INIT,"memblock_synth_0.dat"}, singleval, 0, 0); - `else - $readmemh({MEM_INIT,"memblock_sim_0.dat"}, singleval, 0, 0); - `endif -end - -always @(posedge aclk) - if(config_ce & config_we) - singleval[0] <= config_d0; - -assign m_axis_0_tdata = singleval[0]; -assign m_axis_1_tdata = singleval[0]; - -end -endgenerate - -//signal valid after 2 tready cycles after initialization -//then stay valid -reg [1:0] tvalid_pipe0 = 2'd0; -reg [1:0] tvalid_pipe1 = 2'd0; - -assign m_axis_0_tvalid = tvalid_pipe0[1]; -assign m_axis_1_tvalid = tvalid_pipe1[1]; - -always @(posedge aclk) begin - if(rst) begin - tvalid_pipe0 <= 0; - end else if(strm0_incr_en) begin - tvalid_pipe0[0] <= 1; - tvalid_pipe0[1] <= tvalid_pipe0[0]; - end -end - -always @(posedge aclk) begin - if(rst) begin - tvalid_pipe1 <= 0; - end else if(strm1_incr_en) begin - tvalid_pipe1[0] <= 1; - tvalid_pipe1[1] <= tvalid_pipe1[0]; - end -end - -always @(posedge aclk) begin - rack_shift[0] <= config_ce & ~config_we; - rack_shift[1] <= rack_shift[0]; -end - -assign config_rack = rack_shift[1]; -assign config_q0 = m_axis_0_tdata; - -endmodule diff --git a/finn-rtllib/memstream/hdl/mux.v b/finn-rtllib/memstream/hdl/mux.v deleted file mode 100644 index f7087f9735771a73aa532ae19baf18569e9de663..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/hdl/mux.v +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -module mux -#( - parameter NINPUTS = 1, - parameter WIDTH = 16 -) -( - input [NINPUTS*WIDTH-1:0] in, - output [WIDTH-1:0] out, - input [$clog2(NINPUTS)-1:0] sel -); - -assign out = in >> (sel*WIDTH); - -endmodule diff --git a/finn-rtllib/memstream/hdl/ramb18_sdp.v b/finn-rtllib/memstream/hdl/ramb18_sdp.v deleted file mode 100644 index 8d2fbf9a988c0e9702e1ed83f2b4e79efb1c5a85..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/hdl/ramb18_sdp.v +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -module ramb18_sdp -#( - parameter ID = 0, - parameter DWIDTH = 18, - parameter AWIDTH = 10, - parameter DEPTH = 2**AWIDTH, - parameter MEM_INIT = "", - parameter RAM_STYLE = "auto" -) -( - input clk, - - input ena, - input wea, - input [AWIDTH-1:0] addra, - input [DWIDTH-1:0] wdataa, - - input enb, - input enqb, - input [AWIDTH-1:0] addrb, - output reg [DWIDTH-1:0] rdqb -); - -(* ram_style = RAM_STYLE *) reg [DWIDTH-1:0] mem[0:DEPTH-1]; -reg [DWIDTH-1:0] rdatab; - -`ifdef SYNTHESIS -reg [7:0] idx = ID; -`else -reg [15:0] idx; -`endif - -//initialize memory -initial begin - //note the hacky way of adding a filename memblock_ID.dat to the path provided in MEM_INIT - //ID can go up to 99 - if (ID < 0 && ID > 99) begin - $display("ID out of range [0-99]"); - $finish(); - end - //MEM_INIT path must be terminated by / - `ifdef SYNTHESIS - if (ID < 10) - $readmemh({MEM_INIT,"memblock_synth_",idx+8'd48,".dat"}, mem, 0, DEPTH-1); - else - $readmemh({MEM_INIT,"memblock_synth_",(idx/10)+8'd48,(idx%10)+8'd48,".dat"}, mem, 0, DEPTH-1); - `else - $sformat(idx,"%0d",ID); - if (ID < 10) - $readmemh({MEM_INIT,"memblock_sim_",idx[7:0],".dat"}, mem, 0, DEPTH-1); - else - $readmemh({MEM_INIT,"memblock_sim_",idx,".dat"}, mem, 0, DEPTH-1); - `endif -end - -//memory ports, with output pipeline register -always @(posedge clk) begin - if(wea) - mem[addra] <= wdataa; - if(enb) - rdatab <= mem[addrb]; - if(enqb) - rdqb <= rdatab; -end - -endmodule diff --git a/finn-rtllib/memstream/hdl/ramb18_wf_dualport.v b/finn-rtllib/memstream/hdl/ramb18_wf_dualport.v deleted file mode 100644 index c7850106ae4cad21f1230477ee86062411e531c8..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/hdl/ramb18_wf_dualport.v +++ /dev/null @@ -1,111 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -module ramb18_wf_dualport -#( - parameter ID = 0, - parameter DWIDTH = 18, - parameter AWIDTH = 10, - parameter DEPTH = 2**AWIDTH, - parameter MEM_INIT = "", - parameter RAM_STYLE = "auto" -) -( - input clk, - - input wea, - input ena, - input enqa, - input [AWIDTH-1:0] addra, - input [DWIDTH-1:0] wdataa, - output reg [DWIDTH-1:0] rdqa, - - input web, - input enb, - input enqb, - input [AWIDTH-1:0] addrb, - input [DWIDTH-1:0] wdatab, - output reg [DWIDTH-1:0] rdqb -); - -(* ram_style = RAM_STYLE *) reg [DWIDTH-1:0] mem[0:DEPTH-1]; -reg [DWIDTH-1:0] rdataa; -reg [DWIDTH-1:0] rdatab; - -`ifdef SYNTHESIS -reg [7:0] idx = ID; -`else -reg [15:0] idx; -`endif - -//initialize memory -initial begin - //note the hacky way of adding a filename memblock_ID.dat to the path provided in MEM_INIT - //ID can go up to 99 - if (ID < 0 && ID > 99) begin - $display("ID out of range [0-99]"); - $finish(); - end - //MEM_INIT path must be terminated by / - `ifdef SYNTHESIS - if (ID < 10) - $readmemh({MEM_INIT,"memblock_",idx+8'd48,".dat"}, mem, 0, DEPTH-1); - else - $readmemh({MEM_INIT,"memblock_",(idx/10)+8'd48,(idx%10)+8'd48,".dat"}, mem, 0, DEPTH-1); - `else - $sformat(idx,"%0d",ID); - if (ID < 10) - $readmemh({MEM_INIT,"memblock_",idx[7:0],".dat"}, mem, 0, DEPTH-1); - else - $readmemh({MEM_INIT,"memblock_",idx,".dat"}, mem, 0, DEPTH-1); - `endif -end - -//memory ports, with output pipeline register -always @(posedge clk) begin - if(ena) begin - if(wea) - mem[addra] <= wdataa; - rdataa <= mem[addra]; - end - if(enqa) - rdqa <= rdataa; -end -always @(posedge clk) begin - if(enb) begin - if(web) - mem[addrb] <= wdatab; - rdatab <= mem[addrb]; - end - if(enqb) - rdqb <= rdatab; -end - -endmodule diff --git a/finn-rtllib/memstream/sim/gen_memblocks.sh b/finn-rtllib/memstream/sim/gen_memblocks.sh deleted file mode 100644 index b6e6b656ad1ea7846666108a1d4b79eae295490f..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/sim/gen_memblocks.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -NLINES=`cat $1 | wc -l` -NBLOCKS=$(( ($NLINES + 1023) / 1024 )) -rm memblock_*.dat - -for (( i=0; i<$NBLOCKS; i++ )) -do - START=$(( 1 + $i * 1024 )) - tail -n +$START $1 | head -n 1024 >> memblock_$i.dat -done diff --git a/finn-rtllib/memstream/sim/golden.dat b/finn-rtllib/memstream/sim/golden.dat deleted file mode 100644 index 1466271bcab961f742e242aea4241a4ffad02828..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/sim/golden.dat +++ /dev/null @@ -1,9216 +0,0 @@ -AFB2B66A -BB100CFF -1ED93E9B -1B8E800D -DA9E0150 -38B1C916 -93BC4E64 -860F8373 -B31D708B -C2934023 -739C9593 -4C898A3D -CCC8F4C5 -8FA275E6 -47732CC7 -6857ABF0 -31671013 -6BC4AA43 -73D4F790 -2C6158B6 -FDC3B5D -6DC755F2 -E0E7E8C9 -7862E17 -3D4FFE1E -9AFFF447 -C862FD7D -A4C4D89A -D7D6EF51 -10E5A31D -79DA9C63 -A83060A8 -EA988813 -6B411BCF -85544B5A -5AC91DE6 -586E6779 -8FE8161B -4C57CC92 -74C918A6 -36B20D44 -5CB62FC0 -62FDB2E1 -4B1CB514 -526B7CEC -B3FA61D0 -C95DDBE -CC2BA600 -2466CD1D -3354A056 -CCED3EAC -6FFA09EE -F9648FAF -18CB5358 -EA506270 -66F385A6 -5B0246E5 -26218A76 -BC7CECFD -5969F6FF -3DAF5901 -C53D05BD -1EDA2D76 -5C0C0010 -7A6C0C8C -BF99E997 -C964C884 -4DE417F4 -8637312 -133B8C3A -D637DB88 -297288F6 -CF1D00B3 -426BD0F3 -4D258120 -8F7EC898 -E15482D9 -DFDFC442 -16A5C4AE -7A6A14DF -5E9C2807 -31BD3EA2 -BD6DCDBC -E47CD35E -FA4FE42 -CCDE0036 -345EBCB7 -64686255 -AE1D77EB -D2B42B84 -CD5E5824 -8DABAB1F -4E07FFCA -7F3B4C13 -1A62C962 -CE08835F -E8E05318 -DC25C7BF -132E4308 -5D0122D6 -B7451ACE -829D2507 -19329C7F -39FCA8F0 -DCD1A574 -17E2EEE -B2B6583A -2181E65 -7013A2A7 -46535CDE -C85BF5D3 -2FD5EFC2 -E05C5D2E -244F0F96 -F01D711F -F1CBB67E -6DAE6666 -84AD6F4A -B95BC84E -9DD54B95 -5A7CA1B -7B1447F4 -44A8EDA7 -20929E9 -40E62E02 -3D03CC3E -81EEF8C4 -1E686D13 -17C13B3D -A14967BE -D8693E0E -15A7FDD1 -19F51C6D -249D0C21 -51424939 -BA05F551 -C614827A -32841A0D -2F8B041 -11A2806 -DBF24199 -F246D9EB -52FFB23D -F3061A47 -B6D51EF3 -2DE434C3 -E1D3F874 -85270B0A -CC405B14 -DD3E9F23 -A0352F98 -67EE5731 -96892C65 -6D67A443 -16354414 -17959F75 -A554F236 -C585076 -2B665011 -7D503509 -77A4530 -6A13C8DC -31996F5 -916AD400 -E761D000 -D23CFD32 -CF3A5154 -C575A1CB -B91ACDBF -BEE7F338 -44C26212 -8124CD5B -245F7451 -DD6D18BA -6B838EC6 -5247AB98 -2F41FDAA -A780BD3B -1FD2F95 -6CDA39C -C31FA5A0 -AB56A5E1 -87F50441 -47093971 -BEBD81EC -2A7F6977 -8C83BD29 -FB067DAC -5FEBDCDC -8FB43F72 -EE45FC6D -4088691C -34F235D0 -43AB8E4D -67FA8BB5 -FC2D2C02 -DA77044C -22E6FC7 -6B6039A9 -BA6E3C45 -46DEC612 -8E7E0FF7 -438DE467 -F4525025 -7937973A -9ABE4BEF -8F8DF841 -F74C5087 -7EDE1CA4 -FF3C7F98 -A025FE0B -59E5EDF6 -6DD27411 -65C080E6 -C86D872D -628B6B26 -B9316D56 -E09EFA8B -A8CD3F21 -C0CD8745 -F4D62BA7 -D4D7FB99 -E9174232 -7F068FC4 -767480FC -275BBBF7 -3470FF88 -E632ACD1 -85677507 -AE0E2C69 -E2C74DA9 -C307B72B -5FB5A769 -99C18162 -FAFB7660 -6E984733 -E17FD97B -EC5E6CA7 -3D659815 -30826B60 -300BE8E8 -86D0B096 -856F2CB0 -2A61ADE4 -24EEB996 -2FCB729B -8190CE0D -E64F7E6A -4D0D42F -CE29765B -C77DE893 -9264C299 -A200E419 -868B5EC6 -8452AC39 -59F7BDED -422E75B2 -74E6329A -38F053E8 -16F8BD5A -363A2E43 -8018AB7B -44AE4CF5 -C8F7B14B -52658A45 -7B46C7D8 -CD319C38 -19AC8957 -5F42CFAA -5DB4DBF7 -DF66DDBA -4FBCB611 -266DFB86 -4F0EE64C -1765E724 -E30C89CA -4705FCE8 -BB7636B3 -789EFEFC -AAC0F37F -424B1661 -234F05AB -1BC0ADF8 -7F9EC67E -500448E5 -BF4D1C45 -C5B64E3B -914F44FE -EB17F041 -1752165C -F5B72E31 -6D68C060 -4EF27C55 -8CEDFDC5 -E3996A56 -25C5C632 -430D930F -EE04DE4D -576E4921 -E13A2A6E -CFE21675 -B1067912 -4C888068 -3C3A1A6D -FCE12E0 -FAD6AD8B -F7DE2E0F -E8DC0DE7 -CC8721DF -34411355 -2C664D07 -ED034324 -F57FDA56 -8C70BCDF -3A6FF2C8 -C6440537 -8113D976 -A40176A1 -46D1D0D9 -877A407C -3FBCD395 -3E74C1D8 -72E22A13 -BA46116D -CFB14406 -21400896 -7AD34367 -2905F60C -C1F9C16F -2E0E5FCF -2EEB00A0 -9C2D94A9 -8DE1CF01 -5912596C -CF2CA22A -774E7D4F -805657AE -1BA223EF -236FD53F -C1ABFD4A -6B8DD778 -6A6E40D2 -70CF4F79 -950E8D35 -5E4F9545 -86AA4166 -28D056E9 -9C550D75 -CB435A3 -B875667E -F54E6E97 -BB7ACD6B -F11637E9 -C220E1FA -C7CAD54B -32853439 -65BA20C9 -1838F8C0 -C3CCE57D -7D2B69F9 -137AD6E9 -6C041B9 -296497AA -98C5E853 -D37AB835 -376764A9 -2F714011 -D24BE867 -B2BA4E -9EA785F9 -726FCED6 -6B4C6950 -44C6D5C0 -85DEA727 -733F5A86 -41785CFF -BB395E8A -100F8117 -276A08D3 -9268A16E -FBF63C19 -AA497F25 -E92E1DC3 -185B4692 -FE6377D6 -C50771B -D98BCD04 -50FC7D74 -BE5BC294 -2C9C4482 -12FBF6CD -D1E04AE4 -5C9679EE -889D2695 -3699F061 -933D06A9 -930DC367 -496D7A37 -C4161D19 -3E08728B -66388C70 -B2363734 -5D12926F -39B4AEF8 -1948B925 -321E08BC -27559FC2 -A543B709 -4D28BC0 -46C64305 -F7B7D459 -97C4966B -A027A9C8 -43CABFA9 -F7C3643D -1128AB2A -AA4A1419 -AC6F2B46 -8F6FEFEF -34284D4D -D951EB81 -77AC6B7C -70F6E0B2 -FD7BE3CE -77BE497E -4883FBD6 -FCAB08D4 -9BC032A4 -67DA8A5C -82037EC1 -E3EC6CC9 -481B7623 -DA1F3873 -CE9E8787 -785CD654 -1661CF27 -42BD0C3C -990F261A -49F18930 -FA336094 -FFD6FC06 -B71077A6 -204B911E -BA1586D6 -8A2F6DBC -36B184AD -76017CAB -DA7E891E -88A51A1A -97AC49CB -2482BE28 -CE6BD009 -C7776DE0 -4E960944 -64081AF2 -56512D55 -D6D1C640 -EE78145B -54CC5EE0 -BE5D3E1F -8FC8816C -1D6AC407 -5D98F8F1 -18FECC5C -F3DE9A29 -93A19068 -AB623B35 -43FF1A02 -AA26434C -B071FDD5 -45AB6A2E -C1275AA7 -EADA5CDA -E427C95E -AE6E5B77 -89F3CA30 -9648C00A -330A03A7 -20DB35D6 -AA9946BF -A0E3050E -DEBB5819 -5047E2E -9C8FBEB9 -6B70D173 -8A99428D -230C88FE -3B26DBD4 -8DBED704 -EFF1C946 -C2381970 -71087497 -2268599D -FCE50AAE -460A49E5 -EC65BC4C -5A83C23C -DD44120F -D6E81BEB -D10235B7 -9362A387 -B3C9220C -46F21F0 -3D04FBC0 -63A2B38D -8F7DEF26 -F326457D -21933DC1 -775197FB -8D6C7C5F -B2D7D570 -147F9FF7 -78666356 -BAB7D249 -69B45EC6 -F56634ED -34738794 -26DF0163 -188DA00 -D2035A36 -FFBB8062 -62852DCF -55FC882A -849388E6 -43BE6E2C -D53EA2A2 -A228BC21 -9112A960 -5FCDE2F1 -79F42B27 -8AE37179 -1D722815 -5AE6DD26 -A8531C6F -EF386673 -AC761B14 -23C6BC3A -488D93B -AE6B0D63 -A4F1CEAC -43F80A43 -D9681EF6 -BA959674 -CCB852B8 -D9F4D79E -6403622F -75FAECC6 -7F43813F -51FC7BE6 -896A3A28 -CAF31C60 -76000EE7 -C1135AAB -6E83B2E6 -2AED1966 -C4F88A86 -21219EA -8AF14AD6 -14014BA2 -BC0BE2D5 -78757CE8 -C09D83DC -6B2021FE -D5AD900 -3685A49F -FD8B4BA0 -7B005539 -2F0C36EF -B41DBA0D -1DCF61B0 -CB3DA1A6 -24C0ADAA -BED01B2B -59C8C334 -11CCA76C -6F962508 -ABE672A6 -3C281A24 -A6C3DC39 -A72517B1 -FBA81175 -9906CEE4 -E8177FE1 -338D0184 -CC6650DF -840D8CA0 -4C55C42B -6B40F9CC -57B7E7B7 -B7C42442 -4500E9B -8C788183 -9B8F5FCE -49D0AEE1 -426B2271 -EC25BCE3 -7D63A976 -2EFFF592 -32A9E43C -AF5AFA52 -3ABE1133 -35B75ED7 -8F4271A9 -725A6EF -7ED7EB40 -37BD3B -7A0A5AF2 -F6492D7D -C2856688 -9595C241 -C07F646A -7D394FDC -7A991B05 -2CE3AF30 -9929E6E6 -4AE66BD4 -F0F3D1A3 -F76F72E9 -6C2051E2 -72431DE4 -B1796A93 -E04FD748 -D19522B1 -71396A78 -4202F058 -4F2CEB1E -A186853F -8B4474AA -C679B644 -98E10D42 -E7CEB08C -733CA225 -3478B95C -A706A842 -9510B8EB -F47E426E -9A0A17EE -2DA8832B -E73536CC -E6CA4B40 -11A2708F -753AC1E1 -8C304DED -5FC83F07 -4F9A04C9 -E0737708 -9091DFDD -8E1B322 -2552D768 -7C894296 -EABDC081 -E3B2A37 -DEC7EC87 -37FFB6DC -2B2A0CD6 -7E797B13 -64ABD0C5 -1FF12252 -F81AFB24 -C16F1ABC -F0B5AAFC -F80281BA -E51C04D -EEF8BD3E -450A49DB -AC985D7B -CBD4D077 -CAA6370A -FDA6530C -20B71F06 -ED5A891E -BA51A622 -E9F8E132 -63C23719 -2F59EE96 -14D77539 -1A98FC31 -12FCC937 -F39AD8FB -3750DBA9 -564E45B -F74C47FD -1010AD3A -8BE0AED3 -28B27F7B -D5E8EEFA -DC0EFEFB -959F5394 -A10ECCB8 -5C366706 -3B82A5EE -74E377DD -9881CEF3 -D1A4BD88 -69106661 -B209B42 -B56EE86B -63F37839 -C5AB7736 -4AD627C4 -8A4C7E1C -F7CC6334 -3D6CAEC4 -A86A18D5 -8FD910B1 -972371C8 -A423E9B6 -CE8C76C7 -DF930841 -C9D4A7B0 -18521955 -F6F167FC -889F1625 -432C606A -CA5EB4D0 -AFE77C91 -EAF55F16 -6F9A9777 -33726C1D -DC7B1D64 -8031DC00 -CF13144F -84BF2AB -45F5FD45 -6AF06D8C -C50FBE6C -11B8A4A2 -16B780E1 -98033979 -8EFAAEC0 -DD984A5A -D6A80AFC -15C793A3 -EF458063 -B784551F -552CC380 -D1E05EBA -4A795261 -F2B25418 -66066848 -D935B481 -136D2C8F -7A25AEFB -7000439A -E147CC62 -68976C6E -69447DAB -C72506F3 -C6E3FE3B -4FB0FD96 -DB465740 -A254195C -B11EA223 -FC3C44B5 -A9A86F1C -8EED03E3 -24CFF3A -A1B488CE -FD75D002 -9FEF0461 -75DC6637 -B3D38CD2 -57C8F65D -C62026D0 -D6320A18 -5E961798 -80FE0097 -6DA57E68 -D1E8A3C7 -96D49CFC -A8D2DFBC -520D2C1 -151C3F1D -8180DCC7 -4461E43E -C895BF5C -18EE374 -33EA06D4 -75B9D006 -23B934C1 -C2E89F39 -444BCB75 -78077AA5 -ECA64716 -3C1E3FFD -F7DB9CEE -6EC313DD -9CABEC47 -675FA281 -16B8304D -3E38FEC -A9663BDE -8EF647F2 -B646C61C -2228E400 -2B411566 -7A72EB44 -88BD9AE9 -4EF4EBA3 -BCC822D9 -4668160D -695667C1 -CE51A675 -40DE9687 -877561EF -416F5AE6 -EF9304FE -34C1C9D3 -5B63E1BB -C50E9899 -1831810D -25DE2CC1 -10539A77 -EE51D9B2 -462E5A70 -B0F8C3B7 -CA16E410 -1796F2E5 -573F6B28 -E157A965 -2640969A -153B4909 -7FC1290F -ABCAC2F -2A42D17 -BFFA3865 -7B12D8B9 -9321F9EF -E560B7A9 -36E18DD2 -57710FF9 -FAE1F933 -F717FEF8 -E86BAF7E -D0CE3E89 -C8755650 -704BB6ED -6309F650 -E21DDB4F -7CBF531C -7E0AFB8E -D6A1128B -60F16A1B -534186AF -72971F2E -428A867C -F571D32C -CD522E7B -13F6443 -38CDC9EC -D01C51E6 -2E575D3F -7E86B596 -C1460B28 -1403B019 -76D89A66 -4F2D9465 -9B87B1 -172A00A4 -4669559C -105C8A19 -3CD2DD63 -EF054D76 -8B9AB48 -64136500 -71C56349 -B7AEEDF5 -4145D7AC -D6A3E4C7 -2F9E0DF4 -31E418C8 -D2C839DE -63E919D9 -2F4D0353 -8812C572 -B88E671F -54D2BBE0 -E166998 -B7487741 -64312607 -5ADF6F3E -31A86BF1 -D8A96C85 -22AA3021 -AD4719B5 -49EB0670 -93B76AAF -B109648 -FBC7346C -2530A7B5 -C8525175 -15EC0A76 -315FACCE -D8C21A6F -9EDEF96D -6495575D -722A0577 -51EDE2ED -8109F168 -6CBA0929 -1ED88DCD -D79A67E2 -CE62A29C -6FE2A87F -D1E6E3B9 -601988A0 -6A045849 -A7E30F35 -E0EE4424 -AA89C628 -33D7A7A3 -FCD27B7A -80CAF9A4 -2E7F1302 -69F19C -80DBDC64 -392FBDC -E5981A33 -B4AF4210 -1DBFDB9F -31E5DF02 -5C571556 -EE256151 -9F573818 -200D540B -87743240 -1335188F -5A1E9D1F -FA267CB -688D2302 -80D32C1 -195719E -EF151174 -772EEC93 -DD2E2E4E -D8EA362D -3B24FC06 -FFFCF7FC -C571F2F4 -A8DAC7D -3BA7880C -16FC184D -7DBC453C -8F355780 -65C7ED3D -2202E50E -9EC765A9 -9D8F8CDA -CFA71D0B -7A463A33 -AA94D750 -359750D8 -B9A4BEFD -B153CD8C -93AFB5F4 -2676E0A0 -78C0805 -347133 -3B229F4D -4486A7BE -F3A0FAF3 -D29E9349 -A62C0FB4 -574D3763 -BCDAEE6E -BA27D40D -896903EB -8AE6171C -A911D78E -970FB490 -33B8A631 -893F7E3B -700EDF9D -EA7AC6E6 -6041F473 -FC6702EE -F225A258 -96A21B4 -CCA94D4D -FA6D00B7 -35580441 -F5E42BA -EE9AB535 -50874EBA -4454B2B -30653468 -9ABFE240 -29A13784 -EBF5F88F -B1769BB8 -EF22637D -A2FEEE4E -4B39E8F8 -38AD4316 -A3FCB454 -7D6F402 -18CEA9F0 -956B2CCE -6559ADC4 -F00F696E -C878E2A3 -3AB31BE4 -FF2E6E3A -3767BE32 -37CFBCBC -C307A74B -ED6A132B -8D5A1B70 -774C41D1 -A45F1CA9 -3FCF576A -C1BBAB8C -5B11B23A -620B6C8E -A6F5CB83 -450BFF8B -FBB9620D -BD936B56 -2FBF9A89 -2E000CD5 -E508C955 -2FB99422 -5043B664 -1C43CF3B -2D7E713F -FAD8A72B -7CF2FA33 -8FDD90A6 -8B5CDCDE -6CBF908F -740425F6 -D142F4B9 -2B30DF9D -3808D354 -508C4729 -E6FB0279 -FA0F9DF5 -2FFA33E1 -8A93B18 -FE7C0855 -E69193B1 -AA7E4DA -DCDD121D -4E7CD1 -14C03D9 -ACB60232 -818C10F0 -D8CAA46E -2CBC53B4 -46F82991 -9B24E92B -E1DBF265 -C6649C -87D0CA2F -C24A605 -AEB470E -8DC36FE7 -2D6B856E -9B459A3A -5C204000 -C7CC0BA9 -E637D8C4 -1F8C7240 -41788DF4 -27B94DFA -BBA5B2CD -51E1AB57 -FB14B16B -B6821713 -F955BAB9 -44FEBDEF -A484D04E -FCC08A15 -A117E11E -CAE09305 -789A734A -338EAB60 -183825B -61931C6E -ECBBBA86 -1AC53895 -BCEFB579 -CC68D938 -217A4ED1 -3CC6F2DE -12E55EF5 -FAE1CE98 -CF89DDCE -8FEFFF33 -8C27552E -6D63AA8F -B094E27C -4E7632FE -5D9DDBD8 -8E2766E6 -2EF9333E -98B9A7D4 -20D98AB -C12C8047 -5995F2BB -BB30E14 -C769CC0E -632D8C76 -B7FBE051 -3170D046 -D595ACCF -190326FC -D1D03166 -DA4420CD -81FA57FA -D8615FD4 -33AEF793 -E2B32AB3 -E2B2D613 -5A37DB74 -EBF473BC -62C5F8CF -624D5D2D -9A9006D4 -8515BED2 -7DD650C8 -D0BABA59 -1E635B2C -690CBFF7 -E4028EC4 -E4E5B3C2 -57607B0E -D4087B2 -3C06022A -813133A2 -B206699 -3827A132 -985BF479 -6C11EA62 -F58DA68F -818CD2B6 -F204828B -64A0D011 -A6F07C40 -6816D54D -8B00F959 -3B6A1891 -EF20520A -B5B90BD0 -D70B3B4 -7B165E3F -FBE60B95 -50656296 -6250C189 -B50E29BC -7BBB35AE -124AD7B3 -BAD38F67 -A0CA136 -FB03F6CB -B88FB36D -9025524E -4EB80454 -D07FEA2B -D9385E1F -B1EDF69A -11D2AE5C -9EEC00C3 -55916263 -AAD5CF88 -2740548B -662FB2DE -173DFA86 -8D734BE9 -D4A27E13 -E92A39A2 -A58A3F4A -A71CE9AC -B43ED5F -1600E2AD -265C4182 -4EA4F91 -1E3A0BD5 -62650FD0 -BC6E23A1 -3BF3E963 -5F6AFA4A -6BA2B659 -5C00047A -E8F81B0A -C30BF4A0 -DFF059E0 -4E3F93FE -D688F348 -3220541C -F8A72F57 -6D78CAE6 -AF13AA11 -BDB3229D -936DA76F -749DB9C1 -EBF347A6 -BBFA776B -6472B218 -6144ECA8 -E66CD255 -274BC846 -64C0C67A -95748CF2 -25DE3E48 -29A685B3 -CC8C7B15 -F18FA7CF -5F2D1C01 -6DFEC90F -CF834DDD -A72D9439 -BC6D83C3 -9F888C34 -385D225F -168886B3 -98EF8EB2 -BD8ADDD1 -80DA0EE2 -F4196AC8 -6F020F21 -61136480 -4DA28475 -86A506E0 -1A75F4D7 -222C4645 -8C4486EE -98560E3C -944205C9 -D5E0BB3C -C9667421 -2932030 -BFE65EB0 -FB463370 -9FE77763 -DE8ED32D -FC9BDBEE -FD77E3F -288C605F -7475F3D -C3F75513 -C5AF2C40 -40FB62E2 -2C7C83E9 -A8A7E6CC -512E4560 -950C9D -EC507007 -65B7CEC6 -4A91094F -3BDA586B -7029FB6E -739B556A -678652AD -7B940AD3 -4A8728BC -76841FC0 -F53DEB4C -1B13B0F8 -80A5CFA8 -69C8B602 -6F984889 -14A53B17 -409BF6B7 -46D597EE -3502ED7D -315B1DE7 -E785791 -21871730 -78BE7E05 -D1536BC0 -F9708FE6 -EE4E143D -4E498B00 -A2113F88 -630DFE4E -3FA3D4B -F88D623D -3ADB0736 -BF25AD18 -CB89D619 -1D41D458 -EEFA6367 -7671EBAB -B98E8CFB -238D9F19 -C5155B -223C16B -E484FED9 -DD6A6680 -5192089B -CFF24757 -F2CD17B3 -CC3C7B1C -581E6ED2 -C2D7E5D2 -E9789543 -424EF913 -E6B10C7F -706C0B16 -6EC36BE6 -54C41CF4 -CD1EAD0D -17460ECA -452A78CC -D680E5A2 -57AA8EB1 -252EB084 -9DBB8E55 -BF759D75 -6E5E9F27 -30EBEFCA -C4514A4F -FE76382B -99A07A25 -F9017D0B -452226BA -3DD6111B -967464D -C0BAF41B -C4D39425 -767A57E4 -7183FC19 -844A33A5 -54F13F7 -C5854DAD -BE406FE9 -14340FCF -F665DC28 -701D2EA1 -A7B6AC6C -AC3167EF -C3CE6810 -C6844D77 -64887D7E -4EFF4E1C -8508CD3 -45CD4361 -3FAB9023 -9121F935 -46C5C6BE -272C83A9 -24762973 -EB858013 -FF2D23BA -6F5C8026 -A045E967 -7B844395 -2611E8E4 -8AF4659 -89FB4D33 -D9F50DF4 -CA6BD0F6 -A47A1386 -F78D3515 -2E73ABAE -36C0297B -DCF0FD32 -3930C7E1 -246799B2 -BF8BEEAF -7AD6D40C -7BDCB9B9 -7829D32C -EC826EC9 -ECE1D576 -4E3D613B -DCB44DB2 -67EA1BF2 -D1DE75BF -4609E175 -423132A3 -D33DD5F6 -D74829AF -FE0FB1F4 -C32939D9 -4FB97597 -1441DE62 -649D26B5 -4835C073 -1F67EAE0 -E28AE826 -DB808A84 -58FD0074 -1424245 -6BD9E7E1 -26476595 -E8C08661 -F1F0D3D5 -577263A7 -CB86C426 -EA57839B -C8B37BC9 -FBD2B525 -D033D0BC -A3A0474F -22EDE40F -CCD58291 -CB64AA7D -3176C162 -78DE2512 -ADD0A1B3 -EB41F141 -A7B5DAB1 -C68652ED -1F8E90D -31578AF4 -CFA12A8A -E20A88F2 -74AA9676 -3B353B5E -1956E731 -AA8B10C0 -63369269 -C833A9E5 -9425A8E4 -89DB1783 -1BE23F63 -D84221B9 -F8D9FE9B -EA1FD309 -E16516F3 -8F0EA801 -F5256123 -F21B02D8 -F3335520 -F7729F5D -B7F2AF17 -6B97F182 -806347D9 -962A011D -A5427014 -B7358896 -E9D6A1C6 -2E3DBDE7 -94B06EA1 -4B3D9107 -26F1956B -1726E033 -6660681C -39E4E3D5 -E8CD4742 -78D71E0E -15733521 -89D0606F -D449755F -A2753DF9 -AC7ED71 -7803B9A9 -87CCA2B4 -23003317 -2A91CE6 -C37B28F5 -CD9A436B -893C12E2 -C1FB04FB -3D8230BC -737002C2 -15314ACB -F4D74B95 -6C8BCBFC -292459A8 -1692BDFF -DC68FEB8 -48DEF854 -4BAE6B50 -8B850B23 -AEDD7125 -5B740DA0 -AA83A652 -474C59D4 -A4B2D4D3 -451C3B83 -D93BD101 -BF10B243 -8AB74771 -68C5891 -C8EE35CC -D22DC638 -5C7FA2D3 -54A2001A -747538DC -AC75ECD3 -F1BBFFB4 -844C0E4B -D7D25E9E -460EC0ED -688BA8D7 -CA6E35E7 -9396DBBA -3E9C3E0C -5D29B720 -3E5BB85D -F1CFA9A -8EF00E21 -28669B1B -98BE145D -2696E360 -F91E3763 -B0E3F6FE -45699C1 -F5945549 -2CB64CA4 -F3508C44 -653BABD0 -773F51CB -9D228D81 -E4FAB747 -1DC767E3 -89A77290 -8E2A722 -45D00328 -42E979FA -C19D28EB -C6645B54 -5AD41E9A -93587C5A -719944B2 -B10FF0A7 -A57FE070 -78C8DFAE -138BFBAF -1126A4D8 -C9DB256B -EE01D5FF -A8EB81AB -80AB24B4 -95B129FD -802078 -A6F71D37 -334BFF82 -32678187 -4AA896B0 -149226EB -5B8C446 -D1799EBD -74EA35A0 -FA9B52C8 -FAC6A436 -9E543685 -C1184EE -2D8CF846 -C2AFF300 -18EED386 -80C04036 -77FA6FF7 -5D1512F0 -D2C0C9B7 -22DBA873 -62468BB9 -42C90933 -F7EA7A3C -69449140 -7DD1B0F0 -52AAADFF -2F8B7479 -70B719F9 -CD8E1081 -4B46932 -DB933B74 -1E7A04BF -75DC735A -C3925701 -7EC84718 -DFEE049D -E8B3328A -3A9936EE -F2E22D2A -1F2B5894 -DB44DCE5 -4F1DD5B4 -B66F3E9F -943480BE -ABA71BB2 -E4F15D5B -4C9D7A9C -B751518B -24C9762E -F9DA3386 -D13AB9B6 -5CFC891C -CBEDF3E9 -395421ED -5A3570B8 -1641D0A0 -AF9A9981 -A07CC659 -4BA92C0 -D94C7431 -AA749489 -372456FB -690097AE -B5EF28F3 -1F8F313B -6C45ECE2 -24F4CAD9 -40C5200C -920AFACD -A2E0DD6A -CEC81C6C -DED2D22F -4AEA1A34 -7504D5DA -1F8E8F02 -72100835 -BB4AE282 -A0154848 -EF3ECE2D -6DA87A1A -46D17BF -DAE80D31 -FA8CA757 -8F75F943 -AFFB5EDD -F1A09255 -A80EDAB5 -5AC04A14 -B51A2E1E -FD9C51F4 -F99A5A90 -3EA5F0D -C4D40DFC -C0280AF9 -CEC83127 -FA1A5F6B -D603510E -3663D878 -A79682FB -B7313271 -7E37A2C7 -A1CB289D -C51B6F15 -EC66F0DA -80D5C268 -F3A52A28 -E056F895 -4A0A2418 -66E47974 -8E8CA911 -FD7E6D05 -70960317 -5D378166 -3A2D634 -CA6510C4 -93BBB6AB -4FE2CF83 -2273B7D4 -E372BB74 -8AD6B40E -496AA885 -11F4186 -8DEDF498 -5435E535 -5145EF8D -44AB3DF -7B449D2C -3489063E -F0A61E35 -A2F75775 -F691A0D2 -9CA997F2 -D64FFFB7 -DA79CC6A -2DEA4171 -D2E4D598 -C641D01 -79699CD2 -49FF5A89 -C967A1C4 -F4C7FF25 -9CD04F9A -374C3740 -7B6376BD -ECC505A1 -E76F3618 -42C0B205 -B28C63BC -2BA4280E -7278103B -83B861F6 -F862D563 -433B3F81 -358E4226 -2E9334B5 -2E9B7324 -23BF3CB0 -1E44A323 -BAA2480D -3B8483BD -419659C5 -91A9B2C2 -82574F8 -28A32CD0 -3534C89B -759FD52E -B260329C -82112334 -2D5B7F7B -816C0227 -ED5FAD1D -7BDFA5AE -B5C8006C -BD9691EA -36C28C33 -B8702558 -EB3E656A -D752A865 -FA94FF5E -AE5D43C3 -747587AD -6E5E5C96 -39312BCE -B13B468A -81543486 -1B57D2B3 -4D3D70A7 -2D4ECFBA -640E83F8 -4FD1588B -4EA4599A -E231E4F0 -A2D4437B -47D88CE6 -D048C6D1 -4CA7F923 -E9E435A8 -E93D6805 -C032C4A6 -E15934E3 -CB728ED0 -E7D65CEA -8E5D2F8B -1676D174 -B42D23CC -A1462E09 -CA718E2A -F5BA8F57 -EFA467ED -6DA31185 -895FB4A2 -649A7D89 -3B71CFA2 -C67F9D02 -DFBDDF09 -AAB8BDDB -870C617A -220F7717 -795DE75E -5C787D87 -BB94CBBC -99928778 -9D5C4DAB -4EEC433E -F4C08960 -F71FE87B -BF78D7C6 -671FB341 -4EAD6A0E -534B1D46 -1B4DE7CF -A7B45E06 -97F43041 -4B77382C -61EBC96C -336A9206 -E2A6FD02 -72E6EE51 -26144F77 -DD22DF66 -CBAFB596 -B9CE864D -CEBC372F -907981E8 -A9FA3C97 -6B1704B8 -B1160637 -FE603AC4 -274C6ED5 -6C317434 -77A16703 -2489D28D -2DBFB899 -4A3D882B -E81AF570 -1B8F583E -F1CFA601 -C7B776D2 -A26651A3 -303D5E43 -CD80678 -7E9DCEBA -E0F128C5 -4B1807BB -25B10534 -4117D98B -95079C39 -58C7BCE2 -AE0AF4E3 -331A0152 -DB3D821C -F4F11B78 -E2F55DDF -15BF23DA -15E7695F -1F40D321 -128A49CA -2D25CD8F -AE762164 -7EC8AC49 -1D9A1899 -97B6BAF0 -D7E07736 -A2566738 -A903EE89 -67CD354E -89C1C57A -97B3EF5C -240FC35D -52CE3A2C -15E8D7D2 -6A8A9E32 -4254550D -A345B8F1 -464C5420 -FD2E1DB2 -C629DA54 -81D24EFE -421E30F4 -E4008742 -62839D68 -AD78257A -23DBB6EE -49DAE0F2 -B1B07AAD -EC7791BA -3B4D3E2F -C241836D -C836E98A -EE9D6DA5 -33B5A570 -81D50D38 -6EE68232 -76677B3C -AF355302 -D2415D7 -1510CCAA -A6627F82 -A5A96453 -CD0B833E -5CF4C1E1 -C14866A -AFB8FE0E -B7D08BAC -4CBFF97E -F0191C3D -4E2A3EC -E76E048 -FF368683 -F4DF51 -8D0F29CD -91E431F5 -B6808051 -927E3404 -6ADBDD1 -5852A1E9 -394DFE4 -8990BE64 -A69026EF -3656791E -63C5AC11 -B9E88670 -9326F9CC -414EFA53 -B5028CB5 -22181175 -3B1A49C1 -22FEDBAC -A39731D2 -9C7E2E87 -E931F133 -D9AFCE3F -C2CC527A -A85B19BB -C66CB9EC -93558B54 -F5197362 -7EA88969 -B380F206 -56AC8890 -56D0C8A6 -B39C42A6 -7B966768 -1B6E37E5 -43429273 -668BAF0B -327CE28C -CEA34DC6 -EA727DD9 -2C1AE3E4 -802A7A51 -A1934827 -1A18C4BF -AEB9CA99 -D572EF76 -18DFC210 -11A4385C -671ED0D6 -D1E5D02E -9EE0AE12 -DF1EC812 -51BFF4B5 -CE089E79 -CE4BADF4 -75879327 -C98B6178 -D7B1E852 -95D6767 -1283D091 -20F90A2C -9020BD75 -504D84DD -D8982F3B -E41E0CF4 -55F4FE2E -2097DB6F -4B8B7790 -F3A1E487 -F4C274C1 -3452A00A -15587F21 -687D0671 -7EB3715 -945B9A90 -8C83F0D1 -8934F9BC -38A50D8A -7EF49EB5 -A45D34E3 -6C014201 -D4D19185 -821E216B -569485E9 -6DCC7357 -7711858C -852AA907 -591CCDF4 -775E7DDB -9463CA74 -DFF1EFEC -1F60E4B -2628AEE4 -EC89EF52 -49D232FB -E8BD7DD1 -EED418A8 -C35E3A33 -5C739CE7 -979E4B23 -B386E4FC -62F98F10 -2FEF090 -599508E2 -F3F9F428 -17A18287 -639B700A -AA9AA4A6 -B1AFC9E7 -FB6E8D34 -44F6A6D9 -EEFB7788 -9D616EA3 -78F3BDCF -A5E71361 -1D25ED7E -9059ACA7 -89118CEB -BDE78C2E -55B9E0E4 -FB6B9A -2DBAC44 -85C0DEFA -1E222914 -2413FBCA -C8569486 -E757EC3C -5ED9DB70 -3EA2086B -F4A4057D -E29E1B00 -C271490A -525A60E4 -9A286CE0 -61A42BC0 -D3F6ABE4 -9F31FB75 -335ADC59 -9EA61808 -232ACBB1 -270C7B13 -6EA6535D -F1D1B1A0 -AE9088BE -D9E4FD87 -3C8C0972 -5EAA57A -26997EF4 -3B02B885 -A4722715 -434BE51C -495165DA -BC9FC978 -18D8C1E -328203FD -12643D32 -65EFAAAF -71297EEC -EF8496AC -E5B7BF16 -2B2C5A0A -86B713DD -101E03D1 -14F4FB7E -34EBDF2E -2A9F4CF5 -7143B386 -448716E5 -C61C8469 -5F9F797D -6A89B910 -548E4139 -C48968FC -11F52973 -E18DC2B5 -7EEDA069 -2EE38156 -B8F99E97 -E066E1BB -ACC5C04E -6E645848 -98CA4890 -78191984 -84EC83C1 -C58D9987 -3AA63D1C -E17CA75A -CF8B5E23 -155BC19C -5809C3C5 -E2A7DAE3 -D55C1B6A -585BF6D2 -5D192255 -310467FC -ECA8FE97 -4ACDBA8C -E6319F8B -FD4F3E85 -47FF7B0 -B6FA3B69 -D75D49C2 -B831D3F4 -1D6282B8 -E335FE0A -C955B98D -87968F47 -B9600C1 -805AB6DD -2677ED62 -86AA7680 -836DD1B4 -82C073FF -F2664656 -DBE8C3BB -E4DA24B2 -AE14BE60 -1CF178AA -F2C661B -9ED5C4B4 -3B67F448 -426F85E0 -40195BA0 -66BDEE57 -3A128638 -A48D546B -7DC7834 -C7706566 -1E23F578 -CF55EC28 -F46031E2 -CFDD3546 -6CD58E9C -C40E02C2 -19558D54 -46E056B2 -C1581093 -20C057BD -34695F72 -1C4B7B13 -2FD3155E -152F2F86 -189E2F15 -31991472 -1B85405D -D1F72A1F -8AA93824 -CE409894 -9F6D30AD -E72C6DE5 -A31CC799 -694EB42E -C2D96633 -7F4776D2 -509C0781 -6A84F278 -E11739F5 -CC5EFAC4 -DDD81D37 -6960145A -E40C5DEC -70C068DF -1E6CC338 -592EDE93 -A19B8534 -DA27B1C9 -608D85FD -63AAE798 -509A13B -BAF29F05 -69342538 -5A2FD47D -5FA22C82 -AC7E3397 -4E546537 -4611C427 -DA39FAAC -445F1CE8 -5BC83B69 -64AB6C7D -F2B4EFB5 -DC0016AF -987EDDC1 -3354C952 -A5B9ECBD -E5B77548 -997279F9 -7C460F6 -82A1099 -B7CF0472 -ABC3726D -DD4155C0 -319B8C50 -CAE7E88C -910F1C5E -B1367D8E -56B78305 -8F4CB7A1 -8765A3AA -89624EB6 -22DE29BD -A12D4C67 -6BC56ADC -B587BB0F -3806EC0 -3C269C48 -9EA289A3 -B5EB4FDF -1ADB0729 -A991429C -CE574FF8 -CF071DB5 -CE0D372F -3D99AE5C -D6D56E7C -3A493434 -86AC7C63 -FAF8B585 -B9F1994 -89CB3A3D -7C8974F7 -2169640E -D74D62DA -8F0D850D -3B9D0225 -4E2CBB6A -BCA7006 -9DCE6E7B -3695D660 -EB344960 -F3D223F5 -6B8CA588 -45744961 -2F493968 -E9CBD376 -9B0FDE95 -F17603FE -B0825FF2 -5B1CCD35 -6F98639D -5CBBFA88 -890B3C42 -2DD4CA67 -DC9513B5 -A7B91C22 -83A897B6 -399ACDEC -AD11B2EF -11D76C5E -E170FB03 -9326B999 -87845BB9 -CA14B73D -943FE9FF -341ADB81 -D800A2CD -A7265DEE -1E7F3F7D -8AC49BD1 -CCE49B1F -58764B66 -D57DF0D7 -229BE279 -42DB683C -D8530314 -F1FE931 -DE1A4EEB -DF35B43B -3E90F80 -B3934E4A -FD658EFA -E6CF1CFA -472B47E9 -20F155AD -77571441 -9FE03233 -8BC0043E -80E9B238 -D325F7D2 -F0333147 -FC86E62F -A5451DCE -D9374B52 -674D4083 -9952E9AC -B529BFF5 -B7E072D6 -5BCD2886 -8381AC4 -5CD6C7FF -F24E3549 -9EBB5EB9 -23F47A79 -49D578D0 -6CA5874A -2F3C83E6 -D975C720 -FB484F11 -3BCFB5C0 -3A66DB47 -B3BB4F33 -D5136C2 -D4AB89C5 -8A782859 -C8FE9ADA -B5D57BA5 -9C8D2781 -7D0919B5 -D362A6D6 -1006FFAA -3BB31D71 -7709BEE4 -8A348C59 -44A704D7 -96F2AFF3 -592DF706 -F3247289 -3E9BC2A8 -570D8349 -2F615AFC -B3802616 -B54191C6 -DD155718 -455945B6 -C74C7DF8 -232005C5 -6185D2D2 -8FACE1C -73D27EB -770D2680 -DB913D28 -90FC0FA5 -9DE358EA -2BD3287A -D5C8095A -DE541F30 -D10F0F61 -4657627D -739F2E93 -F9F7B479 -DFC6490 -3D554A13 -D3C6C2EE -80145765 -D601408B -52EFFD8 -A44B597A -9E65E39 -2A5CB536 -A0420638 -EA752AFA -A7DE4743 -18480882 -A559B83D -2DC4B6C -8F33055B -7C4E3B8D -52C7F9F7 -9FFA0A63 -A0413C90 -ECA35002 -AB4A7AD9 -A829613 -71904BCD -9560A35E -118EC2D1 -CA730775 -A631E447 -F526588 -C415CDC9 -DE509745 -C2C64E6B -4A3350CF -CB04DB23 -8D3BA4E2 -3FC18EC6 -C8CFB2C4 -C2B600BF -FE36BBA5 -EB4B302E -F2BD24D2 -A820E2B0 -DDE54189 -744E33AA -9E63B141 -21C2E601 -2C12D5AF -85AAD794 -EE1F97C2 -9096006 -14132FBE -FDDA365D -E3623A52 -9F52F94C -18F84D8D -F866F6EB -9759E208 -38195047 -E31F1936 -9D7E9182 -CEC2787B -975EB96B -12F202B -CA36D8E3 -A694168A -F033E484 -DAEA79C6 -C465D02A -154EBBA3 -FFE408B5 -977F7FD7 -59992C2 -72DAEF3B -47AD9078 -11CEA76E -3B88B352 -BA2FF2D9 -2A7F4E47 -DD6B398A -164FCDDE -CB7284FE -9FCF9606 -34406791 -104CC89C -A2F32BB7 -213E9CB0 -1E1E0B37 -7226FA86 -20502886 -4C1C9E90 -2D4D0ADC -D843214D -57730409 -614341B4 -ECF30446 -330F5216 -5FBA2C4F -B4102EF6 -D6129240 -7D5DFBEA -EB01FCDB -7CA7342 -46DFED3F -5BE1B2D8 -2F40EF9D -59622E77 -A6AEA365 -78133A87 -7FEF9106 -3956BCC5 -8C6509F9 -79525FD -D3A518F9 -A76193BA -3F552EED -F974C309 -12A5B04E -A71DD6D4 -D9FE2B7D -95F822BA -EDBE32B0 -92BFA916 -79899BA5 -3FBDC933 -BC0E7C30 -6D7FEA47 -1F1954E -4F2F17AC -F6EA71E3 -B8E34FFE -3BCD8BD6 -695B7934 -D4CE8358 -26B0699 -784EC0DD -625BC98B -8861D087 -44DF0DE -35B7517A -A8FA9A12 -244B927 -AF7A58C -BE48CF00 -95C13C21 -9D8DBCFD -AE8B4798 -ED04535D -47A2219C -C8B87734 -8355D2A5 -B4127CD6 -DDA3394A -36846F2C -F38282D0 -177D3FF5 -EE8924CA -5E6CB3D2 -1F6C2C7F -3EACD843 -51A77194 -51D89AA4 -DCC17C24 -DB5043E9 -25D52B74 -1C7176E2 -1F483DAF -24B587EA -6188E94F -C886E2F7 -7B24254F -A761DFA7 -357C70B5 -6BC46A7 -31B8CF7C -BACB7205 -6C1B0387 -50685794 -7726ACF -64C49E4D -7AF06B7F -D1F2AD02 -E4F5BB37 -2A8A4925 -4245E047 -B7CD8000 -6C72A8DD -19590349 -7F7EDB49 -5DAF5458 -5EEBC5E9 -6E84757D -AD3868FA -F85A2B5D -A8569A1 -88F1F6BE -AF363178 -D9A61BFD -A2959EC8 -C1343E46 -B34A697B -22530AC3 -70213F56 -1DDEECA5 -4DF030F3 -78A4B8E6 -F93B20A6 -27AB7A7B -F43A2969 -AEB9E421 -75A8F820 -52CD9316 -CA166F29 -C28D14E7 -51E4C76A -50249FCB -3EDA432D -C6C3EEB3 -6CFF2A56 -5B50A9CE -D2CEB19B -2F16746B -1C19CB24 -9CD2076 -3F804860 -FE59323F -62F1F95 -2CF56FAE -E1A3437E -973F442F -DB62AE6C -C0AA4F87 -67224779 -A28378EA -6C5BE4D5 -97F75FF8 -49922E2 -19ECBBCB -C89000E7 -436496D2 -29C94230 -21A4D75 -3DF46E1A -A6D150BF -4EDE1CCF -37A996E3 -B0F73D3C -33E41F15 -14076103 -7BC6082F -E98E377E -1E787464 -16AB93F5 -B8E3ECD1 -4A944320 -41E77D61 -8B669E91 -20F1F65 -F4D26572 -81D9D4AD -99843F88 -7066E60C -4D6B9549 -C79BBF94 -F53252E4 -EDB94B9F -EA504F01 -9BE5AD3C -98F301D4 -C1C0ED35 -3F2734C7 -76351C26 -AEC02AAC -B9D4A014 -A01F14A1 -2DD27A90 -27C43590 -5A06F84E -64CC23AC -76387C33 -A07A8306 -3BC362BF -5ED88200 -CA6DC828 -4DBF3E47 -F633C85E -96F44176 -76B2A46B -CF414D71 -AD77A07A -9A1F71BC -FDEE86EE -7A8AC33B -AD3C257D -BEFBD214 -5B562E2C -3527654F -FAFCD066 -575BF8E0 -BC2A071A -C903C2CF -EB1AB30 -7B8C7CA1 -5ED6E493 -E1C822C6 -368B9DDE -91122C29 -5B1358F8 -6DCADBBF -ED845AC -61E42CB5 -732B420B -39154876 -C10442B5 -E1CC1A11 -875215B9 -AE9E4FEC -B2435F4C -DBC844A -10FDB0DA -F85D3FC4 -608B78A1 -DAE2B7B2 -DCD08039 -CC0962E7 -10602FA7 -62522FE1 -D3AFCD9D -2882BAA3 -70C31CD3 -A69E9A2A -975BB834 -2A35C91F -5FB2644F -69B2BF1 -9C365DDE -E4199E06 -ACCF8904 -DE105FEB -9C07AC45 -F75CF55 -EF6E3E9C -1FB088A2 -9A93BA86 -4E91C403 -E07827D7 -5F7593 -FC778EF4 -5B831E07 -354A60B2 -8D39DB34 -5C3C16CF -38489DCA -D83EBDED -F9E5BE76 -D2C7FCF3 -E868A2FA -D29E98A9 -5AFBCA1A -D01628BF -B2334643 -4EC99A5C -189E9585 -CC2B18FB -C692AC25 -A7F6B978 -C1530E03 -AC815E6 -6304151C -52EB83ED -C4921682 -96441A15 -56338D69 -5C82292 -FCA308FD -978D2310 -192DB3D1 -CA6B9EAA -7AD9F05D -E7C35D2B -AB5505FB -3DD6013C -532AAD00 -87EA4F8B -1AC88F4A -4BFC2053 -65356D9B -B03A54FF -6F585110 -2C75F6A4 -CFDC2733 -3E7BD30C -2DE068DD -F318385E -26CEC150 -532C4D5B -B264C41E -46229E71 -39E85376 -A074FDB6 -461E84CD -BADDA454 -77D4AD4E -479457C8 -F0E4F65E -DBA7730A -24D4FEE1 -9442683 -7725F0EA -F8647367 -5F4D5208 -6DC11B5C -4E65BE22 -EC0713FD -1D54F605 -4B0F99DD -E585AB57 -E14C5EA4 -B7909465 -12ABA66C -EEF519D -62F4CFD1 -48DEF31F -16B38659 -5528B313 -5C031870 -87ED6DE1 -55ACABF2 -FACEBE99 -3007B9E5 -F5C0C90F -E97F9A15 -951AE375 -67E41B2C -CF7F6BC3 -C7836B7F -88B077DB -DA60BEA0 -1FD6BE04 -95A08F39 -B7EA73B3 -10F6685D -A9C04118 -EAC17020 -CEEDC89 -7EFB007C -8D900B82 -4C2BCF1C -9B9BDFC5 -28846A96 -139B4D19 -32E0786A -72F19BF4 -66D61EB0 -609F7568 -3A785E09 -B6F2294F -96E73FE3 -99A0812E -1BBAE42 -9DF477DD -111FF2F7 -8A882B32 -2542FA4E -7BEAFF22 -405268CA -2427EDE6 -7D9F0726 -7EF6ABC7 -7F8DD904 -C3F2F4AB -213FB22D -62AD3732 -955CA4C7 -9E83055D -BE9C70CD -C0E6DDF0 -892D1B64 -56F3A648 -43547D3E -35EB967E -EBC18CA5 -D4DAC35A -9DDB564B -6DFD4F07 -CB02555B -425A1595 -B978D512 -B3D78E9F -A3EA970F -8E27124E -6A57B7D -26D405F2 -C8A1CED7 -7A6338C -A497AA49 -95602B8B -C6F1583D -CF5B6A58 -81F2D693 -A34B3C07 -B7180B4C -46C6E5CC -8C3736E9 -980482E6 -8A34B532 -B698520A -20E9DDDC -A5D8B27 -6A0B3989 -10071434 -C82002AE -8A343B26 -2FD61FC8 -C1257546 -FF154858 -1AFEAE33 -C2B1532D -D979A2DC -93F9FD3F -769B0DDF -4132C851 -A372D4CC -6A5532FB -E8F203C1 -A421B3A0 -B50F5C9F -AE5B067F -8CE6F896 -8BFFEABA -B0CCFB51 -D455681E -FDEEE781 -A4873A97 -E3FAC8DA -5039A29 -C703A1CF -E4E29AEE -39C0B0DB -DE5756E -303C7D43 -586246C -41ADBF9B -D1CD7207 -3BC8FD94 -7E50A650 -390914DC -ABD6170 -ECFBE529 -3D51360 -569802B4 -25F255D -1523D176 -9F98AEF0 -9DB1B681 -DAE01D8 -46D4F7B7 -47DD8DB6 -23BDB9D8 -90C47F30 -998BF564 -5D60F7E4 -309B5851 -9D246C3 -C1895130 -1F918DFB -6F303265 -71E0D0A7 -77F2FF64 -589BBF0D -A25C4510 -9F05AB6E -4990B583 -D335BD7 -6CBC0400 -D7894817 -36176CCF -1C6A98BE -53EE793B -4003C3B3 -9E46BEB5 -57647A51 -D5599FED -38156D3F -B1F425B1 -7AD6402D -74B619BE -A11B18AA -9C4211AF -DB076668 -7A94C4DD -6833F9A5 -A088A4AE -6A70BAFA -BC6740FF -B7F6508A -F3BAF225 -29BF8108 -7F074F1C -18B3D5C1 -8A948077 -BE0483D3 -46B195FE -D7AF0FD0 -C31414F4 -B5BD4871 -CFAC4C37 -57D2D42C -10A73F90 -407A80A8 -21C50A11 -22E165A0 -8361F9A8 -EDEA52BD -28F3650D -CAD63254 -9AB9033E -82BA1020 -E6E6A470 -9C829847 -BC3AB877 -A91A7C99 -1ABAB07E -583AD9D7 -9AFA901C -9AE116AB -27B4F5A6 -877D0225 -92DEB3AB -BAA1506D -EB04B325 -C275FBF2 -2331B6DD -74F623AE -933EC4BD -9470C6AF -6C0828EF -AAC0532D -318961A -29C176E6 -4011BAB1 -895DF78F -410AD703 -F363E54D -B4913DBE -6B5047EE -E7099A72 -E2961301 -E587CAE2 -1449E31A -EB048AC6 -D21BCEF -EACEF00E -EF09B5C6 -2C050BB2 -D660ACA0 -361BA74E -26D1A92E -10F1FD22 -DAD028BE -5DDB96F4 -A1C8F873 -66F44797 -DD6019B -618F707A -4E4525A0 -551B89EA -6A93FE33 -8219D90A -5E3E3FA6 -C9C25F24 -D4593D42 -CB12B9FF -B09814CE -DAF289CF -C59234E7 -6C96C435 -1E7337A5 -FE315E60 -451A4E00 -CC3E2B8 -EB1AABDF -B2D1AD85 -2A12A008 -B525A4EA -ABE700A4 -80603A44 -3E2E49F6 -48630509 -9673204F -7B0DEAD3 -B0B2B6D2 -68C0453E -BA31833B -4BD68812 -C64D0638 -A8987E25 -48850A6D -9B337E66 -1D99461A -D47AE0D1 -2E3023F7 -29CD452B -A211306A -15CD90B9 -D5D57C24 -727FA881 -51316FCD -BF62F735 -9E67B311 -51A2B90F -CF7C9936 -A537087E -3EB2EE91 -8F4D2C93 -F83E1906 -826C14F4 -6CBE676 -ED2DF931 -38270781 -4C567B1E -96BD9972 -E089656B -7DD03E9 -534E777F -695B12CF -338EDC74 -D5E3DFDD -13937C2C -A386AB68 -CADAD94A -B624A652 -9E4D0656 -3BDD26F4 -8B9D1ADD -180D5005 -E8744FCF -6CA71503 -20697624 -49269DB9 -B27B12B1 -AC181CE2 -9289684A -E5D3A21F -6A79B5AE -EE6DD5DE -355DA7A4 -C5B13162 -5FFA0324 -602F32A7 -85BA4032 -DCBEE18A -D76BFC80 -4B72BA0 -4101BC2D -A3CB1CE3 -4C6262A3 -59198E3D -AAD7C84F -4DFE129E -E8153DB5 -66EA03BA -D3247EB4 -750DAFC0 -68FB3A27 -67005B98 -C2255031 -1D9106CC -7FD4C833 -491CF81A -28D5F0BD -E2275FB1 -762FF58D -D9D940D7 -C6B5CBDC -810E0D6B -DAFD7E89 -15C3544B -D7B6A237 -3DA125A3 -3272795 -A7BCF9DD -4FE52CD5 -3FB69C23 -4F106EA9 -3632D2EE -9DA08D3C -5282D2C7 -9575F24E -D390A80B -2897EB0A -A4B9FBE0 -DA3FD83B -EAA2A95A -73FC7AEE -CCDBF4F9 -3EA97EA4 -A8AD7E75 -C533A490 -3FCE73 -D451BBF2 -6A71BE12 -76E1EC5A -1845E1F8 -CD2B7C0F -4D92E7BD -81B44E4B -65E1B458 -6B69FD73 -86CE76BD -88B1CA29 -EA1F0D7F -43D393F9 -C85E394 -B5C665F0 -AE373F77 -46196293 -E6057838 -7C63A634 -C3F66075 -1F15C3E1 -ED457843 -83F9BA3C -D8B8A399 -852DA2FC -3B81F785 -DFA3848 -877B985B -1C82BEF1 -6482EA27 -A4F94E9D -9FB72748 -47CF963D -C514BF88 -4D4B79D -232D2991 -3DEB3B5C -49784213 -9D79AAEC -EB89F7E9 -B9F9993 -71528CF1 -E1390DCC -F4655453 -97847A30 -3C30D55E -72649CB1 -F0647A6 -C6C8AC04 -FB48D1A -39EA9573 -70C70D43 -3F6BAD93 -342ACF49 -F37B506D -EE64D0B3 -4DC05CFD -79E116BD -5458D922 -3957971C -970D89F1 -9AF398C7 -A9A651DF -D3A64902 -27339129 -2FCC3329 -B1C70D5C -3FCCAD9E -C10A34 -80B546E -7EC04275 -512434B7 -526742B7 -E96DE8A8 -27CE6F9D -FD566C7B -8DB1FE12 -93F810FE -C660877D -348D5704 -BB3F2FD7 -9F859C53 -907BB57E -318DA95D -BF1CF416 -3E8BF68B -BB8CE4F6 -A9954212 -D1A396D6 -C33F5A44 -2DC0A59D -5B66EF45 -1CB288E0 -D6874F40 -E275F00B -E6B62E72 -6BB1EE97 -389CF9D6 -8C093ED1 -D4CB36E1 -12F4840B -F18A2F83 -782EB525 -12BFBACE -78F772C4 -91988F79 -55BE57F8 -6605D204 -5A7471F4 -355005FE -267A8C9 -CAB49590 -9479E9EA -BEE93B2A -34E95C45 -61788682 -6B99ED61 -33D4D3D8 -DD149E5D -D3BED775 -287B4087 -A2552A0E -477D609D -96765321 -2696E220 -3B6E26E8 -5CFFD0A4 -FDBF561C -4C41A4FC -B0637D44 -85DF60F0 -539171DD -9A1D1F12 -72ADB48A -D8C0C9CB -E4FE15BC -24EB5C50 -E1A9B3DC -360563C8 -F20C02CA -E9FBE774 -B2FEE97A -EF34194C -6DA8A0E1 -ED9FFA1 -4EB5D717 -47D296E0 -FA147414 -C1F868CB -761182D1 -6B9F8311 -7A99903C -95449FC9 -A349B21D -F2AA6E8E -CBD733B -1EAA2224 -C7CC9CD1 -DF3D1C7F -81343E5 -30682CA5 -65C5BDFE -811D5CC5 -8D2DEF35 -D8B4F4DD -9E121109 -FCA97592 -99E76951 -7CFB5D -8489CBDE -D7A8D721 -ADD1A5B5 -4A96DA59 -CE6C2C78 -17593D2D -F94AF7BA -6CE767D0 -DBCEDF25 -43629583 -CDB11A86 -BB630047 -8A579D2A -FC17AF19 -ED54597D -9BCAA00 -B7865C74 -BADFD092 -9AB0AF05 -AE371DB7 -EC0EE641 -A9781E96 -D1B8A429 -FE9A2043 -BA4C2CC0 -F243E36 -78A88066 -70925DF6 -97A35A05 -F18822EB -212A79D -666D7F82 -4558A3AC -FCF953EF -F8C6DD4A -C535BE4F -973A007C -4DB7E662 -C8995287 -B3527C60 -FA4F7A3A -D417AA12 -D861531D -11A81498 -5072EC65 -5886C667 -7EF848B3 -CA4ED80C -3DAEA7BC -34EC1028 -349C86EB -6423A583 -22A163C -339CC766 -E93138FD -7A79EA77 -E480913 -1220E06B -65ED8DDB -ADF487D5 -82CAE485 -A88E6546 -3A7F5961 -4672ECFA -425EB8F -AA3C4450 -44CA10FA -B1EAA942 -9EC93584 -E417CBF4 -B5F4C488 -EAB1DE5C -10446170 -C5F9C89A -391EF7F7 -10C62C73 -817FC74C -DA1A9F17 -FA38D673 -D2026552 -D7CD67A8 -4E0E21A6 -56812AAA -1D7294ED -575452A3 -90581C22 -82E00D73 -A8FECF07 -1CB1E500 -7F51D70F -F840E8D4 -DD73E72F -8DED415A -3F029F0D -C9CC871A -3388492A -AA1DEF8D -F2E93846 -F9CC596 -48221BB4 -6F7B2734 -F5A1010C -C0FB41C5 -8693416B -C8EAD749 -21ED8A7A -9FF52520 -613635AF -92C5E0FF -435C33AD -2550A70F -B17B7FE9 -9CC5F28E -690D4EB3 -5C5DCAC4 -25E14191 -B03B4C07 -50DCF2C0 -499BCF9A -5CCD6CF1 -ECBB2C48 -A2990792 -2105FDBF -3D62BECB -493AA5F0 -2CF5BAD2 -DFF53D23 -50D77C82 -35CDBF8D -E3BD4C29 -6A2FC510 -A9B2D0FD -404B053E -BF548C52 -E52081D2 -AD550AB1 -D4316A79 -776E6C42 -203A4395 -54DAB8DE -EB67FB95 -46E34074 -21679614 -C395F6BF -6D513D56 -93DDFEE7 -7D2866A -2283CD12 -12789536 -5C1F1037 -4170B23 -8BB451B5 -A9915ACA -784C0FE1 -50A95654 -CB574A -8A1690D5 -D9753D9A -3084718F -8E429880 -D1B7693E -A7613422 -C1707E97 -D658E57C -1C2A8F42 -21BE34EE -E545D5C3 -23DF7522 -B7AD16A3 -C6E7279A -2AD251D -FF0BA8C9 -E586EA40 -D86C394D -1A0D6737 -5AE27469 -8A0F53FE -1A0DC5E9 -8A56C2C4 -AD3214FD -DD999E92 -E53F55E7 -5AB39BDD -119C7046 -19B8238 -E21A4F81 -5DE3F0F9 -BFB5E145 -5020F616 -C2794F78 -9B7D9F3A -8FBBF3F1 -1D9C111C -49FEEDAE -1C83E386 -BB5B0273 -C290FD8 -52C788BC -86C12DD3 -6608E8F1 -313C6430 -142570B6 -F75B9552 -C8F1E8B8 -F3E5AAB1 -9E4D9E8A -7E48E48F -2182FBF -F21DC3 -BD6E45C0 -8DC88EA2 -D5B67DA1 -C592692A -979B0A6B -783D09B0 -C2231CCF -5CBB3057 -4C10986F -3F738112 -BED7BBF2 -A2577A6D -13128005 -3C71262B -BC8E920B -40C44CC9 -C6C4B496 -5AA9CBD6 -C7A9741 -2A8EDC58 -D2253A26 -F343439A -13F71CF9 -A4BB5CE3 -FB52ADA9 -1AF0749E -ADABA787 -C22B2194 -C5132023 -846C2188 -33A64D52 -E5CE9022 -CAA4C044 -E7032B82 -30251130 -22463302 -954AA98D -52D6F132 -11E0FDD7 -D62BAE17 -9844BF8B -68ECD60A -E637BA92 -1D7BA1A7 -F091F891 -CC96CCF3 -E2C50AF4 -149FAA77 -F16F7294 -27212569 -B96E1119 -E7806734 -15A5818F -4E05DAF0 -F022D5A0 -303D930 -B92CF71 -377DE596 -8835F16D -2D0B6E77 -2A89FF6F -9EA75369 -FCDF31A7 -8F674B8 -34D270E7 -BFE6FD70 -F165A645 -675B8D2D -318F8DAB -9F52E28A -A464F277 -B998CE45 -9E932DF9 -2918A97F -EA5C5130 -952FECC3 -7DCBA50B -DEE7C01D -96B96F4F -1C6106A0 -85A1AC4E -D62EECAE -6387F846 -271EB1BB -E1A2582 -D1E03035 -9EC6EA57 -300E10D3 -CB91419 -52652E8 -8291BE30 -E1D52680 -5044FC2D -35E58D3F -C6A01A83 -814DA7BE -97A50A83 -DB801411 -D4C43BF3 -BC3D29C -E4A072E8 -6F51D4C3 -21A5886A -F744A91A -5E12BC21 -F86FDFF8 -C320E6BC -3DEC9656 -F89A6364 -F668339E -44999436 -F40A8A0F -71837448 -B09D47B3 -2D2CAB19 -3FF04F12 -D8E5CC71 -33F39593 -160D74D7 -FB841949 -95F0E78B -B9A6102A -A4D3C679 -4774D90A -AC55693 -8F3CF617 -5BDA2B57 -A548BA77 -B1158C29 -FE9A4D00 -B52446D2 -E6DA1712 -3EFF4A4A -41EF9936 -D65FB56B -E3AED57C -BFF89053 -192E499D -DD703817 -C2B8C9A2 -65A8417 -670D3446 -2E936BCB -8A14CEFA -CF71A41D -842BD0E9 -628148DC -9733E864 -1C57CF93 -1A0CA311 -A1E13B05 -2C8F3844 -66C2361E -8981A417 -A4668A3C -271048C3 -6DD908BE -1A933D24 -BD0A78F8 -57C44DC3 -1EE04ABC -32275D51 -B25BCCC5 -509C83A2 -E5E1B85F -D45DFB17 -EF39D3BA -4F4F32D2 -8F1E52D -62A47A4F -7E4010A6 -189250D7 -CF3B51EF -5E9BE373 -E9719F77 -B2741A6D -CF19D7BA -993284DD -A1839978 -AC00E790 -ACD3A888 -1E74292 -6306A56B -F9EC26A3 -9FC5BC2 -2D6F22F -8CAAA98F -CD2135D6 -D2F5CD5A -CFCC3D48 -6AF7A18F -5A3EA067 -8DE9498F -A279E5FE -8C1D89E2 -5D15FE82 -AB291798 -40421279 -E101CFFC -D2D0D57B -5C977DF4 -68D4EF4D -22C36080 -81526010 -E5A41122 -160C517E -8BDCEC09 -5F12637A -F3714AF4 -D21C140F -B1EFABEE -E49A3E48 -E67BFC93 -C4BE9508 -21854565 -60757AA0 -FB5C43BB -150F6634 -115BE267 -3BE8F3E5 -EBF986EE -BA18FFF7 -82B52CF4 -50546F93 -118CCB96 -AA6603F1 -F434B7D1 -FC356F35 -C996ABD3 -CC8CF7C9 -4C2935D2 -2DC9EB76 -ECA4D776 -5D2D35A8 -7C747824 -ECAA990E -A6078345 -CF589355 -7E9AEC63 -859E12C -C2F31842 -6563A3BC -D43FE9EF -39D1717 -AB887505 -1AADAED9 -3D07A0C -7D2B456F -53C1B39B -DF349267 -FD9CC686 -5C1CB396 -89DD96DC -A0D8DA69 -F2A68012 -7F40A406 -1DBF2E24 -B31EAEB0 -5D5073EA -19C16D03 -10E50F00 -47D3D228 -A3C0E13B -5E801D5E -C58677AC -F6E9095C -E2C0938C -14CB070F -11B98703 -9FBA36D6 -5ADB369F -681BC767 -BEAE4008 -5A0AE129 -ACAD1673 -F9992AFA -2CA14EAA -F77F77B6 -2705BD3F -F9C3E6D6 -D3ED854E -4A5FB85D -54187218 -B9B8C83D -EBD38F57 -C0D17CF6 -8B464900 -3F8D26CA -C0FADB4A -7F79A367 -123EEC9B -99B683A9 -157062A4 -91DE43EF -65733625 -56DC9E5F -2C88A8E2 -83AE236C -DDBF0A9C -18873E45 -5040B3D7 -29927CA4 -B5A18202 -93CC4EA3 -5DC2F698 -A97A1713 -A104C149 -B9C5588A -AF182A52 -CFEC25AE -CB1C0A91 -143A132A -27C4A3B9 -D73DB7B0 -53AF7F76 -9A614866 -82A54DBB -D77A5A23 -AE3FA285 -8C2EEA1B -DD21D577 -186EBEF7 -DBACB855 -18E30376 -144A1FCD -773561F9 -F18F3C71 -4A13E021 -8738BA8E -1A9FF053 -56A546BF -860C6457 -9E5F2177 -B3CD57D8 -7A2CAF5E -F8D57DC7 -941CACB -E70A729F -7EDB09B5 -E972B09 -ADB7C542 -3832A659 -AF33DD9 -152082D4 -9A2A3452 -70B5EDBB -C6549E13 -D621FFE8 -15152F3A -7781B485 -67B0DEA1 -C787B62B -75B9A705 -C2A30FD7 -41CF8EA -3D2B2148 -CA0445C0 -802799F6 -FCBCCE57 -F539ADB0 -54952BE5 -B343804A -25752CC0 -3F276012 -7228715B -7F61944C -DCB8676E -132DC654 -CBA2782E -33016B92 -30F194E -F2D953D8 -15A92EA -495D2D8B -4366F311 -8F8DC099 -C4B2611B -D90839F0 -CEDA9833 -5CA78F56 -5D5F4751 -7F37FE54 -5B8F6537 -6B89CDD1 -6728B0EF -D2BED44C -60293190 -F41CF0F0 -8BF08F76 -861F32B8 -2053AB98 -315DF7D5 -58BAE934 -F38B7C9A -653396B3 -E2152002 -A4E66BCB -C1E3F151 -AE7AF50A -545F0684 -643CF8AE -BBC4B464 -7B8F849C -334A660 -3FFF02AA -7EFF666D -F80965DF -42D34429 -B8037A02 -36CA2FBE -539208E3 -D03932C7 -5C619FA4 -FC641E3E -D01051F3 -51DF9226 -116CF628 -8055029F -4A9130C9 -5A2701CF -89251BD3 -52D99785 -B2C16C02 -83581080 -57D8A09C -6D551FEA -EE6334BF -7D8061F0 -8556CEF4 -D9418360 -82DE39D1 -AA9CAE96 -8D3C1056 -8C67B490 -C7BA78F -D46697F3 -879107FB -88F4FC5A -E7B0C68A -3BD94FEA -648EAA00 -22724D11 -B6F00ECF -488584F7 -A104F52 -FEE79F3B -689DBC3C -2DFDA897 -411EFFAC -546F5C25 -45562F46 -C17613D7 -40CD3300 -9908DC56 -5AE62418 -4A3C1C82 -A28631C4 -4AA65060 -5614DE71 -6512AAA2 -5AE841E7 -B04094A1 -AA8F8123 -593A95CB -21919833 -DFFAC729 -106727F1 -273A2977 -85E6CD4A -E9751C6F -DC308E67 -40F7722C -1D8986DC -489D6002 -7A869A39 -6E02A88F -A04E30C2 -B98C740D -3672EB58 -9702EBCB -2CD4FB56 -A0CB2C94 -47299608 -6BB5451D -36EB4DEF -763593B9 -40029F5 -9392B153 -777DA521 -3125CFB6 -E60A4DE6 -98B9CB40 -819091F6 -83D23CD3 -ECE09D62 -22EE60D5 -29A3F86D -797C0E72 -1EC708F -76F78D62 -E527F0A5 -F11AD3D0 -BBF11E9D -5E944B45 -D090FFCF -4B8F7B5C -96ABDB47 -2F5379A2 -38FD509C -F49D4D2E -F5538B3E -BAD3E277 -E9C9831A -22D3C209 -CEE03CFC -EB55F3D7 -C61B5224 -6C4E6ACA -A63B52BD -695DBE54 -3C68D8AE -847F8449 -72B426E6 -95642CE7 -B021A768 -AB094E2E -90D8A573 -D3BFF1FB -460DD461 -EF32D23C -868AEBDA -6BEC2EC0 -34D18392 -6C9D6621 -6CE02624 -75E6AE8F -B5BE7494 -A033B3BE -EED6D471 -99D40A8A -BC742254 -530DDD69 -77698872 -E89F0ACA -39716DFA -C811D562 -FA7770AC -1F68B8E -7D325ECE -8CD870A9 -DE561FD2 -8D49A512 -979F1346 -CBC53E73 -E779994F -354561F2 -ECDDE60B -52EE9980 -46AC0C6F -555C8C8E -D382E1DE -2A9A602B -4F18FA80 -96068D7F -D1E5CBFA -957912AF -DC0A3107 -77CFB940 -E7161980 -EB44FE07 -C1597F4E -FFE737C9 -ECBD5506 -AF75488F -6D0BB14E -9ED0A181 -8EF54B6D -4E69EFD -9337A7B7 -A880D3A7 -97A5D09D -FD9F77A -7CECCBB1 -2869D0F4 -F1806C1 -F9FEB241 -7D368AA7 -FF972C5E -FEA0C745 -CC1413 -DD4CEA96 -FC8C6CEF -75727E51 -5A17C784 -422EDDB7 -6505031A -5662B865 -D7848124 -A93A9AC -D874DF58 -FEFDE7F8 -5B3E37E8 -5CDC346E -CAAFB037 -BF2135D8 -C6977D49 -8D61C84A -C6B1C620 -30AF013B -B98B3270 -CBBE51A9 -43E26F1 -99534D9A -11DEC7C2 -F3952B8C -52900E87 -80D2B350 -838A2A8C -F8BFC35A -AF0466F9 -CCFC01C9 -C4A559B8 -5FED8BFA -ECB87D1F -7BF187 -4662AA70 -1274E59B -41188FCB -A769BABA -38F43333 -D4645494 -3E464034 -6F3BBB27 -8149A2D5 -D3D96C7F -C04CB115 -DE3B6C40 -B94FC85F -E0E6291E -3E22885A -30D35E07 -81014DDD -A40ED586 -A713CBC9 -7E0CC084 -439FE695 -F4094931 -C293453E -741A83B0 -D9C2E5F3 -4E623673 -309436D5 -807620F7 -7DE3993B -8F31B5E7 -F12F65FD -66763A72 -D3606695 -ED7794EC -8BD7EF5B -5B3449BB -D9B93EBC -5CF89E53 -103CE7A -A1ADA14F -BD020E01 -F737C35B -8695E1B -2AAC416C -43B6BBD5 -31036C5F -E5A61222 -F3E01282 -9A93EECB -BA874043 -1D010D4C -3F45AF54 -662F04F8 -279C9BE3 -217787A0 -1D399000 -6669B218 -A8F4D699 -181ED599 -A584DCDF -97A49036 -C5D4A8F7 -3C7351B3 -E4A7A0A2 -9A13953B -A9649AB5 -E9B91DF8 -CA6E2F04 -F0B63E4F -C0F55BF2 -38EBAE63 -8D8A619A -1A798058 -E5C218FF -8B67C799 -A81704DD -2562EF33 -74B37ACB -B2C84D35 -2E0EC87 -5CAC361D -7FA10429 -DDC1672C -3574275D -A831D84E -65339BB4 -4B936FAF -8348EDC1 -B1802336 -601EDB14 -BB5E4EC -48CE4DD2 -4CC93BBC -E77987CA -6348CFF9 -90830A68 -1BF0414 -C2BC8AF9 -3EDED4A4 -66B38B85 -CD6A6E08 -92B71F79 -6BB2BA9D -B4EAF374 -5B723892 -C350B751 -D7A56661 -576B1A79 -C66D8E1D -442DA54F -ED0C819A -809EBE76 -413B884A -817EF987 -D76CDB84 -90F40F80 -2BEB3E69 -C2782488 -F07FF38C -93AD0DA3 -C3E8DFD3 -5B804608 -9CEFF79A -BC524335 -495E18F4 -7FEB37D1 -A8F15A96 -3AE50033 -9DC5D0BC -D4A241D8 -8F3CC38A -4573A224 -5A3DA58B -B446C862 -69EFCA93 -83B911B -CD50A370 -2E05D74A -407D2B79 -AD108E34 -95EA144B -EA3DE818 -7AF026A3 -21366692 -4D5B7972 -C7D14546 -B6EF2543 -48E7457F -6947E018 -F6B2DD01 -9FF698B9 -EA11BADF -741FB523 -70901C0E -6A71C468 -8BD95624 -1D98077E -EF7CE480 -21F44B08 -563A0A30 -D9165A -7F8E8474 -219FFBE2 -FE1D6D6E -F7B8D66C -CA49F15D -C481484B -85D5310D -3FF17830 -8F69C740 -590A3DE5 -867A85CD -21C9758 -2E625FDE -7CD5B8DA -8BF43699 -AA17B723 -C0DBB2D3 -617F6819 -4D6BE357 -A2D89B90 -C4B19255 -748BC770 -4BA5F90C -2AB43820 -CB75746F -FE7480E4 -239B7D6 -2567653F -7BD1399F -55A842E4 -572D6A8D -CD1600C -6C880525 -1C18F7EC -C9C74D53 -AB3AB21E -F5EA5F69 -F6F730D5 -FA454FEB -978E940C -64D4DE80 -2BB0D31F -10268273 -D060E295 -85A74B89 -A7A3AE03 -7B8883FC -D0615497 -9D637210 -105C40E7 -F9FB184B -B4E67A79 -373530B8 -30E04C2 -47A1D75 -A6A67936 -1B789F9D -AAC21CCB -E00A8B8 -517BDE82 -B1004DA3 -3F745A4A -8FD0E21A -529E48CB -BE6AE2A5 -DFD7DE91 -145FF288 -2B1AD7B5 -C2AE7259 -88B84292 -373D8796 -5E4B4FC5 -971622EA -3C6F40B5 -5FBCF21A -144B7DE0 -C588DF6D -804B7F0E -4B6714FC -C1C2E61 -1CB08E0B -6355112C -1912B0BF -22263C9C -954A5DE3 -4520505E -459D0661 -70FF554F -F1FED0C0 -D1F602A5 -AE5D07A5 -B86AAF05 -452536BA -B00C120F -1431099A -42F0959A -FF1EAB1E -9FD43C93 -5076B428 -ACB3DAA -5D0BA50 -16E00180 -90E21E72 -D497B8D8 -8414A6CD -B933AC93 -18B2DC20 -5BCC1468 -101CA9C -5AF125FB -E65A4FBE -A5B927FC -A8163208 -CBC14C7C -A00E7C50 -62DDE328 -3704BAEC -B354A1A8 -1FEFA49E -BFA928AF -73EBAEEF -F21664AB -B82DC773 -397C3EC7 -6DF7A081 -7B57E52F -43B47A0D -4BB8B26E -748CD62D -1D057255 -3A01A19E -ED35DB9E -B9192006 -9DAAEE03 -6F88BC5B -41F22AAE -DAF9FD8B -8A8D06B2 -99E4A71A -E0E5802 -AF2050EE -35D07382 -3CDB4F32 -1587CDF9 -29E0BC17 -F6641B4C -35557A67 -20B08FD9 -F89BE3B8 -994D534E -5084DC42 -B49E2B0B -25AD0456 -B05DABB3 -102657BF -FA7342E8 -508B7BD7 -FED0EFE6 -5EFAD4C0 -15101C27 -420BBBF4 -1783F9D0 -CA890820 -BD3539D3 -578ED490 -1DA8E967 -134F8B74 -D6C5A224 -8C8B1F06 -8977D881 -541937F5 -9013604E -4B54F163 -A9030FBF -A9EF1A9C -CB29FA97 -94A3F001 -4069BD15 -C0D5E43E -4E17F81E -90FFEC8B -32D0B0C7 -4044EC4C -7D7935C3 -BCFF474A -9AD1BF76 -2ED2D299 -263F8852 -4073932E -BEDCC036 -7A548119 -ADF45572 -7D8C451E -465569B8 -CA9E87A4 -731803CD -1DB59C5C -A90C6543 -A22221B0 -173A0706 -E040DBBC -941E546B -5503B9D7 -CC5D8948 -F7FE8FB5 -1AA3AAD0 -20229A2A -82CC4C33 -746BC086 -E9F90D08 -2B356E1A -14897456 -D9BC34FB -9056CB82 -1DD450BD -BF64BC9A -166164AD -94363CB2 -ED715F84 -CF4D9ACB -BC0EA0A1 -46E9697E -72428536 -D9569B91 -2B84C8EA -D4CDE0CD -E439EA2C -E19B71D5 -E45E8566 -541A4655 -845B296B -B2E478AE -1A35840C -C94F4E9F -A7AB9164 -AAF8D027 -82252CBF -20106216 -ACC1C08E -57E445D9 -FF68B8B3 -4DAE2000 -B5A7ACEC -1E9BE78A -88DC5BAF -C8A00837 -210B7F85 -E2A072CF -144DA567 -C6467799 -4BC0A056 -C60819E3 -B2B1ED7C -C0ADC696 -56F0E8AB -8D538C1E -879C3079 -6EE2F434 -7B9CD649 -94A30F21 -7DA211F1 -64035D90 -916A9128 -EC9C52F6 -92991BB2 -53F4309A -5AA71420 -F9B67D20 -45706BC1 -E71E83B -B091D34C -BE56577B -7D3CE09C -1A3F1DD2 -F90362F3 -3FD83E38 -E8274EA1 -CDFDF1C2 -62FD4CFB -C3A1DB75 -15E3C709 -B7F81AF6 -E58D41BC -5376E522 -698DCBFB -C76EBF96 -46682F6B -E5C0AE29 -50259284 -91A4E263 -4B03C104 -4B04D974 -914FF9B5 -783CEFF4 -4B232A85 -303E2F77 -6E902ACB -8D630D23 -9BE394EC -461237B1 -22760BF9 -B1F5BDC8 -F8557002 -9CA2BA41 -76418996 -B734B9D6 -C5D4B1EB -59F49A63 -4F9C6BB0 -219811DD -CB536800 -BDAC548A -824F1A42 -5CE7C68B -AC7A5DE8 -86D89A36 -49E127B3 -EE0E8BFB -4997152C -A43493BE -ED7179 -1049E699 -431EBDAC -379BEDAE -FBFB2AF6 -72C255F -F37B5D5C -2D15F748 -7759FCC8 -D6730ACA -52AE1913 -D709F4AA -581518C7 -BE85DA4D -1A24C4D7 -50ABC4ED -7B50804D -194F2CD7 -A56680A8 -1520F41A -A614FFCF -5F66A0AA -46877891 -4926E937 -74E93C8E -62515A1D -8F3F6DF7 -AA4D19C5 -8057E286 -8C90FAB5 -4AD3F2DF -D953B36F -37D20E08 -644A2AFC -5CF19FD -8C9431A7 -EEDC46C5 -F86BE6DC -6C12ED6C -5EDE86A5 -7E59C795 -5EB83E6 -6F36E55D -D9E35BDF -CC7E1D72 -21A42C4F -332994C1 -4E460BAE -C9A0955F -C080A0A0 -B2013D50 -E6CB68DE -E9C759D0 -4A1C7783 -D1028E6C -CEAC9773 -189398E7 -B57C20FE -D0D3E05C -6FEC2AAD -17643391 -1291E620 -978A16DB -37BE98F1 -9F773872 -1BEB32F2 -CF3DA84 -3088C11B -2BEB338A -1F308D75 -DD542BFE -C568D953 -BEFE8926 -B9E201D5 -EE6FA353 -826FBE38 -CC867513 -A00D32D6 -CE9B8989 -8D3CA53C -1718DB6C -CE2AABE9 -8FF0C7CD -DBEC0AA6 -E75EC71F -FF266269 -3D7D0B68 -D606EE1E -56F86B85 -6B67916A -B164B35A -D4E7337D -D7A68BBA -A39300CF -D7C72CA5 -A32F6380 -385F8023 -1FF83E95 -F4E55989 -6BED2F68 -C714269C -4D2E9366 -8C1A2FE6 -84756541 -6D353F18 -741B7419 -3BE84DCE -8FFA851F -FCA5E50F -519AC53 -2E36273C -995F9DF1 -A1A165BC -F5E804CE -DD395EDB -7B2D8A34 -FC3F84B1 -19EE5FEA -EB2CA6C2 -866CE073 -B60059C0 -35395446 -BD2B582E -C6E73349 -634D409 -B9AAD6A6 -81B516BC -6933344A -806F4464 -22AA3AB2 -A6FA442A -31DB2D66 -F64AFBC0 -480C5B8F -8CE98937 -F8BF9101 -395669D0 -A560F096 -C8A13D26 -9C62AC71 -C0EA2E1 -BDC5E76D -51C79BBC -E84416E5 -30CF1A91 -E87F3E55 -6CA51768 -4D09690F -D488F996 -ED850E82 -510DA36B -709F9D1 -A6AAD3D4 -E0C4B7BB -1A581776 -2F11B35C -748C7EFD -A2F0722A -A8C6D678 -915B88D8 -42E5FD90 -25B58AA4 -8FF166C2 -B5FC3947 -6427FBD0 -E1C01EC7 -91FD1568 -FE570CB2 -BBEE870B -811FA63F -BE89954D -C83ADB4F -C1B4D237 -65AC0055 -5E2B279A -3FC59820 -B1634DAF -AC02E4BB -B9D8412B -AB22C318 -9E528E95 -F4220FD4 -D83A7E2F -7C013BBC -23849524 -BEED0AF2 -C9AD6213 -4F367F0B -8FBA0438 -EC5899D7 -A4111441 -2D18DAF5 -E7349E7E -57AC8D6A -A27E98E3 -AA1A992A -5E7E0E0E -AE4AF437 -20A80262 -AE20A4C -2CA493A5 -FFC756B3 -68045EAC -A56BE46A -7B3EDB89 -BF17C1AB -445B3851 -FE16BE78 -23D0640A -694D05D9 -D76F0407 -AAC3808D -8D2609FF -BDBECF1E -D6074958 -7EA401E2 -CAD394F3 -4A67FBFE -A2A7FBED -59E0B573 -CEFE2B20 -2BE6EB1 -85FF9E57 -42C7617D -E9E01845 -43F02D16 -DF309F8A -880350B7 -65CE706E -CA6A2B8C -5C38AA9 -6C60FA8 -42BAB35F -9453366B -D5864332 -A25A3164 -F32EDF79 -C757635D -F6712B29 -4C43A3E0 -80D02D7C -A9DB16CA -55270F91 -3FE8F468 -AB0C835E -DD8A2F64 -D9551C26 -4642684D -69D1935E -9A7A2413 -E0BEC20B -14724D4 -B4A43613 -559418E -1E4A709B -A32F1E7E -EFEFB7A4 -5B26F487 -E6CBF46D -7139D0C0 -EC214DFF -7045BA9D -A9AB902A -CAE7661B -3B50F210 -A065F80E -B353DA84 -E6538D1B -965D76CE -E7F01488 -A1E57BCD -76920B33 -4EC379D2 -43909492 -8F621446 -C9033570 -FEEEB7B8 -E6FFA222 -E8CDDAA2 -3C5C0252 -A63AF91A -D545D3D7 -28ABECA4 -EA14F18F -23FF43B0 -F9F0198 -24568599 -71F0C3DD -63975EB3 -BF3AF93A -7B95B627 -9B0D74D5 -20967FF3 -A621FE0C -6CFF968B -909CF3B8 -79B5DFFF -FC87A4BC -5BB19840 -DB7D8F85 -D4641400 -54449140 -CA93FF98 -85668EF3 -C871B119 -58D44D70 -D93434A8 -453FD827 -906A01B7 -FD446B38 -CB63F172 -E4B0DFD8 -D4FE1E63 -C78583A2 -1D7463DC -7D69FEE0 -93EECB26 -337FCA9A -5D5D7447 -1ACDDE16 -C4CB8D59 -F178B39F -292E3426 -7A1A4318 -DCCE0A6D -EEC1FCB9 -3B264208 -F9D7CB6 -9A23DA53 -58B2B3A4 -654072EB -6CA920C5 -E145E547 -F5FF4A8E -AB7C553C -2A84E62D -6F6AE7B2 -322DB9DE -17E670D3 -7BDFB473 -7CD05987 -5B12A205 -5E9FB325 -542A1478 -FF46384C -69DE91C9 -65B4C13E -78DA8BBF -D85BC864 -3882BAC6 -444A8F13 -886DBD37 -2613D1CA -7CF2397E -513D4563 -1C57D4F0 -32B75B54 -E18B4953 -B59C2B91 -98F11972 -594CCC07 -39BE7B96 -B14E5D15 -ED093697 -953DA37C -6FDD4B93 -8D678AE0 -8B149A9C -B9ED6AC -E4FE210B -44EB15E9 -805CE5D6 -62FF689B -E6C011C6 -42C85768 -EC22FC81 -16858F65 -6A6BC5F1 -E5090FDE -482D0881 -65EAB7D8 -620494B9 -6160FAE2 -542E102 -81BCAF6F -C31AABA5 -BEFFEDB4 -A802765 -68A8ED5B -A47FADCE -3EC1897A -4DBCCC04 -83EAFD50 -6B8E05E7 -4FA1891A -9C2FCD23 -9ED7C877 -15FF9D1F -67DE6F18 -D2932D4B -E4B31601 -60B47713 -C1326724 -1F5FD6C9 -2A54C06B -599854F5 -C2121D8C -2D0FAD3B -762DB289 -CCE2E11E -622AD608 -29836424 -C9F1F838 -4E0F9445 -16C53328 -B9F2FC2E -28FFB831 -7C216796 -E065DC2C -561328B -92EEB73E -BBC5AE83 -2DE49E4B -BB32B7FC -E59D7B63 -B3375867 -5523615E -5532A7B5 -6890882D -21F33D70 -EA855CD7 -CBB7B3A1 -DD9C122E -5CEAC143 -E9E4332A -6F658BF6 -57E90D54 -715AA7A1 -DE7768FF -D8A3302B -1BECD73C -AD442F70 -EBBCB63 -5D25E0FB -EF9854C7 -DEBB6E96 -61591E99 -BE06EE6B -F74EDD0E -124B1712 -45833671 -1227307A -546B647C -9D2398D1 -DDB609E -EB68EAF7 -F05AFA0B -A6EABBB9 -60B5FC76 -992D25CF -A99743C -5FF72996 -E3D84005 -F47AC3D6 -D92BCBEB -3AD6BC2D -399AE49E -FFD7134A -80856732 -8C92A116 -D23F2A7F -1C1FF7CD -7E97215D -63CE5EAB -1E3D6441 -8CC7E1E2 -3144CABE -1B369565 -E681B9FD -3F72A224 -3146105D -68639F13 -61E4A798 -CF28AF43 -F18B6903 -F4D16333 -557BEB41 -F5DEEE8E -41F036AB -D0DBBD23 -E8E240CB -8FE50644 -8EF8CB38 -F8D6EBA6 -580EDAAC -25F0FEBF -1E09176D -CD156787 -8198153A -3D5D3DE3 -5132C51F -4B39B7FD -15BAA338 -AC2E0CAE -91DC2332 -3632CBA5 -2AD744AC -EF31B613 -6A9D8019 -17DE8C90 -E5CC66F7 -E81411C2 -C5B6931B -E8CF72F1 -ABF2E66 -5B7DEA27 -340E7880 -2B4ED84D -F6E86748 -9C181F92 -55DCA269 -1CEE9C9D -1DB0A271 -B1BB73B1 -2B802754 -596ED430 -25F4A422 -E186EA6C -A0793E1F -B54A8F34 -4EEA557C -A8085CD6 -276D7E7A -F711A6D4 -2534D88B -FA8CEFBD -A7E9E1C7 -EF6F2E -4620FD63 -7955C107 -50E0A968 -81DBA8B6 -92E0F3D4 -C78C01F7 -CFE5AB0F -C290FC3B -F12CC1D9 -56A9B1DA -69AC05FF -964D8EE -EB198C02 -A3D9435 -30D0BD52 -2A1A5868 -DF336813 -14C97AB3 -BA6717D1 -43FC05DC -32A6FFBC -C47276AB -DECB3B2F -1511FAA2 -155693C7 -E5BB37E4 -CB20ED97 -FDFD4014 -FFB25A3D -4F8B2CCE -8EC8D538 -A60DDEE4 -9E6196D0 -8895A4D -A2528B98 -D02F59B9 -47662556 -4FAB84CE -6C7FC2FC -F351CBF4 -F1917707 -B1F2737C -B46CC768 -F87757B9 -A24CA3F5 -74EC8337 -C46290C3 -77BBC380 -1B3087DC -C816F73C -6E2C562B -27C3E900 -4FB423EC -A77B1E37 -51063C80 -432108D2 -11F0367D -1D08F91D -D56068FA -F259DE46 -26CF3619 -6E6AF5EC -10AFB2EE -14F925E9 -5382204 -9F482CE6 -90B0897C -C768AA0B -654ED88C -AD60966B -8EB54FB3 -26275630 -A1C50A7E -21587F6E -9496FD06 -4B768A3F -1798404A -28C6B4D8 -5B579E3D -C79ECD09 -EC63FA6A -162A0135 -7FB7DDB1 -A0167E99 -196F14DB -CCD227F3 -3FB917CC -A3D30D38 -71874379 -E9E489BD -5DA989C2 -4F7C8E1 -F6E0502F -F8445D16 -25CC5FFA -FB06FF63 -CFEA3C99 -E41A8123 -6A5A256C -D7B67156 -50BDCCD2 -8165541 -F067F327 -B1E17258 -6901F3B0 -8B8CA0AC -CBA88A2D -4736E05D -DD5AD020 -35B501DF -73C67F6F -F2C513F -E6CF7C2D -E6A85B1B -8AE4F7E6 -1ACA7CFC -BCFCC182 -2930369B -642DC973 -990B6772 -681EC185 -164AC235 -9C676AC8 -B200AD7D -F13B8C8D -9D22DB12 -CE95663D -CE956E42 -29485F4F -BC5D5F8E -DAB561EF -C4C15BAA -77B9192C -86E8BF86 -5933ECE -E50B93C6 -F8B0CFB0 -3286711B -DD558ED9 -DD043899 -4AFAB231 -637BB2D7 -87036D19 -9A30430F -27798B63 -4D6E407D -CEE251F5 -ADFFB995 -B5C885B2 -7DF6519C -6EF51C85 -B95DAF30 -65EA99E7 -772FBB19 -49DBE1EC -F386A79B -EECD2F55 -8935CCEC -BAC4C120 -C71F82EF -2DF7E67D -9BA39901 -9614A4E1 -C6304402 -236FC777 -D47A5719 -8098EC85 -799E34F4 -896EBD9 -BAB10372 -32ED359C -6F9F763B -9D517447 -22B55AB9 -8E6F4104 -15BEC5D3 -6252E010 -23B5E8E7 -D0B113BA -965C42E7 -F2A0C19A -24CB582E -1F449982 -2E805DF0 -851608AC -755273C7 -3529A161 -6395258D -C5BD7D0C -27BABE75 -E1628E4A -47E5CD77 -EE797B13 -AB11893E -2F65151B -9CE2B20B -233C28A5 -749A0C91 -846BC1E1 -8C36F8FE -1489CF6A -70FB6BE0 -D0A84133 -9734B9B7 -FF166A04 -D118033F -BDDB2D63 -6F6691F0 -44FB36D0 -EFF2B14E -AC02C863 -ADFD2972 -905F6E84 -7C0008A8 -4A043A53 -D104FDC0 -1687FF25 -E6CF8FCF -120143AE -53F92C72 -19E2E798 -EE8C6B94 -15CEA57D -C8968EBD -D50EFBA3 -A8EA5FE1 -E2D073FB -B4EE195F -8928A91F -6B9EB970 -C24B509C -5D340563 -85FC3F3B -934FA012 -A2AB8533 -A6BD3187 -105DF0E3 -243ADD05 -49C299EF -7A42F84C -C90A1935 -3268B298 -CFA3B2EE -470C6457 -E579D2C4 -BB10428B -78D10FE4 -11F21813 -8424CE28 -EA2B114 -8239463D -9804414B -44B4FD1D -82D50F88 -10AED1B6 -E4768ADE -E7235A66 -C8705714 -936532B0 -15C63108 -92A91B17 -154B2415 -9BF0D15C -5F451388 -1DC102A8 -96CAFC23 -B076C0DE -3EBDCC3D -6B2EE523 -C6777AA9 -F7F48C4A -B1E8ADBD -FA30AC90 -5173D22A -D22827A6 -6504AED6 -3115E6F6 -E8937768 -C5ACC0E9 -366E15FD -AB81C84C -C27AFE96 -7361C8B1 -613A0811 -595F48E4 -1619DFA6 -233D2474 -4C174E1C -E7DCC63F -308FDED9 -502A0AB0 -C5004E90 -B7FBEFEB -918A77FF -F7235A04 -5CCB8B7E -3BA4B1ED -32F47DAC -FF7348B1 -996C8E7 -7203F1B0 -70583A2C -4D8046A0 -551119AD -BE5B31AE -35400CC7 -E8ECD409 -D1C104E0 -1A0858F -F26946 -458C8B3F -E8D66E91 -2F3F6384 -B36EC71B -289CD4C6 -6CA9E35 -B198A8B -816873F1 -346D66C9 -BD906E97 -802E5969 -261BBBD1 -9D7605C6 -72C2CDE6 -6C8DBDB5 -D7C8DD7C -F43FB2C8 -A9F384E6 -78FDC918 -6D20841A -20755F34 -F4C6AF99 -19393B53 -A525AE84 -CE881A38 -3D075300 -9B0E4DCA -7EB7E7A1 -4C4FD44A -78483ED6 -32D9D894 -1CCD379A -EA5FEB4B -F7E001D -44FA69A5 -E99F66B6 -9E16CD0B -CD098C41 -6DAAD279 -5FE50411 -CC855E2 -130C6563 -356CD9A1 -BFB318B8 -2E963C0F -DC5A046A -FE16FB -A599857C -F72FE561 -2914E4FE -B247AE8D -6A6F13C0 -B1052C98 -8086E53A -845345BA -D43D5F7A -82B30F5E -4206EB1B -89CCA1AE -86289F6 -567F22DE -25624C58 -6A78EC3F -7EC32D03 -8017213D -3A141336 -D1CA4E6E -FA84C2C -FE670E0 -3238E01 -18DF1794 -A7B900AD -1FCE47CD -14EFDCB1 -C21B04A8 -4C3343A2 -E5E611B7 -ADD06EF0 -32C81695 -201A9FEE -BA8925BB -5182EEED -7DA4917E -CC331235 -C304ABE9 -C2A16075 -937E1C4C -CCA0184E -9DB6C45A -3F2A79C9 -151B469E -162F22DA -D955D54E -E857CC0E -FFF2005B -60AD87FD -85512214 -E0A506A0 -FAF1A145 -9DA17F03 -332D26D1 -9EDF9643 -7BBF2D9D -3414FEA0 -A8FE5964 -D4841879 -3AE4E5EA -BC6B6D60 -950F4693 -70FD0254 -177C7A1F -635FE5B9 -C0C5B6CD -15D1D22F -BA495903 -CC100F38 -A5F1E225 -5AB4584F -AC4731FD -ABB04167 -A0E153B4 -5982BDA9 -8E2EE3AF -D635C631 -7C6154A2 -9F0EEFEE -429B22CA -B1346D4E -6B21663D -6A7EDD8A -DA34A355 -217132F0 -683BA78 -9CD46320 -A5D3BC4F -3194AB03 -DD66F958 -E7506C47 -17EE83A2 -4E4D80A0 -EB56662F -BE889C58 -6F5F6745 -2A05C12F -13D266A0 -3B2B18C9 -EF435E02 -5604DB7F -D35888A2 -CCC34421 -55E24355 -7F607F34 -E493720B -C6A492D7 -7DC6A789 -E01474B2 -97D35C32 -71F32335 -D3083D7 -2327D424 -35EA4BA1 -F5B20C6F -3ED28FCC -453A76AE -192A79A6 -2E64285D -A9463AEB -374E22E0 -92A5CF8F -E707F8E8 -B8E2FF36 -E8E959EC -91D9796C -F03960F6 -B62467FA -8836A487 -6418A93F -60932160 -3B72687C -37BBD7CB -1001C76F -201999EE -5955A1CA -925351D4 -767540E3 -570BBF27 -A073D4D8 -FE96246A -44784995 -232C0150 -AB7BCE2 -D47BF099 -BFA6A422 -70F4BC01 -C2139449 -F9ACB817 -26657111 -13263449 -7989D26A -2E972B3D -2F1C1C6 -930E479 -23243FE7 -BA7DDF9C -50C8AB43 -952377D6 -4C6C2B3A -BDAF48F3 -1C0BAE6E -7F6A8C04 -F529B9FA -9ECA4162 -342E6562 -9BD5EB52 -A14DB3C9 -14B1DC2 -4E1BB6D1 -9A1158D5 -73F84EC -685BD9F5 -8CE72161 -5F116605 -BA861D43 -A7150AC2 -391A105B -C8D798E8 -16633750 -33B29C4C -54211362 -34C2D5FB -CA197734 -A635990A -4E606FD7 -9D56673B -89976DD5 -5F2D2794 -81E95955 -9377829 -5DED53B7 -FEAD5592 -1CC6419B -BD3A45C6 -65FACDCA -7EAD0EF3 -EB856702 -D857FA75 -3B92DC0D -E66AE58C -51912618 -C63C75BC -ED05B556 -17EC2B32 -9F692578 -C706059B -D88D5576 -C2661C7B -6D7751C2 -119292CE -418700CA -2A2BC3D8 -CA20D341 -8A8F325D -D4A2DC8D -959FD62 -67883F8E -FBD3686B -6B862363 -F8C13880 -FCACA893 -8215D90C -67567E2D -3B501BED -7AFBFAF4 -2EC3CC34 -B360BFD9 -716C5E9A -907B1432 -E253CBD1 -4DB52F87 -6A37A21F -C860A6A2 -72DFE5D2 -84E0705D -80DDC195 -1ECD4E92 -2D2035A1 -B10A5B53 -C9AA9A79 -E999CC8D -C8C790EB -F7629DFA -93158872 -FAB6E7DF -58A0A3D -6104EAC7 -2BACDD14 -A8E3DE88 -AC4E16F4 -F7042189 -5AA6D923 -F491667D -C769767B -46EE7E69 -CE4BAE4E -FA1BE581 -2BF14278 -5356E813 -6225B503 -D33A6F26 -1A629247 -BD844A35 -E33ADFB -EFE720D6 -3D49752E -AD542CEB -EE36C608 -99FD833C -BA893EF7 -47E4A8A9 -B269C1DC -CEF39BB2 -91FD5B03 -C02E6C1D -29A3817F -70894875 -8C851D1B -8446E920 -8CBAB8AE -D9D7B185 -97987DFC -ADE83493 -4CD1FC4F -1D82738C -27665936 -CE3C907 -990136FD -E1E40CF2 -A3E15CA6 -DB7D4E0F -D8E87ED -FC23DA2F -76A6A0C0 -1C7F403F -380BCEC9 -C2BDE917 -74145443 -14C0823C -8D73C415 -BD7B9DB4 -C83449E7 -364D21C7 -7F01C97E -9ED9F208 -51417FC4 -D557CFF2 -5ED6B81F -BC0EBF41 -608D56CA -60AA90AF -8FC8A8D6 -809BE4D9 -47CD9035 -8CE71201 -B442C067 -A380EF4D -7B74A914 -513ADF78 -63E5C752 -6D4F2B4B -82717D99 -EC19F48C -7D0D1EC5 -944D936F -358B8D1F -D3A7E17D -5E6DFD92 -D6D2B538 -133AC914 -22C4BFCB -A9F4ABBF -7DDED93D -6836C5 -3F10AEBF -71713080 -A1868A02 -EC341DE1 -33D409F1 -41EA5D35 -47F18F89 -7C062A2E -1C66DC90 -D5E11362 -FACCDD77 -D96EA1F2 -31676D3 -B00B9D1D -36F80278 -754F427 -3D8C40A3 -D1FB426C -ED4869D3 -AD137726 -9704A7D6 -107A0E2D -AAD92A50 -58019B5B -F6FD55A -E876FBF7 -13451AEB -A530BF41 -11FCB24D -EF5D7F1B -BB65E3F3 -DCAF1904 -4262AE51 -8C2318E1 -96E7A13F -DDA281E3 -7B44E7BF -8048EB55 -AFC8D749 -D3F7E592 -23FF8DE -105E2923 -969758CE -B1BF840D -D301EDDB -42A3C6C4 -2C934ECA -B2FB9ACA -452302A4 -C96F49CB -D7342392 -48A6D82C -6B831657 -1A6989B2 -312D282B -9AC1D170 -3FB3070C -D83B178C -D894496D -5FFA91E8 -436E970D -54DC6812 -8CCA890F -96971388 -9CED7192 -216196F -BDBF8734 -441B7DC6 -8FCB2D4 -1C3375E3 -19EE1338 -E8BD4F25 -D65CD246 -85157D36 -34A4CE5A -BFF7BCD5 -41DD5123 -D92D0021 -C0265B3 -652BE05B -7B31FC27 -E8BBC732 -E5DB7686 -2D1EAFF8 -2283884 -CE0E4257 -1936BB27 -6ED44FBF -476ED2B -C249E9F6 -21C0827C -8DA28ECA -707E075B -10EFDAF6 -3DF4B474 -24AC5C3B -81F8A453 -8E1AF272 -E69E1816 -C40F1B4 -5AF2AD1A -C1236EE6 -78507240 -588C4851 -385396C3 -BE2210DE -E8FC3FE2 -B9E7C8F8 -A33939 -B9E8F7DB -F7DF1BA4 -400E6C2F -1139C2B3 -8195BA65 -A6052E5F -29E1F01D -512ABDD6 -ABE172A9 -350BB8FB -63D89399 -6C7CDD2F -F6E20A15 -36947843 -7D26A79A -133DF31B -AB375C67 -35D4F0E9 -8060F5A6 -94893A4F -1B4E1612 -431938A9 -F4F22D48 -E83BC91E -98D9DF02 -7CBB518A -947735EF -16DB6C38 -7BBEB95B -393A60CF -6984032C -F1879BA2 -F014440B -61CAEF50 -F9BAA90B -6D9CDB7A -4A4C3D3F -DD498DC8 -E27FE395 -AEA01257 -15FEAA99 -61A173A1 -28EFFD56 -A27152DF -10C613A7 -47AFE324 -5B4D4B5 -AF67027D -11ADBB9E -F8B22312 -4A9C0C1D -E94F39C8 -9AA4F0E2 -4C394A49 -41ABACE1 -6A96270B -171F3E81 -F29DB470 -A9E7F67E -6B445012 -B53EFB86 -B0AB92A -484432B2 -7C789E2 -116B012D -5A5434DA -83DD29B0 -418637F4 -C9E1FBB7 -FD84E0E9 -BB44A4ED -4847C699 -61807BB2 -F558A9F0 -264F9191 -697F6915 -EBC115CC -A1604C6E -9CD73651 -50ADAD72 -DE3698D8 -DAD728B2 -58F5527 -C58A4754 -C8CCF740 -A5CD4E0A -966E50B5 -6DEA9EAF -66DEDD5B -CE18EE1B -E0293294 -3C0C586C -ED04E099 -A1BB7722 -78AF5367 -3F0FBBB7 -4F623EEA -E3E1A85A -3C8EE1B0 -D2851D20 -F07248A0 -713EBA3 -8CCDC87C -B5ADE0C6 -54DC4354 -F7F43DE5 -AB512848 -69136DAC -71CEFCD8 -5F264F19 -D39D50DA -A184BC23 -57F38C31 -34DFEB30 -6B39F755 -60F7B6C8 -EA7FF406 -914CD331 -F4A15FC9 -68DB20A3 -6609D547 -18BD6EF6 -F5DDB763 -9E2C6236 -A9C0CD72 -EE8A864E -FA9A7891 -DCE7F5DE -4E5A9B63 -FBC574F8 -13C26C91 -70A2AD7F -9514018 -7786A6DF -708A442D -8AC98261 -57EC9F69 -D8B92F1F -5525E8BD -CFB927EB -47BA617A -4A71DA0F -9632F7DD -4A00D653 -3FC603A6 -A34C3C9F -EDFCB326 -BA31E996 -4158D5 -888F01B5 -F001473B -D67ACDF1 -587F7E20 -EC9AFA96 -6942D697 -76FEFEE9 -ED260881 -53D50BC9 -43FAA199 -DA4F8CB2 -D7FE8FC6 -7A659755 -394C88C8 -EFA3AFA -87710DA8 -DA1FF12A -C5D4E7F8 -4F0A47D7 -E7C2A799 -EE894D65 -20E4FD0E -8E51626 -17BB7611 -E48021B1 -4320CA45 -5315D225 -39684701 -3E943281 -B3B7B298 -A63E5C66 -11F2EAE5 -2E339781 -9BE79114 -187467D -9479787B -565D0658 -B43DBE73 -67F7EA80 -D1962413 -BF4B89AF -AC03F363 -1587941F -B7A14BD6 -AE1A36A4 -BF710690 -8009F7B0 -FB37D608 -58934215 -327E7B3E -A2BCED7 -57DB9C90 -3E7E56C9 -E554BE2A -6B6273A0 -766F5A68 -503BD141 -586BF1E1 -AF75978E -D93FB741 -75268390 -BDEAB299 -9871DD6A -9C042A7A -4CED46AC -706B559E -9C9CE827 -EFDAEFCB -A1AA3846 -330AAB65 -602F6FCE -DF14BBD9 -8BEF0FE8 -CEC4AC8B -28456573 -95AB0149 -43E11079 -B50D7970 -6F8F89C6 -B96DCC6C -E114C8BD -CF3F36AA -E02901C9 -8B452A2 -8AFEE7A2 -FD7C3D61 -4DA46DA5 -BD5C204A -83FB677D -42615EE0 -3783255C -9FA48033 -270F0FCB -157E94E0 -CC89D359 -715FCAEC -32EF8DFD -829D0BCF -E4FC364E -A629CB9D -7CE1FED6 -D6E9FEEA -24E55CE7 -8BB2DA23 -2FAEBFC0 -AD6EF205 -96142124 -6891653D -C5061A39 -9EA7F89C -D2CA9BBF -544A569 -E908D41E -EAA11FBF -4250EAF7 -6A5E60CF -5F84A53D -4324D154 -57320611 -DC3C692F -24685A97 -40F011E3 -25A224E -3712F01 -30F1AB94 -45F92B8A -450F8D4E -F3EFF92B -EA54D0BB -7E10A58D -D51BDF85 -FA6E7358 -A16E06FB -CA158DFF -9AAFDAD5 -AA48F649 -A4A78E50 -F2F73CFA -519FA6F5 -32933CF5 -9E55F1C2 -806019A2 -E56E0B7E -5F598AA3 -564C6D40 -757BDE5D -30757BFF -B906BD37 -52C6C503 -D2B00C73 -5969C7A1 -84FF193D -E668D8D1 -71E66078 -A200D7C6 -6585828A -FF8864E8 -B9EED36 -12C9F3AB -2F2C4A2D -2998FE0A -A1D47491 -59463A75 -1347C537 -77000037 -E6AC6FFE -C74CADE7 -83B75335 -767A69EF -4248CAAE -1DAA4A34 -BBCDEA3E -CE177B23 -59449B11 -A9DC563D -85589ACB -8926A959 -CADAB503 -6A1E5AD1 -E79EAAB5 -9C25D798 -B4750BE3 -249329AF -724F7831 -F4D2E094 -CD605F43 -CCC933E3 -4231A56 -8D15BB64 -A7B1E394 -FF2B04CB -7260C6F0 -A483E58C -35E5FBAC -A3D734E9 -64BF02D7 -24F8B625 -FBDA78F6 -6FA335D5 -5CAAE8EA -EBE22B69 -9BE5C3B2 -81028FF8 -E20FD2C2 -CC8506BD -E079C912 -BDE0AE94 -AA4AD182 -AE682162 -AADAA077 -C757CE81 -E4BBF694 -8ACFF53D -D1E85D5E -E29E9979 -9DC46E06 -A8FB412B -CA71D109 -987A6F6D -E5A13D87 -BCF3C6D6 -DA5A6320 -E78095AF -C0C4710D -7F06A362 -FF3D8A8F -428A02D8 -2EBFAF55 -D25B93D4 -344E75CC -ABC855A9 -E3577D95 -843C4274 -F5326A2D -EC6EB288 -7C4C82E6 -A70953D8 -8D8B314 -8772F0BB -3BA5025 -1BE5CFF -9592B505 -B9FE16F1 -EF77DAF1 -4C7B4119 -8B8FEB44 -3542576F -375EBF3E -D0927BE5 -2C6A3AAE -45D18D70 -6126FAB3 -58146389 -FBF50CF3 -3129860E -4B721C54 -95BCFF3C -DDF12106 -1E2428D3 -827395A7 -35266B84 -3CC089A3 -B8198C2A -B8EBD35B -7EBB213B -A93DCCAE -CBB25C42 -2A03D874 -46F6CAA -82986B02 -47EA89A6 -2C3E7BDC -852B0630 -A928EB9 -66A2BC66 -BBB43A54 -A6F55CB7 -FE990460 -5FA8BA0E -1CD34B74 -1C0F2BE4 -FE6C53A3 -C325B6C1 -A980B3D1 -9F031392 -31E17C1B -38B6D6A3 -E30D49E5 -E83F8C4F -BCF13E0E -28124F6E -57AF5DDB -691BCC17 -BD071C94 -DF4984C2 -8579EA0F -92150479 -7BB67579 -58D6EB84 -97754D0C -F569F71B -9990D0B5 -56DAB760 -9E988907 -9679988F -3EC5E4F4 -328D67D9 -317EB4E7 -5E6D7E6A -BFEE035F -D12E6060 -4F2A7A2D -F65F5B73 -54AE1242 -ADAD3A5B -61A81471 -FB09DC55 -72874DB5 -5302F1D1 -8B5F6A90 -82E98E7F -E808315D -DDF5B32F -C35356A6 -6F1FF7AC -1549941D -1460BF8A -D53684E0 -1A384C42 -D319924E -B0B1824A -2772DB36 -BA61B594 -712F9397 -41F5740B -C00A34B2 -F2FCE526 -4C874DC6 -FD5ED831 -301E874C -CE244111 -D6AEAE23 -516AF534 -FC101FD2 -EACEA514 -C23A0FCD -650BA0E6 -5C877E20 -ACB5DAE4 -5E56E78C -1AE6F2A -705046AF -7F53EEE7 -AAB30590 -2A1BD5B6 -300A6D8F -FECD64C6 -A8FF2EC9 -27B583C1 -29CAE718 -66D59871 -16E8C79F -14D20B3B -446862AA -1C5EBC93 -3831B437 -556E9FE -B877897C -D6FE7901 -D19ABB8C -964EB757 -D1DAC489 -B60AFF4D -31D01640 -A963359E -E233B856 -58D923CF -EF31455B -EC071BC8 -94F64E2E -F9384093 -36C8A1F -AC4A701F -657CD41F -731CAD58 -374B9753 -EC20E4D1 -E58959AF -E83E1021 -B7C14D53 -A651DDBA -D54BD80B -7291E323 -31310762 -A54A712F -482BD448 -1FC7B562 -EA69143D -4342848D -C4BB4C5F -B0B43A48 -962EF559 -5C395F65 -6C40A83D -AEC344E3 -881E5E3A -42D50FC5 -144B9CA5 -15DE8B4E -AB91DED2 -17FCB1B5 -87804536 -102205D0 -E57C9F29 -5D08E2E1 -A4AA0B4D -4FB1351D -F3BFE5C6 -5C439E04 -33A0A6AB -826A9A49 -D165E206 -229A4A83 -4897797B -396C7F04 -474B2792 -351AD33 -ECCFA3E6 -901B77BB -42B16DDA -FB3F707C -C6816341 -CE19D1AD -8297E119 -4458AB5 -FD9CA7B6 -250517BA -2E23BFF5 -F0D1C983 -699A7882 -557EB3B1 -D0D5822D -D1117539 -F271C507 -9364161D -6793E35B -8AF902C6 -DA5443B8 -EE1E1A0 -B941E448 -DE0E773A -4A41AF87 -D4AA88C2 -80B09F9E -53F2B381 -1C8EA42E -3D15C64F -93FE9251 -B242B629 -F7ED2942 -6AAE674C -EBF19F56 -E299D4A8 -4F22DB1F -20998388 -4742F182 -F6626B60 -992FB48A -26822FD4 -784D31DD -B84CAF35 -B8163E9E -2A27EE0C -FF09CF79 -81C74BBE -C914DAC2 -E768AAF6 -FFA5171 -CA93E6BF -E495891A -482A252B -18F8FD7D -DE52E34B -A4986019 -E363E1CB -EAF53373 -59FEDE9F -2FAEAEB6 -DCE56F6D -F10257B2 -7609DFE6 -4D0D263A -12696B9B -A56E0541 -8F12E1B7 -9E8E5761 -98C5816A -F2F8EFA5 -B91C1CF3 -59A19F9B -9235B967 -A58D23DB -71377517 -C50BCDB3 -60D31A7A -874811FA -58A69900 -CD8198EE -E4FA90EE -51352862 -3654B5D6 -B0442DA9 -5BA67D5E -A9B84B57 -FF61069A -21102ABD -8E6B59D -1DBF72C0 -9772AC77 -F26B2827 -E985C97D -CC311683 -E8216C66 -13E346BE -199D0C57 -578B8B90 -84462520 -7B33C9F9 -E18A5CC0 -8F70C75D -B9773D99 -8A8BDCAF -78B8631C -1AA0C9F2 -76FDD536 -8CECE336 -999E6F4F -29EB2768 -3417B854 -A56B87D4 -CA2F016B -69DED6A1 -8AF8128C -27732A2E -654939F8 -F0DE0291 -501F84CA -815055FE -99B595F6 -627F49E7 -2A7BE8CB -959032DB -7FD03C7E -54ADDCA0 -62EB2DA4 -6E458899 -2FE00E32 -B2E74808 -35803F87 -7369F52B -1586B4DD -61B61CC6 -1BDD1B8F -C6BAFAF5 -C4339DA2 -E1D3A0DC -8AD49CC3 -673B67FD -D81B434E -A41C5AA6 -BED70576 -22877C0D -71A3DC2A -FDE1F4AB -4FA1751E -DADBAFB0 -1C44975B -76EE876B -E3B81546 -86466730 -6A3F403E -255A72F8 -2D2AAE1D -77717644 -63E003E8 -40CDF1FA -FF37E1B5 -F0FC3CCA -45BE9807 -D8611D58 -D62AB82 -EE875225 -B8149434 -FFD0F0EB -2F3699E6 -7EBD4BFA -3E393CC6 -39777EAC -FE2A33EF -9AECBEB3 -322B14DC -DA2EB056 -1C942882 -C42C7C32 -A20E0D02 -E91D2834 -D465D9D1 -FC60192C -D3B7FCA1 -1E9B03FA -40323FF4 -DFA3D47B -2C26930E -391E6E18 -E340B164 -36FD76AB -204B0D9D -5F5027DD -FB05E9F -33C3443D -ABF1832A -152FEBC6 -FD83B071 -310222F3 -E07F3402 -61818FE6 -6E14F915 -F89FE609 -86FC4F17 -C860D97A -51B0EF08 -779B9BA3 -6D9C0908 -D14ED3D6 -692E8084 -233DEE29 -B85FF171 -12FAD29A -D37B7593 -AEDD969F -8E76CAF6 -A7FDDB58 -B5B7DFEF -A8881968 -50D65153 -D57A8EEC -7D144C49 -99B10DC -5660CCA2 -C02A1001 -7EE499CE -8C281511 -8B43EDB4 -31E58C4 -E9EAB787 -48BD8C20 -87C33E72 -9FD28F45 -9D8374B3 -3AEBB8FE -D25F7E5E -65B705F8 -ACB7BA8A -C7CE28F4 -1A365014 -12997929 -BAC3250 -3DA4DE9C -D90B5C3B -731BC23E -F952A129 -E5FECF74 -26D6A0 -B61C74A2 -B18937FA -E034B86 -6B3E73E1 -FC5891FE -E6F5F72B -BE380D96 -DB6DA2C1 -8BCAC0F9 -FCE57C36 -10230AAB -8E0B6278 -962C5A14 -4C257AA0 -95B50454 -478B67C6 -4BB1F24A -9DE453A7 -241965D7 -DE5E4EEB -77BCEB46 -A87FC004 -4EF35145 -35910ECD -8900342B -C9A653E2 -9AA2501F -DD4D16E8 -A2340ACF -F846821 -9A2A16D3 -33BF35C8 -185C4C5E -9A3A7865 -6CA5232C -8A93214E -8F9C13E3 -CF212018 -777D973A -3531924D -DAEBD9FA -4C4BA7D1 -C6DD4E96 -72F0CF35 -AD82F177 -B8486F78 -C89FE003 -991E4764 -F49CB023 -14C3A164 -B6B2733F -F78D6623 -F1C9D84E -6CE9487C -68F59E42 -B13A9862 -A60DF7FC -5680C3EE -8DBB03F3 -FE660987 -7F302425 -98915B -3EFAFEFE -819E3A26 -CF086D8 -EDDF6ADF -314D6342 -C7DC4A97 -231D9E12 -C8F0BB37 -E2A20026 -A9539B54 -E2047DA5 -3E5C9D4E -F91C18A5 -37B1EDB1 -DE88277F -765DEA9D -555D803F -6FAD1516 -41299623 -66D3E9F -B040E22F -28C55A65 -F5BBEB1 -8F85CC9 -C1F1FCFB -E0ACADA -FD138889 -F4E18B1B -6EAD0B49 -38441326 -17AEF5F -5A6EF970 -20ED5B3A -46A95C2B -CA7475C8 -8FA66C0 -3F831698 -E2C27DCC -7AB6C35D -9D979A50 -27F30FC -4FA19438 -321E637C -AD72B955 -C7BE128E -A428B5EC -48817E5 -7EBF668C -8DCEC036 -272C5582 -F8175767 -6ED7A880 -71E2497F -6EE3595D -D2579856 -15439021 -87C91FDA -A5682821 -E3FC8D77 -1545F959 -6341300 -D52520B7 -B0A0FAE6 -6F1C6BFB -226DE897 -4449D2DD -7E378981 -55A93F85 -91BFE157 -434EAE2F -AEC8DFBE -929F369C -DF654EA5 -CC2D5431 -152C1E93 -D800D93B -1969CB8D -46776BE7 -DF3D435C -2CD82C1F -241528BB -88B41461 -19463B47 -CD61AE6F -3C5DFE3 -8053B926 -5D0C9D00 -75240C8 -53A9DCF1 -B217E766 -616C0F89 -E73E36F5 -1E3E0BC3 -B6C474CC -9AFE8273 -AAA496CA -E9770A12 -9C3E2617 -3CB73C1B -2065FF5C -3A2B3E59 -280EF886 -B6A728CC -DDEE48DC -BE40F70 -449577CF -E5D72358 -5648EE48 -F6B9BB34 -F8E354C -84895AB6 -95DA9283 -882AF6A3 -4FBA089C -D27070D7 -17784421 -DDEBCE6E -4E6A43B3 -82AE90D7 -1A524C8F -D1C0C339 -993FA3FB -52CCA574 -523FF9E9 -764B2F69 -621F0749 -5C95BE3E -F2A36CAD -5C92ADE4 -F4238C46 -BDD0079D -CAE6D9F9 -5F3D1307 -9345998 -22C3C499 -631B8B0 -A6B9A88B -471749A7 -6BCD27C8 -5D371C05 -57081397 -F6CEF315 -1BACE19 -B7BF405 -5B6DD011 -BC74DA95 -781349E -F22A975C -72A5A101 -27BB6AED -933B9126 -14FBE3BB -50D095D9 -1CC937B1 -22CBC28 -1A6135EE -197E93EE -26A1CB1B -79BCF079 -A0134157 -9F232A75 -818BB26B -B2339659 -911E36A8 -AF2F9282 -347C34E8 -6255FF5B -1BB79854 -9A16AE8C -2A3D9B7D -93795FED -8284A6D4 -E58090F9 -A36C45A3 -F8065618 -4122FC06 -6F4DC90B -5336936D -F4E4BEDF -7A885091 -E19CB61D -9D398B7E -C9C4AF2D -A1C076FC -BF60AE9B -CBF56B80 -11038EE3 -4B78AA1C -59C72649 -D687CF08 -B182CC2E -43E4B13A -83126FE9 -EB042718 -627C8807 -47474E59 -3D317A4 -33919B88 -E00CD1A3 -3CC1F4AF -2E91597C -CDDAF2BE -3D3A18D6 -5BD6E47E -3D6A5286 -456410A0 -2B51CF4E -B55046FA -FA43946F -F90AC852 -A064AFA3 -F84235C4 -D316F3D2 -1BB0D769 -46905EBA -255EE03A -EB4D2C17 -6AFFB5CF -D755618F -ABECFB93 -594CBE9A -362C1B5 -ADFAAF67 -ECF2110C -E86FA43A -C789EFB4 -D9FDCC95 -F81FFEBB -C239F63C -16BBBF2F -B1AFC20E -B00BCEFB -D6B41A49 -A5856CBF -E2753B3C -8C03166E -537BA621 -B268C813 -C1B8E5B7 -1FCDD47C -BB257FF0 -37B89618 -6AD0F548 -C5EB6B1 -482EAE33 -1F898EA -C161076A -8112502F -77D0C22B -B1EF60B9 -D8122593 -D0ED144 -A258567E -7FCB11B8 -FC01313B -8A39DE11 -B9612887 -FAF9C5E9 -AFB24528 -C51F261D -15A83256 -E560FDB -5749D494 -61C88749 -F7C9978C -41583770 -73AF53AF -EDB828F7 -5B9A931F -B33EEF56 -3ED0DC67 -915BF5B -CD090180 -3659A346 -E09A572 -B0EB23 -F35F97ED -8708879A -E3761150 -FBCA868 -8EE5D700 -67931F7B -E3819B8F -FA9DD938 -3C3DD434 -FB62C866 -9D6A734E -2BE14923 -7ED6D7BE -423CF38D -CC4C4156 -898F3254 -405B1D62 -25995FCB -C062465 -12471B35 -6DB351F2 -5F23ABC5 -49EF7D2C -91B401B3 -85DE49E0 -81D81230 -9824E09D -767C5312 -E0744F5 -D99A77B9 -7657BA4F -46CA1289 -5D2AEFAC -ECDA74CB -DBA899D3 -AFC6E7B2 -DA79D8BB -F6508AA8 -6D0E5BF -76DD66F3 -DAA00B8F -C7EB98CF -65189199 -FC2F2235 -4F19D2CD -48D4E497 -67A7643D -777B5F1E -2F089D44 -4E841850 -2D371993 -B3ADA2E9 -421A44E9 -1D470C4D -81DA8998 -71D42D8D -E5F09965 -24BDEA19 -F8FB47FE -1CA01D53 -52A53F9B -B13279A7 -840C17AF -F27507D8 -36AA55D1 -29616808 -E5C25388 -404F7A96 -AF6CAD43 -AA2A8D86 -6D0D5DE5 -B60B5047 -F904AAE0 -9BCCB969 -73FFDDAF -AEC2E379 -DDC3B6E3 -85273FF -4F23EA7 -F1048821 -432CA7F7 -FEEFB49D -2749D00 -F0914942 -878203C4 -AB657B2F -FF754E6E -2A1B63BB -2B094F6C -8DD98DF4 -7E8810E3 -D17A81B6 -BF297F6D -FAE3391B -B28655B9 -2B4507BB -702B2563 -FFC8858A -B8DF3A03 -80018970 -4387C2E2 -81246EAC -1201F4B3 -9AF9F9B6 -29F63494 -98A87F7B -C637C322 -BCFB7066 -3505C623 -10BE77F4 -BE44797A -2EF31DB -C8DB4396 -FA7C2378 -AD3C30C3 -C3AEB714 -58183DA -5D961567 -1E42A328 -94430ED5 -866A3D67 -84B148EA -C823439 -80B57816 -D6395105 -B389CD22 -B574BF88 -F12CE1CF -C5B892E4 -94F6CE69 -9387A05E -C806C5C5 -B2823B0D -64F1253B -DD3B64F8 -4C6980E -BA9825C0 -573D9CE3 -A78DB442 -FB5510FE -C45DE1A4 -66DFA70F -47960901 -68D725DA -ACAE1E6B -60F9360 -8C9D39E -E78D5AE3 -A1A0BB75 -80E4ACAF -A0FD5042 -5E0CBC82 -C0474CF6 -840ADEA6 -6F972DE8 -5D16E0D1 -86688917 -E08A3150 -BB5FB87 -2EE82F9C -62867EB6 -B592C066 -64852270 -7A7634F0 -58C6FA6D -E83506E1 -7DC3ADA6 -E972E4D5 -4877FABF -CB37BA71 -7BD3131E -9CA64901 -C072094E -A28F50EC -CBBE833A -225D213F -D4266D98 -3DA08099 -22481B45 -899C4804 -3A8630B2 -7227F512 -FDA1F80E -E5515F91 -6EECC93B -4611F561 -47AD2CF3 -ED2A807A -D694C082 -6DEB43CE -9DBD4F70 -8C918F0D -28C5219F -EB23A332 -AAAACB21 -9B053C22 -6C5AEEBE -B1941AF2 -DEFAA083 -255DAF18 -B513F3E8 -CDE47DE0 -43DD2231 -71BA21A -AB772E2E -510C581D -93A91FFB -ED683872 -E561882C -C503A74E -E274473E -3F7D95C2 -AD48EE4C -887342AA -F4D0DC01 -68023FEA -F996EC8B -F4E33500 -8191511B -AFE0184C -8A6D392B -EDFEA13A -AC3E90B2 -94E7E8DF -76F491E4 -D45224EF -D32B9CD0 -C7167945 -2D56F7E1 -994E7AAB -65EDCC15 -AEAF497A -BA11EA7A -53D5812F -DF05201B -10A9356 -ADAEF92 -508293CC -B45B1908 -DD8C2367 -A385DBEF -A77E11BF -DE9B1792 -A9FFDB94 -AE48AD8B -E7798E96 -BAAF5B51 -44648397 -80303BBA -FBE848C0 -74F37EC6 -C9C0EE6E -1D80DBC0 -6CA37DEC -995387B6 -BA2D99D0 -D1869967 -39D0BB45 -36E391CD -12D6AB0F -4CB16A65 -8BED7413 -99987FE8 -55BD54E3 -5568C11B -F63606C4 -AC4D0747 -3032CADB -52407898 -C461B987 -1F3C8122 -C7E1B1FA -BC1BF34A -724843D7 -2DAB612E -F5180E4E -67FE89A9 -B7641E8E -185E5197 -5FDD9BA3 -C6AC4D7E -DB020625 -16ED5F8D -5A2DB8DB -58F7DE17 -8231D332 -9977723E -CFF39DC3 -A8B71C3E -3335D9BC -D34AE6FB -31559150 -E6494443 -D6C0C713 -515C9C4F -AA09B03F -EB32806D -981F48D -DAB324BE -33EDC165 -88011009 -F1120840 -48119894 -137409C1 -7F45314A -DD74A5A7 -C2251ABF -AA45B420 -4ACBA24E -D020B449 -50E55E0F -D78DD382 -F6E82B05 -9957DCE -1410E573 -CA93CF29 -83DBB1D9 -7AD6D5D4 -7921516F -8399BEB7 -DF07D89D -77AB752E -6D6DBA45 -890771BA -E87CBF52 -F90A7590 -78967761 -6617D522 -2EEDE919 -F28BA9E9 -E1E3AA90 -2CBEBEF8 -1D8A37FB -9CE04F02 -680B5A92 -561178BA -A19545D0 -DBDA24E8 -A7863CD1 -F1B829CD -2BCBD34A -B8DFF2A6 -2787D144 -A075B93E -AA7BC361 -B560CBA7 -F8E79316 -417B968B -9FF31C37 -F88ADDD1 -99A6E199 -D3D400B5 -79F33397 -4AF6EA07 -93EC79F3 -F7D9C5B8 -81D7EE3C -2898D7DC -4B8F67DB -D52D0F0B -10766E32 -E228EA2C -54C96B61 -74A99589 -7E60A886 -8FAF588 -634DD09 -1258CA8E -13E40785 -20861E8F -69BF3004 -E91E2BC8 -583A44C3 -36FD8D36 -572B4202 -BE43EB2C -65F871F3 -723C1C02 -65EBEF48 -8DD407C6 -513D6B1B -150993D3 -4C771124 -A18E6FE4 -C46071C8 -D824EA73 -7A54B17A -4AB1E70C -F7D078B5 -A315F9A4 -9A39A8C8 -CD34D2A6 -8CDEF63D -B273EFA6 -E15B8FB4 -BA2A092B -E540DF83 -33A3B82E -13BB16A4 -4AA79F4 -DCF1D80E -65B77A7E -80CB308 -9A407BA2 -D32D62B0 -DB34DA97 -109F323F -4B07538E -40AD97F -A810835D -6637380B -1ED7261B -DA642F4D -309A47D6 -9009C0E9 -7D9D6E1E -580CCE0B -67F92DAA -1936087F -342D9739 -A191FAF4 -2EF56C33 -EAB9AD66 -FB6E4FF8 -E58333E1 -E42B465D -2D61F572 -9FA12447 -848394C4 -599C9E50 -28675899 -8610332C -968735B8 -ACE06F66 -266C841B -8512CA53 -A25D3088 -D55264D0 -AC3678A9 -D1DF668E -5BEBD716 -DE986F08 -17DB60F5 -B88254C7 -BCA0E5B2 -E78B3459 -494B6F35 -5E0408F6 -A8638621 -62C27360 -8D98C864 -37EDB15B -ADC93344 -4197C21 -FEFE1A30 -ACD03EBB -A3A230A3 -45741EE4 -DE86AD8D -CDBB302B -303A5D5D -A42863D5 -9019ADA8 -EB8E036C -A5558A5D -A4D5AF4B -F04E0726 -C5AEA4BE -FCB9BC09 -3FF2E51A -53E510E9 -86FB3D5B -3031BBDC -1294451B -48879312 -972E95C1 -B8B861CE -FD180B55 -F2930D40 -31C5CF76 -8C132827 -CD696B0C -1446B194 -436D712D -9089677B -493A420F -DF82C186 -377516B8 -20ED2C1E -956EA0C3 -D26B4EEF -BFE59283 -B4D36719 -67B01DDD -6F3CA60 -BF6B98D -1B120FBA -7CF4D06 -83091BF6 -7D3F5D85 -D3E48FAD -E3025BBD -CA30F611 -64D1D991 -6A688C9 -D06F9682 -D346BF -E4DC58EB -4C4F7AB5 -9D5CBB9F -5536C074 -CCD9D1E4 -FADD0C6F -769C50EF -A1F0E40D -72EF3FEF -C421D7AC -182D7491 -3FDDA320 -49F136EE -4EFABBAA -7228A4DE -40A616A9 -EA37E4ED -5DADA164 -2F9C5671 -4D3D4CD3 -3A68B35E -7A26619D -11A14309 -D886253C -8F545687 -3666D9FB -131A5557 -9644C9A3 -FCC47DF7 -7CCDF226 -9FCBB958 -9DB97B96 -630B5596 -1B592B4C -2AB5341F -5817D559 -3C0A5FBE -F65E3830 -1D38ABAB -353E9D4 -41647BE0 -63DC6FC7 -CABC6846 -A7B8001D -2C018A1D -435D877E -3E5F838C -9709BC31 -ACA0EA75 -86A06AB -DBB06480 -2A09283F -D3A83953 -90967E13 -D055B4E1 -3365DA22 -E3FFD521 -50205ED7 -E907F5E6 -4D7D054C -C66CA376 -2A72C5C6 -793120B3 -170AC5FD -C4CFDAA2 -21A3CE3A -19F354F0 -FCE7F112 -279C9605 -AA9FBB98 -E269592C -B8E5DE7F -AE0A77D5 -45B4CF97 -6E9EE4C1 -C31F7C62 -D9E8C76C -75925FEC -EE34024B -73FEA2CD -BC601F7D -75776A1F -AC2A0090 -AA6E1956 -64C62B96 -D73C3066 -2F9C7E78 -7F1529BF -5974399A -79D31554 -2D559A9A -458A1BE -A820156A -26764010 -981D62C3 -A5C8534B -F8A5FAE0 -69EA2102 -2F62B77 -2AE14076 -88EB9A0A -36B5EF31 -73E63D55 -D6A15D81 -F5C8A216 -1EEFBC6A -8F16F5B6 -87064008 -7EEAA78F -35A4B04C -AE70F49 -9642CC0B -3199A9B1 -F0E6FE1C -F682DFA -E500C5B1 -AA1132D6 -3B3A2D9F -86C9A21E -BE1422DB -2218AF29 -64512A76 -C4624FF3 -F4E52FE4 -8473989E -269C4193 -B67528F3 -76FD1A6F -ACF6869B -DCEBBBFD -3ED92226 -3FEA0905 -2C4A131E -4CC5DF7B -63E3A62 -988BE035 -BB06A621 -61C2E087 -C2E46B3F -78010D43 -9EC6DFEB -3781CAAF -6D000EA0 -7E952EA8 -2874E849 -FAA54995 -45DB5F56 -8CB1094F -336FA04C -8CCD3F1C -A40704F0 -7AC652EF -83E998AF -8167F5FD -AA7527B6 -543AF979 -F21F16B6 -9A4E00F -1686D0AC -FB0EF404 -EBA9E0F4 -1A9BCC03 -F66D4C53 -4328EB30 -DF52A096 -4A61DDDE -3F19448E -5F3E0EDC -C9FEB2B1 -D8EDCB6 -4EAE672C -47FB8C0A -B4D64E67 -7F5AA323 -38796C27 -3ED30872 -6241EEE1 -AAFD55B6 -F31CA43A -54CE5828 -6D9103FC -665303B -ACD9B1CC -4961E187 -EEDB6D29 -544577B0 -9CC76FDC -718802FC -2EDC02F0 -6735768 -FC351962 -30F3C426 -7BD3050D -4C19A7C -97DC5F3C -720D7F42 -2F735FAA -B067A6FB -4F5EF847 -F500ABE8 -FD9E7B9E -8C37652E -B6189BE1 -BAEF411D -2584FC7F -FEA99C78 -873C71EE -51491598 -8BCC9600 -60A2176C -9D6D9475 -94E1A54E -78124EEF -4DDDA3D5 -DE77F79C -67E3A57B -1E75B5B5 -290C7ADC -30FDC46D -63BDBBD7 -9E61B234 -666593DE -8C7C1E27 -9C723CAF -EF1F2DDE -CA69CD52 -4DE571F3 -A0AD3A46 -902EB90 -D761B7BB -9F209F04 -15B1B5F -5C389CFF -B736B159 -97994EC -A2DBE074 -353360C5 -19E771B -94A72285 -2F4706A0 -64CC6476 -627BE8B7 -90FE94EA -7D02778 -2EEDEFD1 -9A5EF7C -E7B7B437 -F21A3517 -F33DF1F0 -7A865164 -4BFE70A7 -88A8B45C -C0D320E2 -E93442D3 -AA086067 -11B873ED -1BE002FE -2E799A3 -2AACAAA0 -EB1A91C7 -9FA88D6D -4D956843 -75FB8348 -1584A0EB -4C9D1E1A -413548BF -FA0CF448 -90D1256 -BEB74BF9 -EE7C6510 -765277BA -A6081E2D -E616DE16 -EDFB0495 -12EDC382 -DA64FCA3 -E258DCC3 -92E0B54B -B41B389A -D818F160 -F8F1A55D -17916C31 -DBC21683 -3272DA3 -931C08B3 -9F8EA606 -232CB0D7 -EC870992 -B5F586AB -3ECEF68A -BF7BE567 -2C009224 -C2BE6397 -90EE0A64 -FC3E6BC3 -F1190F98 -1D05D7F8 -52AA90F8 -FF7C45B0 -7F5579FE -6609C7B -9B56CD69 -4A6830B1 -ECF9E86F -62331FA4 -294B7FAB -DC7DFBA7 -4DFA98F8 -CA6447C5 -B0416FDF -5FAD4523 -BBBEA8BD -47DA6D1D -FB598321 -E4A1EBBB -DD0CD41D -77FC8F60 -E4D74C7F -E4B2B064 -52EF568C -91E87E37 -FAF6069 -6E28131E -4D39B103 -59A3C4EC -3AA49C6E -D90E743 -44FC3B9A -7D181041 -AD89A0E7 -616A565F -129B06C1 -907298A -5E98085E -9648A06 -4FE2BFCA -F73FCCCC -62DC849B -BB543EC0 -EF301310 -9801EC66 -43557EE0 -2C382E49 -5151FB5C -3C1DCC5B -DD1C153B -77B3F30 -FDE0F3E1 -C967E75E -D5C68278 -6CC1FA37 -A3FED046 -5DE77F4E -FB7F40F6 -2C9191BB -D089B672 -1E9C6BAC -756468C2 -13352B81 -D2CC73C6 -55B4D4BD -8D6BD8F4 -65F7C5C0 -34A629D9 -79424449 -1CE03FD7 -451FC3D3 -255B39FA -F5F01286 -D1623E81 -4B33EB3D -CB2326EC -9C1189DE -1ED995BA -1298FE00 -A5FDB07F -D80D48D -575374E6 -3664F373 -5ED3FE -2171B235 -413BEA38 -FD67D4A -34F10135 -F4544A59 -16BA37D6 -649879DE -EE8D839B -A545FEF1 -4573F79 -D53FE034 -F4418DBF -92181012 -FB81741F -376DF3DE -19763A21 -47FB6EB7 -7F997F6A -CB94D301 -36461AC2 -A3C2378C -2541AE5 -67D92471 -EC619D04 -3BE21ECC -A441FB3D -A19F0955 -39492084 -6C680626 -C8D37B17 -68B215A0 -8B3846B1 -9B21F1DE -8021097 -EBCC81B2 -E9310566 -AD50FB31 -AF65F01B -739CBC38 -35573201 -F7F58733 -4015ACA -6AA65104 -33202FD0 -B5B1AE8B -C1C66F1C -8BA3BEC9 -E55A2ED0 -49ABBD4B -42DD0652 -A936340A -8EE63409 -5C64BE2D -4D47E9F -745994DC -7CCF78A6 -516C7BF5 -395F9C6 -58E11E54 -73EAA341 -E2D4631A -C3552D0F -4CF36F47 -3FE7034B -EEFCB8C6 -8219943B -E800BB09 -55544B91 -A3292FE8 -89BC5746 -F63B4EE1 -E866DAF9 -E99B2D4B -BB57E938 -34FB7E1A -EBB559C1 -24838BA -48075561 -9E621607 -998E5D98 -DFCF97D6 -2ECF6FC5 -15EE774F -C3E53B77 -8EF5F879 -763B1F55 -5C90BD9 -267E7FCE -625E8032 -F12724C8 -635FC29F -36AF3D44 -B7D2299C -6E8F0DBE -A76006D5 -723C72E0 -ECA467C2 -5C7DFAD4 -23AC163E -F306D785 -67972062 -57D31D2C -4038D82E -D21756BD -257A9123 -BE96CEDC -917019D1 -362C4F33 -2A305FAF -D4389CC3 -4C435238 -D68F1F0C -372B2979 -A7D6B646 -53A2E4C2 -19E556E -62D716A7 -64918481 -4D3AA8F0 -BA8C6B54 -2468C102 -499AD5B3 -81AE28CD -42E94077 -C969675A -341B58FE -41159415 -ADE3FA94 -FF5F42BA -379C83ED -A7E678F -C2D60CBB -CC75230C -A12B9169 -9CF6EE67 -2DD905D3 -EACCF580 -367F9A41 -477BB16D -8438B576 -756D14EF -980599BD -C181C6AD -99A3EF95 -151D4F12 -CD85DFB7 -695F12C9 -4CF48772 -CB00E50D -B9E2AF4C -97EC19E3 -54810B59 -EC4F2D89 -ED77DA60 -19451088 -D5A52E95 -F6FAA3D3 -F2458DDF -D5AB6D8 -D4042924 -AEBEC90 -505DB6D0 -52505B2A -ED9CB8B3 -DB06312E -C508C5AF -4279ED2F -5C72A874 -15E22E84 -54E967EE -80A13FE3 -EE346264 -3569BCA7 -9AA9263B -2BEC95EA -966F3368 -B74F6A2B -25ADEA56 -30A1BCE9 -71EE7AB3 -74807D9C -E4C0D662 -A62305A1 -6B9FB6F0 -C2CAB758 -E3FA413E -5266648 -754C0A13 -C4FD0D47 -BEFA676C -786AFDA7 -297AA674 -F2895DA0 -72A98C20 -A662B307 -54DFB586 -8147050E -CF7C5819 -760EC4AA -F011339D -2D496BE5 -6FD43E03 -1DFD893E -814ADCDF -B7C38DCA -2149763D -EB58B9BA -9F1B81B2 -94C15E0C -5A9923B7 -6C4E0E11 -C63C3D44 -BF9AA840 -1A3E83C5 -B81CEED7 -7E9FD999 -C1A15CFF -B28F657F -287D5990 -8DB5B01E -E241144B -EB0EA64E -884A8775 -99F5DBEA -3DBB21D6 -CC9472CE -B932014E -22A35325 -7B22DCF6 -882BB2C3 -B47CDAE -28767633 -ED17CB12 -6302A17F -25D91C08 -4D61BFB6 -FA240AD0 -E9DBF560 -F0E9AD0E -835C152D -61E5F126 -C176F8FB -B793DC1C -622E04B -D9FB6072 -60124DA7 -8BEA323D -6C496459 -FBE1E578 -F1C73C9E -6A7C4C58 -43F1DB50 -E9BF93AC -B7DC5C72 -2E68083B -F3DE081F -AAA39D71 -73406424 -B99D0139 -E4FB0C67 -142AB82D -3312CC57 -7A3BEDB7 -6B6E42D2 -F8330EA0 -2FE05DA6 -3E6BB118 -3C73E09 -5FDB1471 -6A226A31 -88792727 -78708ED3 -7A095177 -9CCAD23E -C3B75180 -226F8D4C -46DD1DBE -D799BE11 -1F852432 -7361585D -97380EF8 -4F1A8127 -2EB7A73C -35B892A7 -933075A1 -2B6D3BEB -BCDCA6F1 -E9409A22 -3A8E5575 -E37AE0CA -97C2866C -BA575BC0 -C16049A3 -79FED5B1 -6356E153 -98789BE6 -47B95292 -FBDEC30C -2275A4D -632C436D -FDCBB3FE -4E0ACB8D -36A77186 -593FDA25 -D9B74A5D -18021557 -3919EF9B -DDD00927 -B0C6DFEE -F761C0C7 -886DBB5 -807A21DF -778F06D1 -27A67D08 -2CBBD43E -2696EC44 -1F916066 -DE884377 -1472CADD -F30A91AE -89C35DEC -84E5487E -792613D4 -1E59B1A9 -B18BF896 -8D7034AC -A144CE10 -F2FFC2AD -2F5FBA7D -FFEDDB97 -7C506BFD -85B811DE -CC3AD4C0 -B6CC2F1 -BFD63C90 -281E81D7 -89E82B39 -E5371DE9 -5BB68ED3 -3DA62382 -3C8CBB1D -4BE92297 -878783A4 -F925E76B -77DE554E -7EB5914E -9B3F869E -F47FA82D -23E861F2 -19E38BDE -C26E5CA7 -317C9C64 -B96B12FC -F6EB43AE -F979DCAE -DD5BE081 -5B11401 -3C4A8866 -38C6F309 -2FE6DD71 -84E2BDC8 -2FA36F63 -F0D171C -8AAD8CA5 -92D5E506 -D4CF4E62 -82DFFC21 -2C686264 -CDDA9A2B -98CF101 -847DC151 -C0FEC6AC -A1638360 -DD36C966 -A6A8635A -F700C63D -48377DC5 -138CB9D1 -857331B5 -4844609F -E29224CA -A5079F42 -3B39EA92 -F020BFFE -4859CF8E -7C1B1E1E -DD95482D -24C31760 -3555FB83 -B1D20BED -403E6587 -D04E4309 -74F63A1 -EAFDC6CD -781795C6 -BA9A1FD1 -60F61FF3 -B93EE92A -7BCCFCDF -477FB17A -B508142D -D2BC8CD8 -F11D8200 -24A8149A -8F00F213 -3822F374 -E37B6219 -4727F504 -12CD7551 -5FD2779 -E8EC01F6 -29CE5CE4 -1EDDBCF9 -69AFBC0F -11B3CB87 -E39AE82B -E66CDCBF -6824DB75 -7183BE54 -12A11956 -ADA59196 -437E5E61 -F1A7F4A1 -671FDE0A -9202817E -33ABACB2 -B0705AB1 -39952407 -D3672EB1 -A03BD94B -B46D2252 -1DC47573 -EE4C78D4 -B6E4D8E0 -12C2206A -5656E1EE -4D9D4988 -35E36416 -3AC9C8F2 -2161B02C -1B5A8615 -62587331 -CC4036C -EACDCEC6 -F40C98DC -9C8FFDE9 -D87FB3C0 -C55AABE7 -1BE31E0B -C0796911 -C08C311 -E41B196D -E4FFB7A3 -2483C766 -FD348C63 -F294631A -7B74B50A -D6416CD9 -66559F6C -A7CE68E0 -ACD88C63 -BB49939B -7987A018 -E1797428 -CE39ECE8 -D7B3DA7 -8F2A3F0C -37E3C72E -21F1A24E -57AFCEF2 -AB8CF2 -15B5A4E9 -94094315 -29C3AEB6 -A56B4233 -6D57E64E -3A7399D2 -103AE960 -8B93E67E -D5193079 -767DA47D -88AEDE6F -ABCFBF34 -2650782C -7A716475 -C86C9BBA -4423420D -3AF8FD02 -72E202EE -5A264F7B -4E103072 -4DA5A0E0 -59319F97 -B54F9AC -556DF0B3 -ABAD7DC0 -2A715C13 -9D443D0F -54BDC92C -1EC2B967 -80BE3AC2 -FA646E8A -2EE396F1 -8B0315E8 -9F52B6E -DAD30422 -2E9B6CDB -8686D47A -5D9DB3C7 -717E799B -20A4D4E5 -C2DC8AE4 -F630FADD -8C7DF047 -65F4928C -BE66D11E -6004484D -C1B509AB -FAA4C75F -B3D272A0 -7FE6F083 -A54B6584 -FC3292F -4D27DDFC -A1ABC224 -872FED55 -D235AEC -27ED8546 -1B170B2A -CE9E5C0 -2267B02 -285992BD -F855CC8 -8FFB1F6F -C7BDDF81 -349B4F5F -B9B28843 -D5D532A0 -8FD7BE3C -2DB04DE8 -C7D0C2FD -B6822987 -1FE0710D -8EADA490 -A03F99CF -F3E7F902 -F56CCCA3 -CED5B6BF -D6B3DC0D -92AA9FE8 -351208D -A1C9623B -5802547D -3480D77C -404D4E65 -679025BA -905FF962 -B7130CA8 -5AFA9CFE -2A654EFC -26218A8 -473A88A -5E3534CC -771FF1E1 -EADD6296 -DF7157B3 -D48E42E8 -3D6E848B -29CD6C -68732656 -A6C6D52A -B50279FF -705B645 -6DF7F119 -34152606 -72948D92 -18BEE72 -36BE21E3 -C34FD53A -9765DFF -E5C9B4AF -4604B155 -DEAC2388 -7841FE0C -2E275885 -3EE65330 -EB66439B -FF4AB5DE -67EDA5EA -BB722F57 -6A645B7 -DE9DD302 -5AC7601D -371B5D5B -42BAC84D -21C7AA9E -F4ECBE94 -554C8B8A -B7C8BB88 -4C77DB1D -D4D8F3AC -DAB292E5 -85D906E8 -47785703 -9CEE88D4 -7DB86DB7 -694B5A34 -DE77B361 -E8DE3CB9 -315EC35A -A71943BC -C297B8CA -55EA528C -A11AF15D -1490835E -19DA117B -403B0CC3 -FF7DE389 -ED6C22E8 -6F8A8782 -7BF2BA9B -6C95F5DF -F8270769 -AB421268 -F06B05EB -8FF7DE5F -F2AB2FCD -A5EDD602 -31F05712 -3C269177 -67D92F11 -38D8D3C5 -2047013B -8E8BA724 -EB6A773 -5AF14AD1 -49910D46 -C9D6F784 -B44B09CF -1AEA48EF -2F12BD47 -10E3F7C9 -39EA8108 -B88ADC9 -19DAC1B4 -554908DC -587A0A7E -109D1E5B -1920E3CF -BC49C914 -C1EB74A7 -A5E9A494 -5FA5B8C9 -320673C2 -CE643004 -720E4075 -FDFED2FE -89C22F8E -40887408 -3235FF6B -A906F59D -F6F98F12 -7122ECA4 -4CDFCB42 -391F2365 -53AE3667 -6CCCE2E2 -44877A8A -92561CAB -DA5DE0E7 -73B898D6 -2E37229E -ABAAED3C -21087331 -58C85412 -8BB37690 -1256467F -6EE9FAF7 -DB0895D6 -954EF968 -1C7693BC -5786650F -7D441E12 -10AA9174 -492C6A3B -34374CC9 -98E59E7C -5B7BD4E0 -D1124C9F -B5B3362F -8ECC58C7 -8EB0E23E -72991400 -13DF853B -789E8DFE -D85E60DC -A168D4D -C3B6FA3A -11443EE2 -F63F9FDD -1A14A7A5 -5EEBFD5 -B24D582D -AEA8F125 -4AA038EE -5F6A1A16 -CBADD812 -340605AA -8BD8F6E9 -B85F3A6A -A585AE8C -6D12D2B3 -17C97329 -DBB835B9 -789C3DF4 -E048D462 -BECE080A -506DE5CA -63C4FA5C -7C2D8103 -689A3516 -B218BADF -8B7F0BDE -85B17891 -8888A9C6 -3DFC9FA8 -5F2859CD -FF72AE34 -9EA3FFCA -CF2194D2 -53B56E7F -C7009619 -B127FD51 -3A513DF0 -E9147D4B -2FDF3C37 -22FA1629 -61480015 -57EE267A -EE04DA43 -EB2D289C -2C102144 -B012EED -B1B339C8 -AC1EA89 -3A4420D0 -5623907B -B0613D35 -A70F1B2C -589E3EA7 -F998AB7D -9566E921 -B133DB2D -A3106F6A -EFB4518 -6AA3FB8F -C505C8DF -65032E33 -6D3942DF -333553CC -BF392E2 -6C77F980 -39211AFC -9E0B71C9 -A3BB7123 -7CE16B9A -F15BB634 -BD68DE3E -77BB27AB -BB72659C -BFA916CA -7022CF20 -EA64C93D -B61C32CC -20201879 -148DDADC -58977 -8D5CC2E6 -76E678BD -5655B362 -587EAB4A -599E3DCF -7B470038 -E87E82DB -9088EC5E -ED9F9E4C -3DD98E27 -5AFA5052 -3DF313C4 -BB22A60D -44D97BDA -601409F3 -CD1D3CFE -7EAE52D0 -41ABBAA0 -A1D7C883 -FFE2B4C9 -13717374 -9DD27EC8 -29301EF0 -87953D6C -9309161C -C91DFE7C -DD5EC452 -F6C27DF2 -43B433FD -6D16B93F -92F09DBA -ABB598EF -B49A721A -3A03EE56 -3177D3AF -5D24FD94 -FEF88FB2 -52B3170F -64264DCC -18B683B7 -6B21935F -901A396C -4601FB55 -51F2547E -DD37C23B -35E6B3DF -31ABC979 -C7223449 -ABCA9CFB -A8F57AFA -A097240 -78704130 -7F1D7661 -456C2409 -63E31F62 -FD0D4BB1 -97FCC39 -951A7C93 -893165C9 -E86163CC -25F5694C -8890910A -43F3AE36 -55D414A1 -1ADDD3BA -C7EDFDDF -5A8607BA -219D3208 -27BD79E2 -2E9EA4B8 -5D8F951A -F9E880D5 -B2C7612A -862CCCF3 -7EDC71AC -1B6EA644 -EC3AA9A0 -970224FD -6C0DD16A -C589D1B6 -71AC91EE -C75B0206 -50232786 -316AAD4D -F4D5A31B -E30CCF43 -BD72BEAD -26DE4F8F -56E97741 -9243E978 -F7E2363D -BAE2CF31 -6367CFB1 -B72ED4E6 -75216393 -4626E74F -61194364 -8D6726A8 -458611B8 -1B536E4D -837AAD1F -F5A226D8 -8BB37701 -31F19003 -8E48DEEE -9DA11E9 -3BBB5BB4 -C6F15B5D -1A53A4EB -69AADAB -4FAE6295 -F0943601 -A449516E -BF7EE395 -176B1370 -F55873EE -553FEEF0 -9F3AB09 -2539B92E -F6803BC -BAA192FB -DBB0AD5A -B9C5415 -F92D0588 -88B9E738 -A033C767 -A1CA1EFF -5AC07200 -AC60C03D -17FE20F9 -B898B9AC -51AF425E -2706FC42 -F2A258E7 -353652D7 -CF3F89EE -63A13050 -5E6A7997 -153FD92F -1D0E8614 -6E504447 -5AAEC133 -9B6E5499 -64D5EAE6 -A29CFBAB -52B44B68 -8DC7C01A -704EB2F1 -395F1F7 -7D897418 -2FC66846 -ECCE81AE -21CD8E31 -B2EFA3D4 -16C4CD41 -D6A21ED0 -944897F9 -F495D730 -B4317C3C -8C074582 -22F6A9D9 -CE4425FB -FB08BCBA -DF07A006 -293AD5BA -BD224A44 -9DA6701B -DAB46DE4 -9F88773B -57CC02C7 -7A6B68E4 -55A54D48 -BCFC1C53 -DF64F920 -A9FE6014 -4C64DB55 -5FE9345F -412A1E48 -45D41945 -23B44D08 -8D5563A2 -26E5E437 -CECDF4D0 -1BE55025 -84329F92 -37C97F8F -C3CDE976 -580955A -C79E1131 -C5BC58E7 -7D14509B -3DE94089 -1B78FE71 -49A0ECD9 -501D09B1 -F30135CD -B0FA41B4 -33B11313 -32AB01B -635EBA76 -666D7FE5 -68CCC93 -59B0ADA5 -B305CBAA -1C553509 -5E564F7C -F057084C -52811FC8 -987465B2 -461DA750 -F0C471BB -3C9D3E64 -73C920AF -355A26B9 -3A1FDD13 -CEA3F7DD -66C0687 -1319291 -9045182D -174C724D -2A491012 -BA53519F -A62B41D8 -F6E1559E -25F93E6F -2A40C5F4 -C63D1AC2 -82598002 -2B81101A -63442848 -3788BB2D -74DDC016 -214CE0F4 -9CBAA8BD -9288E1AC -EF76E528 -719E7BAE -BD579EF6 -4E6B0C62 -6285F757 -9049BDA3 -80BFE3C1 -4344B7A7 -4552F1DD -DE2C0DAC -86346BE2 -A0A897E7 -1797D93 -6CF3C7F0 -7592D9E7 -CFB46F1E -17D6FF93 -87FF1727 -198FC755 -303540EF -78C07416 -46CB391E -8D441653 -3724DA3C -860D4DDF -A99F046E -4B167D86 -E2AFCBE9 -6608F2D2 -4E49A130 -3C64B760 -958BCEB3 -8C784B24 -5E07EF07 -7E6CAC6A -B69765D8 -65897B6D -60A8FB7D -6706E0E1 -142E4310 -15C4944C -F6A075AD -3CF66DF8 -CE1EFE72 -D6495864 -2BDEFA6B -9E511045 -F2E2E9A7 -B71B03EB -15DD8D69 -65E5A555 -52C644AE -301A8F69 -35075232 -17ADE8C4 -A2C808CC -F1A4C57B -D6EE3EF3 -85942F72 -26011F23 -D4211E97 -595E1A12 -6886CE0 -FBD6F396 -D10BD980 -6615476D -4662EB8F -F80BE955 -93A6E68E -4C3D4CAA -5838D0CB -756FB6E4 -F0BC8312 -EB89BE83 -D34E119E -34F860EC -F371DC73 -BB166E0D -CE86AF89 -C177E633 -A19C1D9B -B1DCBF1B -D7310057 -2452939E -120A830 -F92A9928 -64877B92 -3D69A585 -178187B6 -146C0495 -9A3D8886 -C79478AD -9A429976 -29795A97 -32BD0034 -1EE08CD -8982284A -ED362AC4 -4A1AC734 -6FD164B3 -422ADEBA -9374B593 -BBFA8568 -1C0B26A5 -5DF68365 -CFA1D689 -1C9509C2 -1056EAC4 -D492D000 -64076487 -2C1FB65B -9E1DEBC7 -C5AECD05 -39652664 -57A1B9F9 -3652484 -E8CCF72B -CB7EC405 -7DA97E78 -7ACE1B2C -A5DC0B75 -40C14422 -777B17AF -5AA3FEDF -319C2B1C -AB8EEE5F -159D66E5 -3E479D0 -12AF93DE -55EA550A -38853E1F -FB943864 -781FA52E -4FB9C9FA -377D8866 -8411E296 -641D997F -1933684F -27A62DEF -50E15F68 -755BCD7C -5DF3466F -494A937C -8763C6BD -C04B98E0 -E9E067FF -444151AB -C5FC7398 -5EC7D30E -E0610B7E -76CEBB5 -B15D9821 -37B2D1E2 -CC1249BF -3E064388 -246B17B3 -4A342228 -529E849B -F25F250D -31F3E925 -D1112DCA -DA6A8BC9 -2A7789D8 -C0C2C72D -4BB23226 -68166638 -4EC7519F -D559B4B7 -8035E823 -DFB06DE0 -2B4B86 -83D6F12F -84AC7F7B -7139E98B -C42D8AE3 -2992AD9C -E1E24DA1 -838772BD -CA28D517 -3606947F -B9FDFA59 -6C4F8489 -76DBFFD4 -3F0BFDF6 -1B04AD1B -8BA40134 -842A54F6 -621A0DFE -1F3729FC -C53AFEFE -CD5F1E79 -D2C0C70 -30A4FF4F -D384C76 -D73B9B17 -C74DC3F9 -E5ACD113 -901E6D5D -D376A71F -57BA08F9 -17E25669 -F7485021 -BCD1B9C5 -90C1A916 -EEF9DE6E -6AD37907 -40B05A7B -4A56C1D -901093E1 -5424EEE9 -3336300D -8B1767F3 -707A4B23 -37290194 -13A5E016 -C25902C0 -5C04C3AE -B7D84F4D -D57A495F -EE168042 -1584DB78 -7DBFDBD3 -DBE2218D -9EED8CD4 -2A562C0F -C76F7E04 -8FCA82B8 -7211C54F -8E76E82C -9BAF59A6 -C1E7B9CE -28E9E29F -6746FB40 -7841DDA1 -37D07C7 -88A5CF5 -4B0B8A4E diff --git a/finn-rtllib/memstream/sim/memstream_tb.sv b/finn-rtllib/memstream/sim/memstream_tb.sv new file mode 100644 index 0000000000000000000000000000000000000000..4b2e850415e88fdf820931e2d6b75f6aac2f7267 --- /dev/null +++ b/finn-rtllib/memstream/sim/memstream_tb.sv @@ -0,0 +1,212 @@ +/** + * Copyright (c) 2023, Xilinx + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of FINN nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Thomas B. Preußer <thomas.preusser@amd.com> + */ + +module memstream_tb; + localparam int unsigned DEPTH = 256; + localparam int unsigned DATA_WIDTH = 32; + + // Global Control + logic clk = 0; + always #5ns clk = !clk; + logic rst; + + // Configuration Interface + logic [31:0] config_address; + logic config_ce; + logic config_we; + logic [DATA_WIDTH-1:0] config_d0; + uwire config_rack; + uwire [DATA_WIDTH-1:0] config_q0; + + // Streamed Output + logic ordy; + uwire ovld; + uwire [DATA_WIDTH-1:0] odat; + + initial begin + config_address = 'x; + config_ce = 0; + config_we = 0; + config_d0 = 'x; + + ordy = 0; + + rst = 1; + repeat(16) @(posedge clk); + rst <= 0; + + // Write Parameters + config_ce <= 1; + config_we <= 1; + for(int unsigned i = 0; i < DEPTH; i++) begin + config_address <= i; + config_d0 <= i; + @(posedge clk); + end + config_address <= 'x; + config_ce <= 0; + config_we <= 0; + config_d0 <= 'x; + + rst <= 1; + @(posedge clk); + rst <= 0; + + // One Round of Stream Read + ordy <= 1; + for(int unsigned i = 0; i < DEPTH; i++) begin + @(posedge clk iff ovld); + assert(odat == i) else begin + $error("Unexpected output: %0d instead of %0d", odat, i); + $stop; + end + end + ordy <= 0; + + // Full Parameter Readback + if(1) begin + automatic logic [DATA_WIDTH-1:0] Q[$] = {}; + + config_ce <= 1; + for(int unsigned i = 0; i < DEPTH; i++) begin + config_address <= i; + @(posedge clk); + Q.push_back(i); + + if(config_rack) begin + automatic logic [DATA_WIDTH-1:0] exp = Q.pop_front(); + assert(config_q0 == exp) else begin + $error("Readback mismatch: %0d instead of %0d", config_q0, exp); + $stop; + end + end + end + config_address <= 'x; + config_ce <= 0; + + while(Q.size) begin + automatic logic [DATA_WIDTH-1:0] exp = Q.pop_front(); + + @(posedge clk iff config_rack); + assert(config_q0 == exp) else begin + $error("Readback mismatch: %0d instead of %0d", config_q0, exp); + $stop; + end + end + end + + repeat(6) @(posedge clk); + + // Another Round of Stream Read + ordy <= 1; + for(int unsigned i = 0; i < DEPTH; i++) begin + @(posedge clk iff ovld); + assert(odat == i) else begin + $error("Unexpected output: %0d instead of %0d", odat, i); + $stop; + end + end + ordy <= 0; + + // A Round of Stream Read with intermittent Read Backs + if(1) begin + automatic logic [DATA_WIDTH-1:0] Q[$] = {}; + + for(int unsigned i = 0; i < DEPTH; i++) begin + do begin + // Randomly delayed Readiness + if($urandom()%5 != 0) ordy <= 1; + + // Issue and Check Random Read Backs + if($urandom()%9 == 0) begin + automatic int unsigned addr = $urandom() % DEPTH; + config_ce <= 1; + config_address <= addr; + Q.push_back(addr); + end + @(posedge clk); + config_ce <= 0; + config_address <= 'x; + + if(config_rack) begin + automatic logic [DATA_WIDTH-1:0] exp = Q.pop_front(); + assert(config_q0 == exp) else begin + $error("Readback mismatch: %0d instead of %0d", config_q0, exp); + $stop; + end + end + + end while(!ovld || !ordy); + ordy <= 0; + + assert(odat == i) else begin + $error("Unexpected output: %0d instead of %0d", odat, i); + $stop; + end + end + + while(Q.size) begin + automatic logic [DATA_WIDTH-1:0] exp = Q.pop_front(); + + @(posedge clk iff config_rack); + assert(config_q0 == exp) else begin + $error("Readback mismatch: %0d instead of %0d", config_q0, exp); + $stop; + end + end + end + ordy <= 0; + + repeat(2) @(posedge clk); + $display("Test completed."); + $finish; + end + + memstream #( + .DEPTH(DEPTH), + .WIDTH(DATA_WIDTH) + ) dut ( + .clk, .rst, + + .config_address, + .config_ce, + .config_we, + .config_d0, + .config_q0, + .config_rack, + + .ordy, + .ovld, + .odat + ); + +endmodule : memstream_tb diff --git a/finn-rtllib/memstream/sim/tb_memstream.v b/finn-rtllib/memstream/sim/tb_memstream.v deleted file mode 100644 index ad3efad5bd70c37a860ddb0ec5bff1c2e72c15f0..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/sim/tb_memstream.v +++ /dev/null @@ -1,369 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -`timescale 1ns/10ps - -module tb_memstream; - -//parameters to enable/disable axi-mm, set number of streams, set readmemh for memory, set per-stream offsets in memory, set per-stream widths -parameter CONFIG_EN = 1; -parameter NSTREAMS = 4;//1 up to 6 - -parameter MEM_DEPTH = 9216; -parameter MEM_WIDTH = 32; -parameter MEM_INIT = "./"; -parameter MEM_CHECK = "golden.dat"; - -//widths per stream -parameter STRM0_WIDTH = 32; -parameter STRM1_WIDTH = 32; -parameter STRM2_WIDTH = 32; -parameter STRM3_WIDTH = 32; -parameter STRM4_WIDTH = 1; -parameter STRM5_WIDTH = 1; - -//depths per stream -parameter STRM0_DEPTH = 2304; -parameter STRM1_DEPTH = 2304; -parameter STRM2_DEPTH = 2304; -parameter STRM3_DEPTH = 2304; -parameter STRM4_DEPTH = 1; -parameter STRM5_DEPTH = 1; - -//offsets for each stream -parameter STRM0_OFFSET = 0; -parameter STRM1_OFFSET = 2304; -parameter STRM2_OFFSET = 4608; -parameter STRM3_OFFSET = 6912; -parameter STRM4_OFFSET = 0; -parameter STRM5_OFFSET = 0; - - -reg clk; -reg rst; - -reg [31:0] config_address = 0; -reg config_ce = 0; -reg config_we = 0; -reg [31:0] config_d0 = 0; -wire [31:0] config_q0; - -//multiple wire AXI Streams -reg m_axis_0_afull; -reg m_axis_0_tready; -wire m_axis_0_tvalid; -wire [STRM0_WIDTH-1:0] m_axis_0_tdata; - -reg m_axis_1_afull; -reg m_axis_1_tready; -wire m_axis_1_tvalid; -wire [STRM1_WIDTH-1:0] m_axis_1_tdata; - -reg m_axis_2_afull; -reg m_axis_2_tready; -wire m_axis_2_tvalid; -wire [STRM2_WIDTH-1:0] m_axis_2_tdata; - -reg m_axis_3_afull; -reg m_axis_3_tready; -wire m_axis_3_tvalid; -wire [STRM3_WIDTH-1:0] m_axis_3_tdata; - -reg m_axis_4_afull; -reg m_axis_4_tready; -wire m_axis_4_tvalid; -wire [STRM4_WIDTH-1:0] m_axis_4_tdata; - -reg m_axis_5_afull; -reg m_axis_5_tready; -wire m_axis_5_tvalid; -wire [STRM5_WIDTH-1:0] m_axis_5_tdata; - -reg [MEM_WIDTH-1:0] golden[MEM_DEPTH-1:0]; -integer ptr0, ptr1, ptr2, ptr3, ptr4, ptr5; -integer done = 0; -reg [5:0] rng; - -//clock -initial begin - clk = 0; - forever #5 clk = ~clk; -end - -initial begin - rst = 1; - config_ce = 0; - m_axis_0_afull = 0; - m_axis_1_afull = 0; - m_axis_2_afull = 0; - m_axis_3_afull = 0; - m_axis_4_afull = 0; - m_axis_5_afull = 0; - m_axis_0_tready = 1; - m_axis_1_tready = 1; - m_axis_2_tready = 1; - m_axis_3_tready = 1; - m_axis_4_tready = 1; - m_axis_5_tready = 1; - repeat(100) @(negedge clk); - rst = 0; - #100 - fork - begin - $display("Starting to generate random AFULL"); - while(~done) begin - rng = $random; - m_axis_0_afull = rng[0]; - m_axis_1_afull = rng[1]; - m_axis_2_afull = rng[2]; - m_axis_3_afull = rng[3]; - m_axis_4_afull = rng[4]; - m_axis_5_afull = rng[5]; - @(negedge clk); - end - end - join -end - - -//DUT -memstream -#( - CONFIG_EN, - NSTREAMS, - MEM_DEPTH, - MEM_WIDTH, - MEM_INIT, - - //widths per stream - STRM0_WIDTH, - STRM1_WIDTH, - STRM2_WIDTH, - STRM3_WIDTH, - STRM4_WIDTH, - STRM5_WIDTH, - - //depths per stream - STRM0_DEPTH, - STRM1_DEPTH, - STRM2_DEPTH, - STRM3_DEPTH, - STRM4_DEPTH, - STRM5_DEPTH, - - //offsets for each stream - STRM0_OFFSET, - STRM1_OFFSET, - STRM2_OFFSET, - STRM3_OFFSET, - STRM4_OFFSET, - STRM5_OFFSET -) -dut -( - clk, - ~rst, - - //optional AXI-Lite interface - config_address, - config_ce, - config_we, - config_d0, - config_q0, - - //multiple output AXI Streams - m_axis_0_afull, - m_axis_0_tready, - m_axis_0_tvalid, - m_axis_0_tdata, - - m_axis_1_afull, - m_axis_1_tready, - m_axis_1_tvalid, - m_axis_1_tdata, - - m_axis_2_afull, - m_axis_2_tready, - m_axis_2_tvalid, - m_axis_2_tdata, - - m_axis_3_afull, - m_axis_3_tready, - m_axis_3_tvalid, - m_axis_3_tdata, - - m_axis_4_afull, - m_axis_4_tready, - m_axis_4_tvalid, - m_axis_4_tdata, - - m_axis_5_afull, - m_axis_5_tready, - m_axis_5_tvalid, - m_axis_5_tdata - - -); - -//stream checkers -initial begin - ptr0 = STRM0_OFFSET; - ptr1 = STRM1_OFFSET; - ptr2 = STRM2_OFFSET; - ptr3 = STRM3_OFFSET; - ptr4 = STRM4_OFFSET; - ptr5 = STRM5_OFFSET; - fork - //check stream 0 - begin - $display("Starting stream 0 checker"); - while(~done & (NSTREAMS > 0)) begin - @(negedge clk); - if(m_axis_0_tvalid) begin - if(m_axis_0_tdata != golden[ptr0]) begin - $display("Mismatch on stream 0"); - $stop(); - end - //increment pointer - ptr0 = ptr0 + 1; - //rewind pointer if it's reached end - if(ptr0 == (STRM0_OFFSET + STRM0_DEPTH)) - ptr0 = STRM0_OFFSET; - end - end - end - //check stream 1 - begin - $display("Starting stream 1 checker"); - while(~done & (NSTREAMS > 1)) begin - @(negedge clk); - if(m_axis_1_tvalid) begin - if(m_axis_1_tdata != golden[ptr1]) begin - $display("Mismatch on stream 1"); - $stop(); - end - //increment pointer - ptr1 = ptr1 + 1; - //rewind pointer if it's reached end - if(ptr1 == (STRM1_OFFSET + STRM1_DEPTH)) - ptr1 = STRM1_OFFSET; - end - end - end - - //check stream 2 - begin - $display("Starting stream 2 checker"); - while(~done & (NSTREAMS > 2)) begin - @(negedge clk); - if(m_axis_2_tvalid) begin - if(m_axis_2_tdata != golden[ptr2]) begin - $display("Mismatch on stream 2"); - $stop(); - end - //increment pointer - ptr2 = ptr2 + 1; - //rewind pointer if it's reached end - if(ptr2 == (STRM2_OFFSET + STRM2_DEPTH)) - ptr2 = STRM2_OFFSET; - end - end - end - //check stream 3 - begin - $display("Starting stream 3 checker"); - while(~done & (NSTREAMS > 3)) begin - @(negedge clk); - if(m_axis_3_tvalid) begin - if(m_axis_3_tdata != golden[ptr3]) begin - $display("Mismatch on stream 3"); - $stop(); - end - //increment pointer - ptr3 = ptr3 + 1; - //rewind pointer if it's reached end - if(ptr3 == (STRM3_OFFSET + STRM3_DEPTH)) - ptr3 = STRM3_OFFSET; - end - end - end - //check stream 4 - begin - $display("Starting stream 4 checker"); - while(~done & (NSTREAMS > 4)) begin - @(negedge clk); - if(m_axis_4_tvalid) begin - if(m_axis_4_tdata != golden[ptr4]) begin - $display("Mismatch on stream 4"); - $stop(); - end - //increment pointer - ptr4 = ptr4 + 1; - //rewind pointer if it's reached end - if(ptr4 == (STRM4_OFFSET + STRM4_DEPTH)) - ptr4 = STRM4_OFFSET; - end - end - end - //check stream 5 - begin - $display("Starting stream 5 checker"); - while(~done & (NSTREAMS > 5)) begin - @(negedge clk); - if(m_axis_5_tvalid) begin - if(m_axis_5_tdata != golden[ptr5]) begin - $display("Mismatch on stream 5"); - $stop(); - end - //increment pointer - ptr5 = ptr5 + 1; - //rewind pointer if it's reached end - if(ptr5 == (STRM5_OFFSET + STRM5_DEPTH)) - ptr5 = STRM5_OFFSET; - end - end - end - join -end - -initial begin - done = 0; - $readmemh(MEM_CHECK,golden); -// $dumpfile("wave.vcd"); -// $dumpvars(0,tb_memstream); - @(negedge rst); - #10000000 - $display("Test done!"); - done = 1; - #1000 - $finish(); -end - -endmodule diff --git a/finn-rtllib/memstream/sim/tb_memstream_writes.v b/finn-rtllib/memstream/sim/tb_memstream_writes.v deleted file mode 100644 index c66807454b9a7f8ff7ab7008a504938740fb03a0..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/sim/tb_memstream_writes.v +++ /dev/null @@ -1,486 +0,0 @@ -/* - Copyright (c) 2020, Xilinx - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of FINN nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -`timescale 1ns/10ps - -module tb_memstream_writes; - -//parameters to enable/disable axi-mm, set number of streams, set readmemh for memory, set per-stream offsets in memory, set per-stream widths -parameter CONFIG_EN = 1; -parameter NSTREAMS = 2;//1 up to 6 - -parameter MEM_DEPTH = 40; -parameter MEM_WIDTH = 70; - -//widths per stream -parameter STRM0_WIDTH = 70; -parameter STRM1_WIDTH = 32; -parameter STRM2_WIDTH = 32; -parameter STRM3_WIDTH = 32; -parameter STRM4_WIDTH = 1; -parameter STRM5_WIDTH = 1; - -//depths per stream -parameter STRM0_DEPTH = 20; -parameter STRM1_DEPTH = 20; -parameter STRM2_DEPTH = 2304; -parameter STRM3_DEPTH = 2304; -parameter STRM4_DEPTH = 1; -parameter STRM5_DEPTH = 1; - -//offsets for each stream -parameter STRM0_OFFSET = 0; -parameter STRM1_OFFSET = 20; -parameter STRM2_OFFSET = 4608; -parameter STRM3_OFFSET = 6912; -parameter STRM4_OFFSET = 0; -parameter STRM5_OFFSET = 0; - - -reg clk; -reg rst; - -wire awready; -reg awvalid; -reg [31:0] awaddr; -reg [2:0] awprot; -//write data -wire wready; -reg wvalid; -reg [31:0] wdata; -reg [3:0] wstrb; -//burst response -reg bready; -wire bvalid; -wire [1:0] bresp; - -//Read channels -//read address -wire arready; -reg arvalid; -reg [31:0] araddr; -reg [2:0] arprot; -//read data -reg rready; -wire rvalid; -wire [1:0] rresp; -wire [31:0] rdata; - -//multiple wire AXI Streams -reg m_axis_0_afull; -reg m_axis_0_tready; -wire m_axis_0_tvalid; -wire [STRM0_WIDTH-1:0] m_axis_0_tdata; - -reg m_axis_1_afull; -reg m_axis_1_tready; -wire m_axis_1_tvalid; -wire [STRM1_WIDTH-1:0] m_axis_1_tdata; - -reg m_axis_2_afull; -reg m_axis_2_tready; -wire m_axis_2_tvalid; -wire [STRM2_WIDTH-1:0] m_axis_2_tdata; - -reg m_axis_3_afull; -reg m_axis_3_tready; -wire m_axis_3_tvalid; -wire [STRM3_WIDTH-1:0] m_axis_3_tdata; - -reg m_axis_4_afull; -reg m_axis_4_tready; -wire m_axis_4_tvalid; -wire [STRM4_WIDTH-1:0] m_axis_4_tdata; - -reg m_axis_5_afull; -reg m_axis_5_tready; -wire m_axis_5_tvalid; -wire [STRM5_WIDTH-1:0] m_axis_5_tdata; - -reg [MEM_WIDTH-1:0] golden[MEM_DEPTH-1:0]; -reg [MEM_WIDTH-1:0] gword; -integer ptr0, ptr1, ptr2, ptr3, ptr4, ptr5; -integer done = 0; -integer i, j; -reg [5:0] rng; - -parameter NFOLDS_PER_WORD = (MEM_WIDTH+31)/32; - -task axi_write; - input [MEM_WIDTH-1:0] data; - input [31:0] adr; - begin - for(j=0; j<(1<<$clog2(NFOLDS_PER_WORD)); j=j+1) begin - @(negedge clk); - awvalid = 1; - wvalid = 1; - wdata = data>>(j*32); - awaddr = (adr*(1<<$clog2(NFOLDS_PER_WORD))+j)*4; - fork - begin - @(posedge awready); - @(posedge clk) awvalid = 0; - end - begin - @(posedge wready); - @(posedge clk) wvalid = 0; - end - join - @(posedge clk); - end - end -endtask - -task axi_read; - input [31:0] adr; - output [MEM_WIDTH-1:0] data; - begin - data = 0; - for(j=0; j<NFOLDS_PER_WORD; j=j+1) begin - @(negedge clk); - arvalid = 1; - araddr = (adr*(1<<$clog2(NFOLDS_PER_WORD))+j)*4; - rready = 1; - fork - begin - @(posedge arready); - @(posedge clk) arvalid = 0; - end - begin - @(posedge rvalid); - @(posedge clk) rready = 0; - data = data | (rdata<<(32*j)); - end - join - @(posedge clk); - end - end -endtask - -//clock -initial begin - clk = 0; - forever #5 clk = ~clk; -end - -initial begin - rst = 1; - awvalid = 0; - arvalid = 0; - wvalid = 0; - rready = 1; - bready = 1; - m_axis_0_afull = 1; - m_axis_1_afull = 1; - m_axis_2_afull = 1; - m_axis_3_afull = 1; - m_axis_4_afull = 1; - m_axis_5_afull = 1; - m_axis_0_tready = 0; - m_axis_1_tready = 0; - m_axis_2_tready = 0; - m_axis_3_tready = 0; - m_axis_4_tready = 0; - m_axis_5_tready = 0; - repeat(100) @(negedge clk); - rst = 0; - #100 - //random initialization of golden data - for(i=0; i<MEM_DEPTH; i=i+1) begin - gword = 0; - repeat(NFOLDS_PER_WORD) - gword = (gword << 32) | $random; - golden[i] = gword; - axi_write(golden[i],i); - axi_read(i,gword); - end - //re-reset - repeat(100) @(negedge clk); - rst = 1; - #100 - repeat(100) @(negedge clk); - rst = 0; - #100 - @(negedge clk); - //start reads - m_axis_0_afull = 0; - m_axis_1_afull = 0; - m_axis_2_afull = 0; - m_axis_3_afull = 0; - m_axis_4_afull = 0; - m_axis_5_afull = 0; - m_axis_0_tready = 1; - m_axis_1_tready = 1; - m_axis_2_tready = 1; - m_axis_3_tready = 1; - m_axis_4_tready = 1; - m_axis_5_tready = 1; - fork - begin - $display("Starting to generate random AFULL"); - while(~done) begin - rng = $random; - m_axis_0_afull = rng[0]; - m_axis_1_afull = rng[1]; - m_axis_2_afull = rng[2]; - m_axis_3_afull = rng[3]; - m_axis_4_afull = rng[4]; - m_axis_5_afull = rng[5]; - @(negedge clk); - end - end - join -end - - -//DUT -memstream -#( - CONFIG_EN, - NSTREAMS, - MEM_DEPTH, - MEM_WIDTH, - ".", - "auto", - //widths per stream - STRM0_WIDTH, - STRM1_WIDTH, - STRM2_WIDTH, - STRM3_WIDTH, - STRM4_WIDTH, - STRM5_WIDTH, - //depths per stream - STRM0_DEPTH, - STRM1_DEPTH, - STRM2_DEPTH, - STRM3_DEPTH, - STRM4_DEPTH, - STRM5_DEPTH, - //offsets for each stream - STRM0_OFFSET, - STRM1_OFFSET, - STRM2_OFFSET, - STRM3_OFFSET, - STRM4_OFFSET, - STRM5_OFFSET -) -dut -( - clk, - ~rst, - - //optional AXI-Lite interface - awready, - awvalid, - awaddr, - awprot, - //write data - wready, - wvalid, - wdata, - wstrb, - //burst response - bready, - bvalid, - bresp, - - //Read channels - //read address - arready, - arvalid, - araddr, - arprot, - //read data - rready, - rvalid, - rresp, - rdata, - - //multiple output AXI Streams - m_axis_0_afull, - m_axis_0_tready, - m_axis_0_tvalid, - m_axis_0_tdata, - m_axis_1_afull, - m_axis_1_tready, - m_axis_1_tvalid, - m_axis_1_tdata, - m_axis_2_afull, - m_axis_2_tready, - m_axis_2_tvalid, - m_axis_2_tdata, - m_axis_3_afull, - m_axis_3_tready, - m_axis_3_tvalid, - m_axis_3_tdata, - m_axis_4_afull, - m_axis_4_tready, - m_axis_4_tvalid, - m_axis_4_tdata, - m_axis_5_afull, - m_axis_5_tready, - m_axis_5_tvalid, - m_axis_5_tdata - -); - -//stream checkers -initial begin - ptr0 = STRM0_OFFSET; - ptr1 = STRM1_OFFSET; - ptr2 = STRM2_OFFSET; - ptr3 = STRM3_OFFSET; - ptr4 = STRM4_OFFSET; - ptr5 = STRM5_OFFSET; - fork - //check stream 0 - begin - $display("Starting stream 0 checker"); - while(~done & (NSTREAMS > 0)) begin - @(negedge clk); - if(m_axis_0_tvalid & m_axis_0_tready) begin - if(m_axis_0_tdata != golden[ptr0]) begin - $display("Mismatch on stream 0"); - $stop(); - end - //increment pointer - ptr0 = ptr0 + 1; - //rewind pointer if it's reached end - if(ptr0 == (STRM0_OFFSET + STRM0_DEPTH)) - ptr0 = STRM0_OFFSET; - end - end - end - //check stream 1 - begin - $display("Starting stream 1 checker"); - while(~done & (NSTREAMS > 1)) begin - @(negedge clk); - if(m_axis_1_tvalid & m_axis_1_tready) begin - if(m_axis_1_tdata != golden[ptr1]) begin - $display("Mismatch on stream 1"); - $stop(); - end - //increment pointer - ptr1 = ptr1 + 1; - //rewind pointer if it's reached end - if(ptr1 == (STRM1_OFFSET + STRM1_DEPTH)) - ptr1 = STRM1_OFFSET; - end - end - end - //check stream 2 - begin - $display("Starting stream 2 checker"); - while(~done & (NSTREAMS > 2)) begin - @(negedge clk); - if(m_axis_2_tvalid & m_axis_2_tready) begin - if(m_axis_2_tdata != golden[ptr2]) begin - $display("Mismatch on stream 2"); - $stop(); - end - //increment pointer - ptr2 = ptr2 + 1; - //rewind pointer if it's reached end - if(ptr2 == (STRM2_OFFSET + STRM2_DEPTH)) - ptr2 = STRM2_OFFSET; - end - end - end - //check stream 3 - begin - $display("Starting stream 3 checker"); - while(~done & (NSTREAMS > 3)) begin - @(negedge clk); - if(m_axis_3_tvalid & m_axis_3_tready) begin - if(m_axis_3_tdata != golden[ptr3]) begin - $display("Mismatch on stream 3"); - $stop(); - end - //increment pointer - ptr3 = ptr3 + 1; - //rewind pointer if it's reached end - if(ptr3 == (STRM3_OFFSET + STRM3_DEPTH)) - ptr3 = STRM3_OFFSET; - end - end - end - //check stream 4 - begin - $display("Starting stream 4 checker"); - while(~done & (NSTREAMS > 4)) begin - @(negedge clk); - if(m_axis_4_tvalid & m_axis_4_tready) begin - if(m_axis_4_tdata != golden[ptr4]) begin - $display("Mismatch on stream 4"); - $stop(); - end - //increment pointer - ptr4 = ptr4 + 1; - //rewind pointer if it's reached end - if(ptr4 == (STRM4_OFFSET + STRM4_DEPTH)) - ptr4 = STRM4_OFFSET; - end - end - end - //check stream 5 - begin - $display("Starting stream 5 checker"); - while(~done & (NSTREAMS > 5)) begin - @(negedge clk); - if(m_axis_5_tvalid & m_axis_5_tready) begin - if(m_axis_5_tdata != golden[ptr5]) begin - $display("Mismatch on stream 5"); - $stop(); - end - //increment pointer - ptr5 = ptr5 + 1; - //rewind pointer if it's reached end - if(ptr5 == (STRM5_OFFSET + STRM5_DEPTH)) - ptr5 = STRM5_OFFSET; - end - end - end - join -end - -initial begin - done = 0; - @(negedge rst); - $dumpfile("wave.vcd"); - $dumpvars(0,tb_memstream_writes); - #50000 - $display("Test done!"); - done = 1; - #1000 - $finish(); -end - -endmodule diff --git a/finn-rtllib/memstream/sim/test.sh b/finn-rtllib/memstream/sim/test.sh deleted file mode 100755 index 7cb0497d261ac41a763bad8e58afabb204887d39..0000000000000000000000000000000000000000 --- a/finn-rtllib/memstream/sim/test.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -iverilog ../hdl/*.v tb_memstream_writes.v -o sim -./sim diff --git a/finn-rtllib/memstream/xgui/memstream_v1_0.tcl b/finn-rtllib/memstream/xgui/memstream_v1_0.tcl index 87565bc5613ce783d6a8067e8323d2358adb8061..e802d81c79d9483f3ce515cb69b96f424badd1d5 100644 --- a/finn-rtllib/memstream/xgui/memstream_v1_0.tcl +++ b/finn-rtllib/memstream/xgui/memstream_v1_0.tcl @@ -8,42 +8,21 @@ proc init_gui { IPINST } { #Adding Page set Page_0 [ipgui::add_page $IPINST -name "Page 0"] ipgui::add_param $IPINST -name "AXILITE_ADDR_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "CONFIG_EN" -parent ${Page_0} - ipgui::add_param $IPINST -name "MEM_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "MEM_INIT" -parent ${Page_0} - ipgui::add_param $IPINST -name "MEM_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "NSTREAMS" -parent ${Page_0} - ipgui::add_param $IPINST -name "RAM_STYLE" -parent ${Page_0} -widget comboBox - ipgui::add_param $IPINST -name "STRM0_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM0_OFFSET" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM0_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM1_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM1_OFFSET" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM1_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM2_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM2_OFFSET" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM2_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM3_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM3_OFFSET" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM3_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM4_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM4_OFFSET" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM4_WIDTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM5_DEPTH" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM5_OFFSET" -parent ${Page_0} - ipgui::add_param $IPINST -name "STRM5_WIDTH" -parent ${Page_0} - - + ipgui::add_param $IPINST -name "DEPTH" -parent ${Page_0} + ipgui::add_param $IPINST -name "INIT_FILE" -parent ${Page_0} + ipgui::add_param $IPINST -name "RAM_STYLE" -parent ${Page_0} + ipgui::add_param $IPINST -name "WIDTH" -parent ${Page_0} } -proc update_PARAM_VALUE.AXILITE_ADDR_WIDTH { PARAM_VALUE.AXILITE_ADDR_WIDTH PARAM_VALUE.MEM_DEPTH PARAM_VALUE.MEM_WIDTH } { +proc update_PARAM_VALUE.AXILITE_ADDR_WIDTH { PARAM_VALUE.AXILITE_ADDR_WIDTH PARAM_VALUE.DEPTH PARAM_VALUE.WIDTH } { # Procedure called to update AXILITE_ADDR_WIDTH when any of the dependent parameters in the arguments change + set AXILITE_ADDR_WIDTH ${PARAM_VALUE.AXILITE_ADDR_WIDTH} - set MEM_DEPTH ${PARAM_VALUE.MEM_DEPTH} - set MEM_WIDTH ${PARAM_VALUE.MEM_WIDTH} - set values(MEM_DEPTH) [get_property value $MEM_DEPTH] - set values(MEM_WIDTH) [get_property value $MEM_WIDTH] - set_property value [gen_USERPARAMETER_AXILITE_ADDR_WIDTH_VALUE $values(MEM_DEPTH) $values(MEM_WIDTH)] $AXILITE_ADDR_WIDTH + set DEPTH ${PARAM_VALUE.DEPTH} + set WIDTH ${PARAM_VALUE.WIDTH} + set values(DEPTH) [get_property value $DEPTH] + set values(WIDTH) [get_property value $WIDTH] + set_property value [gen_USERPARAMETER_AXILITE_ADDR_WIDTH_VALUE $values(DEPTH) $values(WIDTH)] $AXILITE_ADDR_WIDTH } proc validate_PARAM_VALUE.AXILITE_ADDR_WIDTH { PARAM_VALUE.AXILITE_ADDR_WIDTH } { @@ -51,48 +30,21 @@ proc validate_PARAM_VALUE.AXILITE_ADDR_WIDTH { PARAM_VALUE.AXILITE_ADDR_WIDTH } return true } -proc update_PARAM_VALUE.CONFIG_EN { PARAM_VALUE.CONFIG_EN } { - # Procedure called to update CONFIG_EN when any of the dependent parameters in the arguments change +proc update_PARAM_VALUE.DEPTH { PARAM_VALUE.DEPTH } { + # Procedure called to update DEPTH when any of the dependent parameters in the arguments change } -proc validate_PARAM_VALUE.CONFIG_EN { PARAM_VALUE.CONFIG_EN } { - # Procedure called to validate CONFIG_EN +proc validate_PARAM_VALUE.DEPTH { PARAM_VALUE.DEPTH } { + # Procedure called to validate DEPTH return true } -proc update_PARAM_VALUE.MEM_DEPTH { PARAM_VALUE.MEM_DEPTH } { - # Procedure called to update MEM_DEPTH when any of the dependent parameters in the arguments change +proc update_PARAM_VALUE.INIT_FILE { PARAM_VALUE.INIT_FILE } { + # Procedure called to update INIT_FILE when any of the dependent parameters in the arguments change } -proc validate_PARAM_VALUE.MEM_DEPTH { PARAM_VALUE.MEM_DEPTH } { - # Procedure called to validate MEM_DEPTH - return true -} - -proc update_PARAM_VALUE.MEM_INIT { PARAM_VALUE.MEM_INIT } { - # Procedure called to update MEM_INIT when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.MEM_INIT { PARAM_VALUE.MEM_INIT } { - # Procedure called to validate MEM_INIT - return true -} - -proc update_PARAM_VALUE.MEM_WIDTH { PARAM_VALUE.MEM_WIDTH } { - # Procedure called to update MEM_WIDTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.MEM_WIDTH { PARAM_VALUE.MEM_WIDTH } { - # Procedure called to validate MEM_WIDTH - return true -} - -proc update_PARAM_VALUE.NSTREAMS { PARAM_VALUE.NSTREAMS } { - # Procedure called to update NSTREAMS when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.NSTREAMS { PARAM_VALUE.NSTREAMS } { - # Procedure called to validate NSTREAMS +proc validate_PARAM_VALUE.INIT_FILE { PARAM_VALUE.INIT_FILE } { + # Procedure called to validate INIT_FILE return true } @@ -105,192 +57,29 @@ proc validate_PARAM_VALUE.RAM_STYLE { PARAM_VALUE.RAM_STYLE } { return true } -proc update_PARAM_VALUE.STRM0_DEPTH { PARAM_VALUE.STRM0_DEPTH } { - # Procedure called to update STRM0_DEPTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM0_DEPTH { PARAM_VALUE.STRM0_DEPTH } { - # Procedure called to validate STRM0_DEPTH - return true -} - -proc update_PARAM_VALUE.STRM0_OFFSET { PARAM_VALUE.STRM0_OFFSET } { - # Procedure called to update STRM0_OFFSET when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM0_OFFSET { PARAM_VALUE.STRM0_OFFSET } { - # Procedure called to validate STRM0_OFFSET - return true -} - -proc update_PARAM_VALUE.STRM0_WIDTH { PARAM_VALUE.STRM0_WIDTH } { - # Procedure called to update STRM0_WIDTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM0_WIDTH { PARAM_VALUE.STRM0_WIDTH } { - # Procedure called to validate STRM0_WIDTH - return true -} - -proc update_PARAM_VALUE.STRM1_DEPTH { PARAM_VALUE.STRM1_DEPTH } { - # Procedure called to update STRM1_DEPTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM1_DEPTH { PARAM_VALUE.STRM1_DEPTH } { - # Procedure called to validate STRM1_DEPTH - return true -} - -proc update_PARAM_VALUE.STRM1_OFFSET { PARAM_VALUE.STRM1_OFFSET } { - # Procedure called to update STRM1_OFFSET when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM1_OFFSET { PARAM_VALUE.STRM1_OFFSET } { - # Procedure called to validate STRM1_OFFSET - return true -} - -proc update_PARAM_VALUE.STRM1_WIDTH { PARAM_VALUE.STRM1_WIDTH } { - # Procedure called to update STRM1_WIDTH when any of the dependent parameters in the arguments change +proc update_PARAM_VALUE.WIDTH { PARAM_VALUE.WIDTH } { + # Procedure called to update WIDTH when any of the dependent parameters in the arguments change } -proc validate_PARAM_VALUE.STRM1_WIDTH { PARAM_VALUE.STRM1_WIDTH } { - # Procedure called to validate STRM1_WIDTH +proc validate_PARAM_VALUE.WIDTH { PARAM_VALUE.WIDTH } { + # Procedure called to validate WIDTH return true } -proc update_PARAM_VALUE.STRM2_DEPTH { PARAM_VALUE.STRM2_DEPTH } { - # Procedure called to update STRM2_DEPTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM2_DEPTH { PARAM_VALUE.STRM2_DEPTH } { - # Procedure called to validate STRM2_DEPTH - return true -} - -proc update_PARAM_VALUE.STRM2_OFFSET { PARAM_VALUE.STRM2_OFFSET } { - # Procedure called to update STRM2_OFFSET when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM2_OFFSET { PARAM_VALUE.STRM2_OFFSET } { - # Procedure called to validate STRM2_OFFSET - return true -} - -proc update_PARAM_VALUE.STRM2_WIDTH { PARAM_VALUE.STRM2_WIDTH } { - # Procedure called to update STRM2_WIDTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM2_WIDTH { PARAM_VALUE.STRM2_WIDTH } { - # Procedure called to validate STRM2_WIDTH - return true -} - -proc update_PARAM_VALUE.STRM3_DEPTH { PARAM_VALUE.STRM3_DEPTH } { - # Procedure called to update STRM3_DEPTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM3_DEPTH { PARAM_VALUE.STRM3_DEPTH } { - # Procedure called to validate STRM3_DEPTH - return true -} - -proc update_PARAM_VALUE.STRM3_OFFSET { PARAM_VALUE.STRM3_OFFSET } { - # Procedure called to update STRM3_OFFSET when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM3_OFFSET { PARAM_VALUE.STRM3_OFFSET } { - # Procedure called to validate STRM3_OFFSET - return true -} - -proc update_PARAM_VALUE.STRM3_WIDTH { PARAM_VALUE.STRM3_WIDTH } { - # Procedure called to update STRM3_WIDTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM3_WIDTH { PARAM_VALUE.STRM3_WIDTH } { - # Procedure called to validate STRM3_WIDTH - return true -} - -proc update_PARAM_VALUE.STRM4_DEPTH { PARAM_VALUE.STRM4_DEPTH } { - # Procedure called to update STRM4_DEPTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM4_DEPTH { PARAM_VALUE.STRM4_DEPTH } { - # Procedure called to validate STRM4_DEPTH - return true -} - -proc update_PARAM_VALUE.STRM4_OFFSET { PARAM_VALUE.STRM4_OFFSET } { - # Procedure called to update STRM4_OFFSET when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM4_OFFSET { PARAM_VALUE.STRM4_OFFSET } { - # Procedure called to validate STRM4_OFFSET - return true -} -proc update_PARAM_VALUE.STRM4_WIDTH { PARAM_VALUE.STRM4_WIDTH } { - # Procedure called to update STRM4_WIDTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM4_WIDTH { PARAM_VALUE.STRM4_WIDTH } { - # Procedure called to validate STRM4_WIDTH - return true -} - -proc update_PARAM_VALUE.STRM5_DEPTH { PARAM_VALUE.STRM5_DEPTH } { - # Procedure called to update STRM5_DEPTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM5_DEPTH { PARAM_VALUE.STRM5_DEPTH } { - # Procedure called to validate STRM5_DEPTH - return true -} - -proc update_PARAM_VALUE.STRM5_OFFSET { PARAM_VALUE.STRM5_OFFSET } { - # Procedure called to update STRM5_OFFSET when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM5_OFFSET { PARAM_VALUE.STRM5_OFFSET } { - # Procedure called to validate STRM5_OFFSET - return true -} - -proc update_PARAM_VALUE.STRM5_WIDTH { PARAM_VALUE.STRM5_WIDTH } { - # Procedure called to update STRM5_WIDTH when any of the dependent parameters in the arguments change -} - -proc validate_PARAM_VALUE.STRM5_WIDTH { PARAM_VALUE.STRM5_WIDTH } { - # Procedure called to validate STRM5_WIDTH - return true -} - - -proc update_MODELPARAM_VALUE.CONFIG_EN { MODELPARAM_VALUE.CONFIG_EN PARAM_VALUE.CONFIG_EN } { +proc update_MODELPARAM_VALUE.DEPTH { MODELPARAM_VALUE.DEPTH PARAM_VALUE.DEPTH } { # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.CONFIG_EN}] ${MODELPARAM_VALUE.CONFIG_EN} + set_property value [get_property value ${PARAM_VALUE.DEPTH}] ${MODELPARAM_VALUE.DEPTH} } -proc update_MODELPARAM_VALUE.NSTREAMS { MODELPARAM_VALUE.NSTREAMS PARAM_VALUE.NSTREAMS } { +proc update_MODELPARAM_VALUE.WIDTH { MODELPARAM_VALUE.WIDTH PARAM_VALUE.WIDTH } { # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.NSTREAMS}] ${MODELPARAM_VALUE.NSTREAMS} + set_property value [get_property value ${PARAM_VALUE.WIDTH}] ${MODELPARAM_VALUE.WIDTH} } -proc update_MODELPARAM_VALUE.MEM_DEPTH { MODELPARAM_VALUE.MEM_DEPTH PARAM_VALUE.MEM_DEPTH } { +proc update_MODELPARAM_VALUE.INIT_FILE { MODELPARAM_VALUE.INIT_FILE PARAM_VALUE.INIT_FILE } { # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.MEM_DEPTH}] ${MODELPARAM_VALUE.MEM_DEPTH} -} - -proc update_MODELPARAM_VALUE.MEM_WIDTH { MODELPARAM_VALUE.MEM_WIDTH PARAM_VALUE.MEM_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.MEM_WIDTH}] ${MODELPARAM_VALUE.MEM_WIDTH} -} - -proc update_MODELPARAM_VALUE.MEM_INIT { MODELPARAM_VALUE.MEM_INIT PARAM_VALUE.MEM_INIT } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.MEM_INIT}] ${MODELPARAM_VALUE.MEM_INIT} + set_property value [get_property value ${PARAM_VALUE.INIT_FILE}] ${MODELPARAM_VALUE.INIT_FILE} } proc update_MODELPARAM_VALUE.RAM_STYLE { MODELPARAM_VALUE.RAM_STYLE PARAM_VALUE.RAM_STYLE } { @@ -298,96 +87,6 @@ proc update_MODELPARAM_VALUE.RAM_STYLE { MODELPARAM_VALUE.RAM_STYLE PARAM_VALUE. set_property value [get_property value ${PARAM_VALUE.RAM_STYLE}] ${MODELPARAM_VALUE.RAM_STYLE} } -proc update_MODELPARAM_VALUE.STRM0_WIDTH { MODELPARAM_VALUE.STRM0_WIDTH PARAM_VALUE.STRM0_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM0_WIDTH}] ${MODELPARAM_VALUE.STRM0_WIDTH} -} - -proc update_MODELPARAM_VALUE.STRM1_WIDTH { MODELPARAM_VALUE.STRM1_WIDTH PARAM_VALUE.STRM1_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM1_WIDTH}] ${MODELPARAM_VALUE.STRM1_WIDTH} -} - -proc update_MODELPARAM_VALUE.STRM2_WIDTH { MODELPARAM_VALUE.STRM2_WIDTH PARAM_VALUE.STRM2_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM2_WIDTH}] ${MODELPARAM_VALUE.STRM2_WIDTH} -} - -proc update_MODELPARAM_VALUE.STRM3_WIDTH { MODELPARAM_VALUE.STRM3_WIDTH PARAM_VALUE.STRM3_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM3_WIDTH}] ${MODELPARAM_VALUE.STRM3_WIDTH} -} - -proc update_MODELPARAM_VALUE.STRM4_WIDTH { MODELPARAM_VALUE.STRM4_WIDTH PARAM_VALUE.STRM4_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM4_WIDTH}] ${MODELPARAM_VALUE.STRM4_WIDTH} -} - -proc update_MODELPARAM_VALUE.STRM5_WIDTH { MODELPARAM_VALUE.STRM5_WIDTH PARAM_VALUE.STRM5_WIDTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM5_WIDTH}] ${MODELPARAM_VALUE.STRM5_WIDTH} -} - -proc update_MODELPARAM_VALUE.STRM0_DEPTH { MODELPARAM_VALUE.STRM0_DEPTH PARAM_VALUE.STRM0_DEPTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM0_DEPTH}] ${MODELPARAM_VALUE.STRM0_DEPTH} -} - -proc update_MODELPARAM_VALUE.STRM1_DEPTH { MODELPARAM_VALUE.STRM1_DEPTH PARAM_VALUE.STRM1_DEPTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM1_DEPTH}] ${MODELPARAM_VALUE.STRM1_DEPTH} -} - -proc update_MODELPARAM_VALUE.STRM2_DEPTH { MODELPARAM_VALUE.STRM2_DEPTH PARAM_VALUE.STRM2_DEPTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM2_DEPTH}] ${MODELPARAM_VALUE.STRM2_DEPTH} -} - -proc update_MODELPARAM_VALUE.STRM3_DEPTH { MODELPARAM_VALUE.STRM3_DEPTH PARAM_VALUE.STRM3_DEPTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM3_DEPTH}] ${MODELPARAM_VALUE.STRM3_DEPTH} -} - -proc update_MODELPARAM_VALUE.STRM4_DEPTH { MODELPARAM_VALUE.STRM4_DEPTH PARAM_VALUE.STRM4_DEPTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM4_DEPTH}] ${MODELPARAM_VALUE.STRM4_DEPTH} -} - -proc update_MODELPARAM_VALUE.STRM5_DEPTH { MODELPARAM_VALUE.STRM5_DEPTH PARAM_VALUE.STRM5_DEPTH } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM5_DEPTH}] ${MODELPARAM_VALUE.STRM5_DEPTH} -} - -proc update_MODELPARAM_VALUE.STRM0_OFFSET { MODELPARAM_VALUE.STRM0_OFFSET PARAM_VALUE.STRM0_OFFSET } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM0_OFFSET}] ${MODELPARAM_VALUE.STRM0_OFFSET} -} - -proc update_MODELPARAM_VALUE.STRM1_OFFSET { MODELPARAM_VALUE.STRM1_OFFSET PARAM_VALUE.STRM1_OFFSET } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM1_OFFSET}] ${MODELPARAM_VALUE.STRM1_OFFSET} -} - -proc update_MODELPARAM_VALUE.STRM2_OFFSET { MODELPARAM_VALUE.STRM2_OFFSET PARAM_VALUE.STRM2_OFFSET } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM2_OFFSET}] ${MODELPARAM_VALUE.STRM2_OFFSET} -} - -proc update_MODELPARAM_VALUE.STRM3_OFFSET { MODELPARAM_VALUE.STRM3_OFFSET PARAM_VALUE.STRM3_OFFSET } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM3_OFFSET}] ${MODELPARAM_VALUE.STRM3_OFFSET} -} - -proc update_MODELPARAM_VALUE.STRM4_OFFSET { MODELPARAM_VALUE.STRM4_OFFSET PARAM_VALUE.STRM4_OFFSET } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM4_OFFSET}] ${MODELPARAM_VALUE.STRM4_OFFSET} -} - -proc update_MODELPARAM_VALUE.STRM5_OFFSET { MODELPARAM_VALUE.STRM5_OFFSET PARAM_VALUE.STRM5_OFFSET } { - # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value - set_property value [get_property value ${PARAM_VALUE.STRM5_OFFSET}] ${MODELPARAM_VALUE.STRM5_OFFSET} -} - proc update_MODELPARAM_VALUE.AXILITE_ADDR_WIDTH { MODELPARAM_VALUE.AXILITE_ADDR_WIDTH PARAM_VALUE.AXILITE_ADDR_WIDTH } { # Procedure called to set VHDL generic/Verilog parameter value(s) based on TCL parameter value set_property value [get_property value ${PARAM_VALUE.AXILITE_ADDR_WIDTH}] ${MODELPARAM_VALUE.AXILITE_ADDR_WIDTH} diff --git a/finn-rtllib/swg/swg_common.sv b/finn-rtllib/swg/swg_common.sv new file mode 100644 index 0000000000000000000000000000000000000000..f2cdc333cad0a546aa9cfb55c9ca1dd74a753dc2 --- /dev/null +++ b/finn-rtllib/swg/swg_common.sv @@ -0,0 +1,249 @@ +/****************************************************************************** + * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + + +// loop controller used for both, "default" and "parallel", implementation styles +module swg_controller +import swg::*; #( + int unsigned LOOP_H_ITERATIONS, + int unsigned LOOP_W_ITERATIONS, + int unsigned LOOP_KH_ITERATIONS, + int unsigned LOOP_KW_ITERATIONS, + int unsigned LOOP_SIMD_ITERATIONS, + + int unsigned INCR_BITWIDTH, + + bit IS_DEPTHWISE, + + int HEAD_INCR_SIMD, + int HEAD_INCR_KW, + int HEAD_INCR_KH, + int HEAD_INCR_W, + int HEAD_INCR_H, + int TAIL_INCR_W, + int TAIL_INCR_H, + int TAIL_INCR_LAST, + + state_e INNERMOST_STATE +)( + input logic clk, + input logic rst_n, + + input logic advance, + output logic [INCR_BITWIDTH-1:0] addr_incr, + output logic [INCR_BITWIDTH-1:0] tail_incr +); + + // state and counters + state_e State = INNERMOST_STATE; + state_e state_next; + + logic signed [$clog2(LOOP_H_ITERATIONS +2)+1-1:0] Counter_loop_h = LOOP_H_ITERATIONS; + logic signed [$clog2(LOOP_W_ITERATIONS +2)+1-1:0] Counter_loop_w = LOOP_W_ITERATIONS; + logic signed [$clog2(LOOP_KH_ITERATIONS +2)+1-1:0] Counter_loop_kh = LOOP_KH_ITERATIONS; + logic signed [$clog2(LOOP_KW_ITERATIONS +2)+1-1:0] Counter_loop_kw = LOOP_KW_ITERATIONS; + logic signed [$clog2(LOOP_SIMD_ITERATIONS+2)+1-1:0] Counter_loop_simd = LOOP_SIMD_ITERATIONS; + + // combinational logic for addr_incr generation + always_comb begin : blkHead + unique case (State) + STATE_START : addr_incr = 0; + STATE_LOOP_SIMD : addr_incr = HEAD_INCR_SIMD; + STATE_LOOP_KW : addr_incr = HEAD_INCR_KW; + STATE_LOOP_KH : addr_incr = HEAD_INCR_KH; + STATE_LOOP_W : addr_incr = HEAD_INCR_W; + STATE_LOOP_H : addr_incr = HEAD_INCR_H; + endcase + end + + // combinational logic for tail_incr generation + uwire tail_incr_inner_condition = IS_DEPTHWISE? (Counter_loop_kh >= 0) : 0; + assign tail_incr = + tail_incr_inner_condition? 1 : + Counter_loop_w >= 0? TAIL_INCR_W : + Counter_loop_h >= 0? TAIL_INCR_H : + /* else */ TAIL_INCR_LAST; + + // combinational next state logic + always_comb begin : blkState + state_next = State; + if(State != INNERMOST_STATE) state_next = INNERMOST_STATE; + else begin + if(Counter_loop_simd < 0) begin + state_next = + (Counter_loop_kw >= 0)? STATE_LOOP_KW : + (Counter_loop_kh >= 0)? STATE_LOOP_KH : + (Counter_loop_w >= 0)? STATE_LOOP_W : + (Counter_loop_h >= 0)? STATE_LOOP_H : + /* else */ STATE_START; + end + end + end : blkState + + // sequential logic + always_ff @ (posedge clk) begin + if(!rst_n) begin + State <= INNERMOST_STATE; + Counter_loop_h <= LOOP_H_ITERATIONS; + Counter_loop_w <= LOOP_W_ITERATIONS; + Counter_loop_kh <= LOOP_KH_ITERATIONS; + Counter_loop_kw <= LOOP_KW_ITERATIONS; + Counter_loop_simd <= LOOP_SIMD_ITERATIONS; + end + else if(advance) begin + State <= state_next; + if (State == INNERMOST_STATE) begin + if(Counter_loop_simd >= 0) Counter_loop_simd <= Counter_loop_simd-1; + else begin + Counter_loop_simd <= LOOP_SIMD_ITERATIONS; + if(Counter_loop_kw >= 0) Counter_loop_kw <= Counter_loop_kw-1; + else begin + Counter_loop_kw <= LOOP_KW_ITERATIONS; + if(Counter_loop_kh >= 0) Counter_loop_kh <= Counter_loop_kh-1; + else begin + Counter_loop_kh <= LOOP_KH_ITERATIONS; + if(Counter_loop_w >= 0) Counter_loop_w <= Counter_loop_w-1; + else begin + Counter_loop_w <= LOOP_W_ITERATIONS; + if(Counter_loop_h >= 0) Counter_loop_h <= Counter_loop_h-1; + else Counter_loop_h <= LOOP_H_ITERATIONS; + end + end + end + end + end + end + end + +endmodule : swg_controller + +// buffer used in "default" implementation style +module swg_cyclic_buffer_addressable #( + int unsigned WIDTH, + int unsigned DEPTH, + parameter RAM_STYLE = "auto" +)( + input logic clk, + + input logic write_enable, + input logic [$clog2(DEPTH)-1:0] write_addr, + input logic [WIDTH-1:0] data_in, + + input logic read_enable, + input logic [$clog2(DEPTH)-1:0] read_addr, // absolute (!) read address of cyclic buffer + output logic [WIDTH-1:0] data_out +); + + (*ram_style=RAM_STYLE*) logic [WIDTH-1:0] Ram[DEPTH]; + logic [WIDTH-1:0] Out = 'x; + always_ff @(posedge clk) begin + if (read_enable) Out <= Ram[read_addr]; + if (write_enable) Ram[write_addr] <= data_in; + end + assign data_out = Out; + +endmodule : swg_cyclic_buffer_addressable + +// buffer used in "parallel" implementation style +module swg_reg_buffer +#( + int unsigned WIDTH = 1, + int unsigned DEPTH = 1 +) +( + input logic clk, + input logic shift_enable, + input logic [WIDTH-1:0] shift_in, + output logic [WIDTH-1:0] shift_out, + output logic [WIDTH*DEPTH-1:0] data_out +); + +logic [WIDTH-1:0] Data [DEPTH-1:0]; + +assign shift_out = Data[DEPTH-1]; + +for (genvar e=0; e<DEPTH; e++) + assign data_out[e*WIDTH +: WIDTH] = Data[e]; + +always @ (posedge clk) begin + if (shift_enable) begin + for (int i=DEPTH-1; i>0; i--) + Data[i] <= Data[i-1]; + Data[0] <= shift_in; + end +end +endmodule : swg_reg_buffer + +// buffer used in "parallel" implementation style +module swg_ram_buffer +#( + int unsigned WIDTH, + int unsigned DEPTH, + parameter RAM_STYLE = "auto" +) +( + input logic clk, + input logic rst_n, + input logic shift_enable, + input logic [WIDTH-1:0] shift_in, + output logic [WIDTH-1:0] shift_out +); + +logic [WIDTH-1:0] Out_reg; +assign shift_out = Out_reg; + +logic [$clog2(DEPTH)-1:0] Addr_w = 0; +logic [$clog2(DEPTH)-1:0] Addr_r = 0; + +(*ram_style=RAM_STYLE*) logic [WIDTH-1:0] Ram [DEPTH-1:0]; + +always_ff @(posedge clk) begin + if (rst_n == 1'b0) begin + Addr_w <= 0; + Addr_r <= 1; + end else begin + if (shift_enable) begin + Ram[Addr_w] <= shift_in; + Out_reg <= Ram[Addr_r]; + + if (Addr_w == DEPTH-1) + Addr_w <= 0; + else + Addr_w <= Addr_w + 1; + + if (Addr_r == DEPTH-1) + Addr_r <= 0; + else + Addr_r <= Addr_r + 1; + end + end +end +endmodule : swg_ram_buffer diff --git a/finn-rtllib/swg/swg_pkg.sv b/finn-rtllib/swg/swg_pkg.sv new file mode 100644 index 0000000000000000000000000000000000000000..1200310acad2931568235a82fd0277b8dd50a424 --- /dev/null +++ b/finn-rtllib/swg/swg_pkg.sv @@ -0,0 +1,41 @@ +/****************************************************************************** + * Copyright (C) 2023, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +package swg; + typedef enum logic [2:0] { + STATE_START, + STATE_LOOP_SIMD, + STATE_LOOP_KW, + STATE_LOOP_KH, + STATE_LOOP_W, + STATE_LOOP_H + } state_e; +endpackage : swg diff --git a/finn-rtllib/swg/swg_template_axilite.v b/finn-rtllib/swg/swg_template_axilite.v index 9479c7f80d7d82b27141dbe5abcce442049237bd..1f39e4440e47f752816907dc454d15b849c8fa85 100644 --- a/finn-rtllib/swg/swg_template_axilite.v +++ b/finn-rtllib/swg/swg_template_axilite.v @@ -1,8 +1,35 @@ +/****************************************************************************** + * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ -`timescale 1 ns / 1 ps - -module $TOP_MODULE_NAME$_axilite # -( +module $TOP_MODULE_NAME$_axilite #( // Users to add parameters here // User parameters ends @@ -12,8 +39,7 @@ module $TOP_MODULE_NAME$_axilite # parameter integer C_S_AXI_DATA_WIDTH = 32, // Width of S_AXI address bus parameter integer C_S_AXI_ADDR_WIDTH = 6 -) -( +)( // Users to add ports here output wire [C_S_AXI_DATA_WIDTH-1:0] cfg_reg0, output wire [C_S_AXI_DATA_WIDTH-1:0] cfg_reg1, diff --git a/finn-rtllib/swg/swg_template_default.sv b/finn-rtllib/swg/swg_template_default.sv index 06e65e911100dd7d3d8879b014a6d59713eb9bbd..78a8d0a3b984b987df6ca62f3789fbbedcbc6d8b 100644 --- a/finn-rtllib/swg/swg_template_default.sv +++ b/finn-rtllib/swg/swg_template_default.sv @@ -28,141 +28,6 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -module $TOP_MODULE_NAME$_controller #( - int unsigned LOOP_H_ITERATIONS = $LOOP_H_ITERATIONS$, - int unsigned LOOP_W_ITERATIONS = $LOOP_W_ITERATIONS$, - int unsigned LOOP_KH_ITERATIONS = $LOOP_KH_ITERATIONS$, - int unsigned LOOP_KW_ITERATIONS = $LOOP_KW_ITERATIONS$, - int unsigned LOOP_SIMD_ITERATIONS = $LOOP_SIMD_ITERATIONS$, - - int unsigned INCR_BITWIDTH = $INCR_BITWIDTH$, - - bit IS_DEPTHWISE = $IS_DEPTHWISE$ -)( - input logic clk, - input logic rst_n, - - input logic advance, - output logic [INCR_BITWIDTH-1:0] addr_incr, - output logic [INCR_BITWIDTH-1:0] tail_incr -); - - // state and counters - typedef enum logic [2:0] { - STATE_START, - STATE_LOOP_SIMD, - STATE_LOOP_KW, - STATE_LOOP_KH, - STATE_LOOP_W, - STATE_LOOP_H - } state_e; - state_e State = $INNERMOST_STATE$; - state_e state_next; - - logic signed [$clog2(LOOP_H_ITERATIONS +2)+1-1:0] Counter_loop_h = LOOP_H_ITERATIONS; - logic signed [$clog2(LOOP_W_ITERATIONS +2)+1-1:0] Counter_loop_w = LOOP_W_ITERATIONS; - logic signed [$clog2(LOOP_KH_ITERATIONS +2)+1-1:0] Counter_loop_kh = LOOP_KH_ITERATIONS; - logic signed [$clog2(LOOP_KW_ITERATIONS +2)+1-1:0] Counter_loop_kw = LOOP_KW_ITERATIONS; - logic signed [$clog2(LOOP_SIMD_ITERATIONS+2)+1-1:0] Counter_loop_simd = LOOP_SIMD_ITERATIONS; - - // combinational logic for addr_incr generation - always_comb begin : blkHead - unique case (State) - 0 : addr_incr = 0; - 1 : addr_incr = $HEAD_INCR_SIMD$; - 2 : addr_incr = $HEAD_INCR_KW$; - 3 : addr_incr = $HEAD_INCR_KH$; - 4 : addr_incr = $HEAD_INCR_W$; - 5 : addr_incr = $HEAD_INCR_H$; - endcase - end - - // combinational logic for tail_incr generation - uwire tail_incr_inner_condition = IS_DEPTHWISE? (Counter_loop_kh >= 0) : 0; - assign tail_incr = - tail_incr_inner_condition? 1 : - Counter_loop_w >= 0? $TAIL_INCR_W$ : - Counter_loop_h >= 0? $TAIL_INCR_H$ : - /* else */ $TAIL_INCR_LAST$; - - // combinational next state logic - always_comb begin : blkState - state_next = State; - if(State != $INNERMOST_STATE$) state_next = $INNERMOST_STATE$; - else begin - if(Counter_loop_simd < 0) begin - state_next = - (Counter_loop_kw >= 0)? STATE_LOOP_KW : - (Counter_loop_kh >= 0)? STATE_LOOP_KH : - (Counter_loop_w >= 0)? STATE_LOOP_W : - (Counter_loop_h >= 0)? STATE_LOOP_H : - /* else */ STATE_START; - end - end - end : blkState - - // sequential logic - always_ff @ (posedge clk) begin - if(!rst_n) begin - State <= $INNERMOST_STATE$; - Counter_loop_h <= LOOP_H_ITERATIONS; - Counter_loop_w <= LOOP_W_ITERATIONS; - Counter_loop_kh <= LOOP_KH_ITERATIONS; - Counter_loop_kw <= LOOP_KW_ITERATIONS; - Counter_loop_simd <= LOOP_SIMD_ITERATIONS; - end - else if(advance) begin - State <= state_next; - if (State == $INNERMOST_STATE$) begin - if(Counter_loop_simd >= 0) Counter_loop_simd <= Counter_loop_simd-1; - else begin - Counter_loop_simd <= LOOP_SIMD_ITERATIONS; - if(Counter_loop_kw >= 0) Counter_loop_kw <= Counter_loop_kw-1; - else begin - Counter_loop_kw <= LOOP_KW_ITERATIONS; - if(Counter_loop_kh >= 0) Counter_loop_kh <= Counter_loop_kh-1; - else begin - Counter_loop_kh <= LOOP_KH_ITERATIONS; - if(Counter_loop_w >= 0) Counter_loop_w <= Counter_loop_w-1; - else begin - Counter_loop_w <= LOOP_W_ITERATIONS; - if(Counter_loop_h >= 0) Counter_loop_h <= Counter_loop_h-1; - else Counter_loop_h <= LOOP_H_ITERATIONS; - end - end - end - end - end - end - end - -endmodule : $TOP_MODULE_NAME$_controller - -module $TOP_MODULE_NAME$_cyclic_buffer_addressable #( - int unsigned WIDTH, - int unsigned DEPTH -)( - input logic clk, - - input logic write_enable, - input logic [$clog2(DEPTH)-1:0] write_addr, - input logic [WIDTH-1:0] data_in, - - input logic read_enable, - input logic [$clog2(DEPTH)-1:0] read_addr, // absolute (!) read address of cyclic buffer - output logic [WIDTH-1:0] data_out -); - - $RAM_STYLE$ logic [WIDTH-1:0] Ram[DEPTH]; - logic [WIDTH-1:0] Out = 'x; - always_ff @(posedge clk) begin - if (read_enable) Out <= Ram[read_addr]; - if (write_enable) Ram[write_addr] <= data_in; - end - assign data_out = Out; - -endmodule : $TOP_MODULE_NAME$_cyclic_buffer_addressable - module $TOP_MODULE_NAME$_impl #( int BIT_WIDTH, int SIMD, @@ -197,9 +62,10 @@ module $TOP_MODULE_NAME$_impl #( uwire window_buffer_read_enable; uwire [$clog2(BUF_ELEM_TOTAL)-1:0] window_buffer_write_addr; uwire [$clog2(BUF_ELEM_TOTAL)-1:0] window_buffer_read_addr; - $TOP_MODULE_NAME$_cyclic_buffer_addressable #( + swg_cyclic_buffer_addressable #( .WIDTH(BUF_IN_WIDTH), - .DEPTH(BUF_ELEM_TOTAL) + .DEPTH(BUF_ELEM_TOTAL), + .RAM_STYLE($RAM_STYLE$) ) window_buffer_inst ( .clk(ap_clk), @@ -216,7 +82,25 @@ module $TOP_MODULE_NAME$_impl #( uwire advance_controller; uwire signed [INCR_BITWIDTH-1:0] addr_incr; uwire [INCR_BITWIDTH-1:0] tail_incr; - $TOP_MODULE_NAME$_controller controller_inst ( + swg_controller #( + .LOOP_H_ITERATIONS($LOOP_H_ITERATIONS$), + .LOOP_W_ITERATIONS($LOOP_W_ITERATIONS$), + .LOOP_KH_ITERATIONS($LOOP_KH_ITERATIONS$), + .LOOP_KW_ITERATIONS($LOOP_KW_ITERATIONS$), + .LOOP_SIMD_ITERATIONS($LOOP_SIMD_ITERATIONS$), + .HEAD_INCR_SIMD($HEAD_INCR_SIMD$), + .HEAD_INCR_KW($HEAD_INCR_KW$), + .HEAD_INCR_KH($HEAD_INCR_KH$), + .HEAD_INCR_W($HEAD_INCR_W$), + .HEAD_INCR_H($HEAD_INCR_H$), + .TAIL_INCR_W($TAIL_INCR_W$), + .TAIL_INCR_H($TAIL_INCR_H$), + .TAIL_INCR_LAST($TAIL_INCR_LAST$), + .INCR_BITWIDTH($INCR_BITWIDTH$), + .IS_DEPTHWISE($IS_DEPTHWISE$), + .INNERMOST_STATE(swg::$INNERMOST_STATE$) + ) + controller_inst ( .clk(ap_clk), .rst_n(ap_rst_n), .advance(advance_controller), diff --git a/finn-rtllib/swg/swg_template_default_dynamic.sv b/finn-rtllib/swg/swg_template_default_dynamic.sv index eb53978b580a4753bbea6c8478f35912deb812b4..5a6fdda170242b804353cac186c1f920a4a71d7a 100644 --- a/finn-rtllib/swg/swg_template_default_dynamic.sv +++ b/finn-rtllib/swg/swg_template_default_dynamic.sv @@ -1,3 +1,34 @@ +/****************************************************************************** + * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + module $TOP_MODULE_NAME$_controller #( int unsigned CNTR_BITWIDTH, int unsigned INCR_BITWIDTH, @@ -27,6 +58,8 @@ module $TOP_MODULE_NAME$_controller #( input logic [INCR_BITWIDTH-1:0] cfg_incr_tail_last ); + import swg::*; + // (dynamic) configuration registers logic [CNTR_BITWIDTH-1:0] Cfg_cntr_simd = $LOOP_SIMD_ITERATIONS$; logic [CNTR_BITWIDTH-1:0] Cfg_cntr_kw = $LOOP_KW_ITERATIONS$; @@ -62,14 +95,6 @@ module $TOP_MODULE_NAME$_controller #( end // state and counters - typedef enum logic [2:0] { - STATE_START, - STATE_LOOP_SIMD, - STATE_LOOP_KW, - STATE_LOOP_KH, - STATE_LOOP_W, - STATE_LOOP_H - } state_e; state_e State = $INNERMOST_STATE$; state_e state_next; @@ -152,31 +177,6 @@ module $TOP_MODULE_NAME$_controller #( endmodule : $TOP_MODULE_NAME$_controller -module $TOP_MODULE_NAME$_cyclic_buffer_addressable #( - int unsigned WIDTH, - int unsigned DEPTH -)( - input logic clk, - - input logic write_enable, - input logic [$clog2(DEPTH)-1:0] write_addr, - input logic [WIDTH-1:0] data_in, - - input logic read_enable, - input logic [$clog2(DEPTH)-1:0] read_addr, // absolute (!) read address of cyclic buffer - output logic [WIDTH-1:0] data_out -); - - $RAM_STYLE$ logic [WIDTH-1:0] Ram[DEPTH]; - logic [WIDTH-1:0] Out = 'x; - always_ff @(posedge clk) begin - if (read_enable) Out <= Ram[read_addr]; - if (write_enable) Ram[write_addr] <= data_in; - end - assign data_out = Out; - -endmodule : $TOP_MODULE_NAME$_cyclic_buffer_addressable - module $TOP_MODULE_NAME$_impl #( int BIT_WIDTH, int SIMD, @@ -242,9 +242,10 @@ module $TOP_MODULE_NAME$_impl #( uwire window_buffer_read_enable; uwire [$clog2(BUF_ELEM_TOTAL)-1:0] window_buffer_write_addr; uwire [$clog2(BUF_ELEM_TOTAL)-1:0] window_buffer_read_addr; - $TOP_MODULE_NAME$_cyclic_buffer_addressable #( + swg_cyclic_buffer_addressable #( .WIDTH(BUF_IN_WIDTH), - .DEPTH(BUF_ELEM_TOTAL) + .DEPTH(BUF_ELEM_TOTAL), + .RAM_STYLE($RAM_STYLE$) ) window_buffer_inst ( .clk(ap_clk), diff --git a/finn-rtllib/swg/swg_template_parallel.sv b/finn-rtllib/swg/swg_template_parallel.sv new file mode 100644 index 0000000000000000000000000000000000000000..83a525ff36b883122cd31a59675557a23085a872 --- /dev/null +++ b/finn-rtllib/swg/swg_template_parallel.sv @@ -0,0 +1,228 @@ +/****************************************************************************** + * Copyright (C) 2022, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +module $TOP_MODULE_NAME$_wb +#( + int unsigned IN_WIDTH = 1, // bit-width*C*MMV_in + int unsigned OUT_ELEM_WIDTH = 1, // bit-width*C + int unsigned OUT_WIDTH = 1, // bit-width*C*MMV_out + int unsigned BUFFER_ELEM_TOTAL = 1 +) +( + input logic clk, + input logic rst_n, + input logic shift_enable, + input logic [IN_WIDTH-1:0] data_in, + output logic [OUT_WIDTH-1:0] data_out +); + +$GENERATE_REG_FIFOS$ + +$GENERATE_BRAM_FIFOS$ + +// fixed interconnect between linear buffers +$GENERATE_BUFFER_CONNECTION$ + +// fixed REG FIFO -> output mapping +$GENERATE_OUTPUT_MAPPING$ + +endmodule : $TOP_MODULE_NAME$_wb + +module $TOP_MODULE_NAME$_impl #( + int unsigned BIT_WIDTH, + int unsigned SIMD, + int unsigned MMV_IN, + int unsigned MMV_OUT, + int unsigned LAST_READ_ELEM = $LAST_READ_ELEM$, + int unsigned FIRST_WRITE_ELEM = $FIRST_WRITE_ELEM$, + int unsigned LAST_WRITE_ELEM = $LAST_WRITE_ELEM$, + int unsigned BUF_ELEM_TOTAL = $BUF_ELEM_TOTAL$, + int unsigned INCR_BITWIDTH = $INCR_BITWIDTH$ +)( + input logic ap_clk, + input logic ap_rst_n, + + input logic in0_V_V_TVALID, + output logic in0_V_V_TREADY, + input logic [BIT_WIDTH * SIMD * MMV_IN-1:0] in0_V_V_TDATA, + + output logic out_V_V_TVALID, + input logic out_V_V_TREADY, + output logic [BIT_WIDTH * SIMD * MMV_OUT-1:0] out_V_V_TDATA +); + // derived constants + localparam int unsigned BUF_IN_WIDTH = BIT_WIDTH * SIMD * MMV_IN; + localparam int unsigned BUF_OUT_ELEM_WIDTH = BIT_WIDTH * SIMD; + localparam int unsigned BUF_OUT_WIDTH = BIT_WIDTH * SIMD * MMV_OUT; + + // main buffer instantiation + uwire [BUF_IN_WIDTH -1:0] window_buffer_in; + uwire [BUF_OUT_WIDTH-1:0] window_buffer_out; + uwire window_buffer_shift_enable; + $TOP_MODULE_NAME$_wb + #( + .IN_WIDTH(BUF_IN_WIDTH), + .OUT_ELEM_WIDTH(BUF_OUT_ELEM_WIDTH), + .OUT_WIDTH(BUF_OUT_WIDTH), + .BUFFER_ELEM_TOTAL(BUF_ELEM_TOTAL) + ) + window_buffer_inst + ( + .clk(ap_clk), + .rst_n(ap_rst_n), + .data_in(window_buffer_in), + .shift_enable(window_buffer_shift_enable), + .data_out(window_buffer_out) + ); + + // controller instantiation + uwire advance_controller; + uwire signed [INCR_BITWIDTH-1:0] addr_incr; + uwire [INCR_BITWIDTH-1:0] tail_incr; + swg_controller #( + .LOOP_H_ITERATIONS($LOOP_H_ITERATIONS$), + .LOOP_W_ITERATIONS($LOOP_W_ITERATIONS$), + .LOOP_KH_ITERATIONS($LOOP_KH_ITERATIONS$), + .LOOP_KW_ITERATIONS($LOOP_KW_ITERATIONS$), + .LOOP_SIMD_ITERATIONS($LOOP_SIMD_ITERATIONS$), + .HEAD_INCR_SIMD($HEAD_INCR_SIMD$), + .HEAD_INCR_KW($HEAD_INCR_KW$), + .HEAD_INCR_KH($HEAD_INCR_KH$), + .HEAD_INCR_W($HEAD_INCR_W$), + .HEAD_INCR_H($HEAD_INCR_H$), + .TAIL_INCR_W($TAIL_INCR_W$), + .TAIL_INCR_H($TAIL_INCR_H$), + .TAIL_INCR_LAST($TAIL_INCR_LAST$), + .INCR_BITWIDTH($INCR_BITWIDTH$), + .IS_DEPTHWISE($IS_DEPTHWISE$), + .INNERMOST_STATE(swg::$INNERMOST_STATE$) + ) + controller_inst ( + .clk(ap_clk), + .rst_n(ap_rst_n), + .advance(advance_controller), + .addr_incr(addr_incr), + .tail_incr(tail_incr) + ); + + // counters/address registers + logic signed [$clog2(LAST_READ_ELEM+1)+1-1:0] Newest_buffered_elem = -1; + logic [$clog2(LAST_READ_ELEM+1)+1-1:0] Current_elem = FIRST_WRITE_ELEM; + logic [$clog2(LAST_READ_ELEM+1)+1-1:0] First_elem_next_window = 0; + + // control registers/signals + logic Writing_done = 0; + logic Write_done = 0; + uwire write_cmd = !($signed(Current_elem) > Newest_buffered_elem) && !Writing_done;; + uwire write_ok = write_cmd && (out_V_V_TREADY || Write_done); + uwire write_blocked = write_cmd && !out_V_V_TREADY && !Write_done; + + uwire reading_done = Newest_buffered_elem == LAST_READ_ELEM; + uwire read_cmd = + !reading_done && ( // if there is still an input element left to read + Writing_done || ( // if writing is done (e.g. for skipped rows at FM end due to stride) + $signed(((Newest_buffered_elem - ($signed(BUF_ELEM_TOTAL) - 1)))) < $signed(First_elem_next_window) && + $signed(((Newest_buffered_elem - ($signed(BUF_ELEM_TOTAL) - 1)))) < $signed(Current_elem) + ) // (over-)write to buffer if oldest buffered element will no longer be needed + ); + uwire read_ok = read_cmd && in0_V_V_TVALID && !write_blocked; + + // includes waiting on W if W-only cycle: wait only on W no R/W to wait for + uwire advance = read_ok || (!read_cmd && write_ok) || (!read_cmd && !write_cmd); + + // assign buffer control + assign window_buffer_shift_enable = advance; + assign advance_controller = write_ok; + + // assign I/O ports + assign window_buffer_in = in0_V_V_TDATA; + assign out_V_V_TDATA = window_buffer_out; + assign in0_V_V_TREADY = ap_rst_n && read_ok; //only asserted if data is available and we can store it (allowed) + assign out_V_V_TVALID = ap_rst_n && write_cmd && !Write_done; //only asserted if we have data available and it has not been read yet (don't wait for READY from sink) + + // write done logic + always_ff @(posedge ap_clk) begin + if(!ap_rst_n) begin + Write_done <= 1'b0; + end + else begin + if (advance) begin + Write_done <= 1'b0; //reset flag + end else if (write_ok) //successful W in this cycle, but R still outstanding + Write_done <= 1'b1; //write can happen even if read is blocked, but only for the current cycle! + end + end + + // main process for advancing counters + always_ff @(posedge ap_clk) begin + if(!ap_rst_n) begin + Newest_buffered_elem <= -1; + Current_elem <= FIRST_WRITE_ELEM; + First_elem_next_window <= 0; + Writing_done <= 0; + end + else begin + if (read_ok) begin + Newest_buffered_elem <= Newest_buffered_elem+1; + + // check if this is the last read cycle (reading_done will be true afterwards) + if ((Newest_buffered_elem == LAST_READ_ELEM-1) && Writing_done) begin + // start processing of next FM if writing is done already (possible due to unused input elements at the tail end) + // todo: allow for read overlapping between feature maps (i.e., reading first elements from next FM while still writing last window of current FM) + Newest_buffered_elem <= -1; + Current_elem <= FIRST_WRITE_ELEM; + First_elem_next_window <= 0; + Writing_done <= 0; + end + end + + if (write_ok) begin + First_elem_next_window <= First_elem_next_window + tail_incr; + + // check if this is the last write cycle (Writing_done will be true afterwards) + if (Current_elem == LAST_WRITE_ELEM) begin + Writing_done <= 1; + + if (reading_done || (read_ok && (Newest_buffered_elem == LAST_READ_ELEM - 1))) begin + // start processing of next FM if reading is done already, or completes in the same cycle + Newest_buffered_elem <= -1; + Current_elem <= FIRST_WRITE_ELEM; + First_elem_next_window <= 0; + Writing_done <= 0; + end + end + else + Current_elem <= $signed(Current_elem) + addr_incr; + end + end + end + +endmodule : $TOP_MODULE_NAME$_impl diff --git a/finn-rtllib/swg/swg_template_wrapper.v b/finn-rtllib/swg/swg_template_wrapper.v index 0cc3579a255fddaf1a470d440b9e8ac245abe486..11fa0a88cbd80f466148eef0102466690af5b9c8 100644 --- a/finn-rtllib/swg/swg_template_wrapper.v +++ b/finn-rtllib/swg/swg_template_wrapper.v @@ -28,19 +28,19 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -`timescale 1 ns / 1 ps module $TOP_MODULE_NAME$ ( -(* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF in0_V:out_V" *) -input ap_clk, -(* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF in0_V:out_V" *) -input ap_rst_n, -input [BUF_IN_WIDTH-1:0] in0_V_TDATA, -input in0_V_TVALID, -output in0_V_TREADY, -output [BUF_OUT_WIDTH-1:0] out_V_TDATA, -output out_V_TVALID, -input out_V_TREADY + (* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF in0_V:out_V, ASSOCIATED_RESET ap_rst_n" *) + (* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 ap_clk CLK" *) + input ap_clk, + (* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) + input ap_rst_n, + input [BUF_IN_WIDTH-1:0] in0_V_TDATA, + input in0_V_TVALID, + output in0_V_TREADY, + output [BUF_OUT_WIDTH-1:0] out_V_TDATA, + output out_V_TVALID, + input out_V_TREADY ); // top-level parameters (set via code-generation) @@ -53,23 +53,20 @@ parameter MMV_OUT = $MMV_OUT$; parameter BUF_IN_WIDTH = BIT_WIDTH * SIMD * MMV_IN; parameter BUF_OUT_WIDTH = BIT_WIDTH * SIMD * MMV_OUT; -$TOP_MODULE_NAME$_impl -#( - .BIT_WIDTH(BIT_WIDTH), - .SIMD(SIMD), - .MMV_IN(MMV_IN), - .MMV_OUT(MMV_OUT) -) -impl -( - .ap_clk(ap_clk), - .ap_rst_n(ap_rst_n), - .in0_V_V_TDATA(in0_V_TDATA), - .in0_V_V_TVALID(in0_V_TVALID), - .in0_V_V_TREADY(in0_V_TREADY), - .out_V_V_TDATA(out_V_TDATA), - .out_V_V_TVALID(out_V_TVALID), - .out_V_V_TREADY(out_V_TREADY) +$TOP_MODULE_NAME$_impl #( + .BIT_WIDTH(BIT_WIDTH), + .SIMD(SIMD), + .MMV_IN(MMV_IN), + .MMV_OUT(MMV_OUT) +) impl ( + .ap_clk(ap_clk), + .ap_rst_n(ap_rst_n), + .in0_V_V_TDATA(in0_V_TDATA), + .in0_V_V_TVALID(in0_V_TVALID), + .in0_V_V_TREADY(in0_V_TREADY), + .out_V_V_TDATA(out_V_TDATA), + .out_V_V_TVALID(out_V_TVALID), + .out_V_V_TREADY(out_V_TREADY) ); -endmodule //TOP_MODULE_NAME +endmodule : $TOP_MODULE_NAME$ diff --git a/finn-rtllib/swg/swg_template_wrapper_dynamic.v b/finn-rtllib/swg/swg_template_wrapper_dynamic.v index ca870ace11edcf097645bc12b0486ffbb83b0ea4..5c09e7c1b4b80aa6f7b9e632750f24cee52898b4 100644 --- a/finn-rtllib/swg/swg_template_wrapper_dynamic.v +++ b/finn-rtllib/swg/swg_template_wrapper_dynamic.v @@ -1,4 +1,33 @@ -`timescale 1 ns / 1 ps +/****************************************************************************** + * Copyright (C) 2022, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION). HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ module $TOP_MODULE_NAME$ #( // top-level parameters (set via code-generation) @@ -18,9 +47,10 @@ module $TOP_MODULE_NAME$ #( parameter integer C_s_axilite_ADDR_WIDTH = 6 ) ( - (* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF in0_V:out_V:s_axilite" *) + (* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF in0_V:out_V:s_axilite, ASSOCIATED_RESET ap_rst_n" *) + (* X_INTERFACE_INFO = "xilinx.com:signal:clock:1.0 ap_clk CLK" *) input ap_clk, - (* X_INTERFACE_PARAMETER = "ASSOCIATED_BUSIF in0_V:out_V:s_axilite" *) + (* X_INTERFACE_PARAMETER = "POLARITY ACTIVE_LOW" *) input ap_rst_n, input [BUF_IN_WIDTH-1:0] in0_V_TDATA, input in0_V_TVALID, @@ -113,17 +143,14 @@ $TOP_MODULE_NAME$_axilite # ( .cfg_reg15(cfg_last_write) ); -$TOP_MODULE_NAME$_impl -#( +$TOP_MODULE_NAME$_impl #( .BIT_WIDTH(BIT_WIDTH), .SIMD(SIMD), .MMV_IN(MMV_IN), .MMV_OUT(MMV_OUT), .CNTR_BITWIDTH(CNTR_BITWIDTH), .INCR_BITWIDTH(INCR_BITWIDTH) -) -impl -( +) impl ( .ap_clk(ap_clk), .ap_rst_n(ap_rst_n), .in0_V_V_TDATA(in0_V_TDATA), @@ -151,4 +178,4 @@ impl .cfg_last_write(cfg_last_write) ); -endmodule //TOP_MODULE_NAME +endmodule : $TOP_MODULE_NAME$ diff --git a/notebooks/advanced/0_custom_analysis_pass.ipynb b/notebooks/advanced/0_custom_analysis_pass.ipynb index f8444520c3ded795702420d7f86335d0048ef043..f915b11fa052e0f2c3b4c056a5147bec61c87fd8 100644 --- a/notebooks/advanced/0_custom_analysis_pass.ipynb +++ b/notebooks/advanced/0_custom_analysis_pass.ipynb @@ -52,7 +52,9 @@ "metadata": {}, "outputs": [], "source": [ - "showInNetron(\"../LFCW1A1.onnx\")" + "import os\n", + "notebook_dir = os.environ['FINN_ROOT'] + \"/notebooks\"\n", + "showInNetron(notebook_dir + \"/LFCW1A1.onnx\")" ] }, { @@ -69,7 +71,7 @@ "outputs": [], "source": [ "from qonnx.core.modelwrapper import ModelWrapper\n", - "model = ModelWrapper('../LFCW1A1.onnx')" + "model = ModelWrapper(notebook_dir + \"/LFCW1A1.onnx\")" ] }, { diff --git a/notebooks/advanced/1_custom_transformation_pass.ipynb b/notebooks/advanced/1_custom_transformation_pass.ipynb index 391e852a71e1109b376abd7bb5d5f9d264d06498..7e4989c902a65167c8ed7ed4a591fcb0dbad7011 100644 --- a/notebooks/advanced/1_custom_transformation_pass.ipynb +++ b/notebooks/advanced/1_custom_transformation_pass.ipynb @@ -110,8 +110,11 @@ "metadata": {}, "outputs": [], "source": [ + "import os\n", + "notebook_dir = os.environ['FINN_ROOT'] + \"/notebooks\"\n", + "\n", "import onnx\n", - "onnx_model = onnx.load('../LFCW1A1.onnx')\n", + "onnx_model = onnx.load(notebook_dir + \"/LFCW1A1.onnx\")\n", "from qonnx.core.modelwrapper import ModelWrapper\n", "onnx_model = ModelWrapper(onnx_model)" ] @@ -122,7 +125,7 @@ "metadata": {}, "outputs": [], "source": [ - "showInNetron('../LFCW1A1.onnx')" + "showInNetron(notebook_dir + \"/LFCW1A1.onnx\")" ] }, { diff --git a/notebooks/basics/1_brevitas_network_import.ipynb b/notebooks/basics/1a_brevitas_network_import_via_FINN-ONNX.ipynb similarity index 93% rename from notebooks/basics/1_brevitas_network_import.ipynb rename to notebooks/basics/1a_brevitas_network_import_via_FINN-ONNX.ipynb index a884e90d7572789fc64cf9b953b5730590d4e8f1..756faf149d125b7c779b89a413953b6205c21e3e 100644 --- a/notebooks/basics/1_brevitas_network_import.ipynb +++ b/notebooks/basics/1a_brevitas_network_import_via_FINN-ONNX.ipynb @@ -4,7 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Importing Brevitas networks into FINN\n", + "# Importing Brevitas networks into FINN with the FINN-ONNX interchange format\n", + "\n", + "**Note: This notebook is very similar to the 1b notebook, in that it shows the same concepts for the FINN-ONNX ingestion as 1b does for QONNX. Section 1 is identical in both notebooks.**\n", "\n", "In this notebook we'll go through an example of how to import a Brevitas-trained QNN into FINN. The steps will be as follows:\n", "\n", @@ -137,10 +139,10 @@ "metadata": {}, "outputs": [], "source": [ - "import brevitas.onnx as bo\n", - "export_onnx_path = \"/tmp/LFCW1A1.onnx\"\n", + "from brevitas.export import export_finn_onnx\n", + "export_onnx_path = \"/tmp/LFCW1A1_finn-onnx.onnx\"\n", "input_shape = (1, 1, 28, 28)\n", - "bo.export_finn_onnx(lfc, input_shape, export_onnx_path)" + "export_finn_onnx(lfc, torch.randn(input_shape), export_onnx_path);" ] }, { @@ -156,7 +158,7 @@ "metadata": {}, "outputs": [], "source": [ - "showInNetron('/tmp/LFCW1A1.onnx')" + "showInNetron(export_onnx_path)" ] }, { @@ -244,7 +246,7 @@ "from qonnx.transformation.infer_shapes import InferShapes\n", "model = model.transform(InferShapes())\n", "model = model.transform(FoldConstants())\n", - "export_onnx_path_transformed = \"/tmp/LFCW1A1-clean.onnx\"\n", + "export_onnx_path_transformed = \"/tmp/LFCW1A1-finn-onnx-clean.onnx\"\n", "model.save(export_onnx_path_transformed)" ] }, @@ -254,7 +256,7 @@ "metadata": {}, "outputs": [], "source": [ - "showInNetron('/tmp/LFCW1A1-clean.onnx')" + "showInNetron(export_onnx_path_transformed)" ] }, { diff --git a/notebooks/basics/1b_brevitas_network_import_via_QONNX.ipynb b/notebooks/basics/1b_brevitas_network_import_via_QONNX.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..58fa3fc7e185919b896f121f1a55c5e88ec26000 --- /dev/null +++ b/notebooks/basics/1b_brevitas_network_import_via_QONNX.ipynb @@ -0,0 +1,326 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Importing Brevitas networks into FINN with the QONNX interchange format\n", + "\n", + "**Note: This notebook is very similar to the 1a notebook, in that it shows the same concepts for the QONNX ingestion as 1a does for FINN-ONNX. Section 1 is identical in both notebooks.**\n", + "\n", + "In this notebook we'll go through an example of how to import a Brevitas-trained QNN into FINN. The steps will be as follows:\n", + "\n", + "1. Load up the trained PyTorch model\n", + "2. Call Brevitas QONNX export and visualize with Netron\n", + "3. Import into FINN and converting QONNX to FINN-ONNX\n", + "\n", + "We'll use the following utility functions to print the source code for function calls (`showSrc()`) and to visualize a network using netron (`showInNetron()`) in the Jupyter notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import onnx\n", + "from finn.util.visualization import showSrc, showInNetron" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Load up the trained PyTorch model\n", + "\n", + "The FINN Docker image comes with several [example Brevitas networks](https://github.com/Xilinx/brevitas/tree/master/src/brevitas_examples/bnn_pynq), and we'll use the LFC-w1a1 model as the example network here. This is a binarized fully connected network trained on the MNIST dataset. Let's start by looking at what the PyTorch network definition looks like:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from brevitas_examples import bnn_pynq\n", + "showSrc(bnn_pynq.models.FC)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the network topology is constructed using a few helper functions that generate the quantized linear layers and quantized activations. The bitwidth of the layers is actually parametrized in the constructor, so let's instantiate a 1-bit weights and activations version of this network. We also have pretrained weights for this network, which we will load into the model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from finn.util.test import get_test_model\n", + "lfc = get_test_model(netname = \"LFC\", wbits = 1, abits = 1, pretrained = True)\n", + "lfc" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have now instantiated our trained PyTorch network. Let's try to run an example MNIST image through the network using PyTorch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import torch\n", + "import matplotlib.pyplot as plt\n", + "from pkgutil import get_data\n", + "import onnx\n", + "import onnx.numpy_helper as nph\n", + "raw_i = get_data(\"qonnx.data\", \"onnx/mnist-conv/test_data_set_0/input_0.pb\")\n", + "input_tensor = onnx.load_tensor_from_string(raw_i)\n", + "input_tensor_npy = nph.to_array(input_tensor)\n", + "input_tensor_pyt = torch.from_numpy(input_tensor_npy).float()\n", + "imgplot = plt.imshow(input_tensor_npy.reshape(28,28), cmap='gray')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from torch.nn.functional import softmax\n", + "# do forward pass in PyTorch/Brevitas\n", + "produced = lfc.forward(input_tensor_pyt).detach()\n", + "probabilities = softmax(produced, dim=-1).flatten()\n", + "probabilities" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "objects = [str(x) for x in range(10)]\n", + "y_pos = np.arange(len(objects))\n", + "plt.bar(y_pos, probabilities, align='center', alpha=0.5)\n", + "plt.xticks(y_pos, objects)\n", + "plt.ylabel('Predicted Probability')\n", + "plt.title('LFC-w1a1 Predictions for Image')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Call Brevitas QONNX export and visualize with Netron\n", + "\n", + "Brevitas comes with built-in QONNX export functionality. This is similar to the regular ONNX export capabilities of PyTorch, with a few differences:\n", + "\n", + "1. Weight and activation quantization is represented as a 'fake-quantization' with Quant and BipolarQuant nodes.\n", + "2. Truncation operations as required by average pooling are represented with a Trunc node.\n", + "\n", + "One can read more about how QONNX works and why it was developed here: https://xilinx.github.io/finn//2021/11/03/qonnx-and-finn.html\n", + "\n", + "Additionally QONNX comes with a set of tools for working with the format. These are maintained together with the Fast Machinelearning collaboration as an open-source projet here: https://github.com/fastmachinelearning/qonnx\n", + "\n", + "It's actually quite straightforward to export QONNX from our Brevitas model as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from brevitas.export import export_qonnx\n", + "export_onnx_path = \"/tmp/LFCW1A1_qonnx.onnx\"\n", + "input_shape = (1, 1, 28, 28)\n", + "export_qonnx(lfc, torch.randn(input_shape), export_onnx_path);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's examine what the exported ONNX model looks like. For this, we will use the Netron visualizer:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "showInNetron(export_onnx_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When running this notebook in the FINN Docker container, you should be able to see an interactive visualization of the imported network above, and click on individual nodes to inspect their parameters. If you look at any of the MatMul nodes, you should be able to see that the weights are all {-1, +1} values." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Import into FINN and converting QONNX to FINN-ONNX\n", + "\n", + "Similarily to the 1a notebook we will first run a cleanup transformation on the exported QONNX model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qonnx.util.cleanup import cleanup\n", + "\n", + "export_onnx_path_cleaned = \"/tmp/LFCW1A1-qonnx-clean.onnx\"\n", + "cleanup(export_onnx_path, out_file=export_onnx_path_cleaned)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "showInNetron(export_onnx_path_cleaned)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now import this QONNX model into FINN using the ModelWrapper. Here we can immediatley execute the model to verify correctness." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from qonnx.core.modelwrapper import ModelWrapper\n", + "import qonnx.core.onnx_exec as oxe\n", + "model = ModelWrapper(export_onnx_path_cleaned)\n", + "input_dict = {\"global_in\": nph.to_array(input_tensor)}\n", + "output_dict = oxe.execute_onnx(model, input_dict)\n", + "produced_qonnx = output_dict[list(output_dict.keys())[0]]\n", + "\n", + "produced_qonnx" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np.isclose(produced, produced_qonnx).all()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the `QONNXtoFINN` transformation we can convert the model to the FINN internal FINN-ONNX representation. Notably all Quant and BipolarQuant nodes will have disappeared and are converted into MultiThreshold nodes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from finn.transformation.qonnx.convert_qonnx_to_finn import ConvertQONNXtoFINN\n", + "model = ModelWrapper(export_onnx_path_cleaned)\n", + "\n", + "model = model.transform(ConvertQONNXtoFINN())\n", + "\n", + "export_onnx_path_converted = \"/tmp/LFCW1A1-qonnx-converted.onnx\"\n", + "model.save(export_onnx_path_converted)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "showInNetron(export_onnx_path_converted)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And once again we can execute the model with the FINN/QONNX execution engine." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "model = ModelWrapper(export_onnx_path_cleaned)\n", + "input_dict = {\"global_in\": nph.to_array(input_tensor)}\n", + "output_dict = oxe.execute_onnx(model, input_dict)\n", + "produced_finn = output_dict[list(output_dict.keys())[0]]\n", + "\n", + "produced_finn" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np.isclose(produced_qonnx, produced_finn).all()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have succesfully verified that the transformed and cleaned-up FINN graph still produces the same output, and can now use this model for further processing in FINN." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb index 388accad3aa2bb2633fb691241d234442d59bb11..73e9f4e6e1f6f01f6d3dc0e934615cb25c70278f 100644 --- a/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/cnv_end2end_example.ipynb @@ -81,16 +81,17 @@ "metadata": {}, "outputs": [], "source": [ + "import torch\n", "import onnx\n", "from finn.util.test import get_test_model_trained\n", - "import brevitas.onnx as bo\n", + "from brevitas.export import export_finn_onnx\n", "from qonnx.core.modelwrapper import ModelWrapper\n", "from qonnx.transformation.infer_shapes import InferShapes\n", "from qonnx.transformation.fold_constants import FoldConstants\n", "from qonnx.transformation.general import GiveReadableTensorNames, GiveUniqueNodeNames, RemoveStaticGraphInputs\n", "\n", "cnv = get_test_model_trained(\"CNV\", 1, 1)\n", - "bo.export_finn_onnx(cnv, (1, 3, 32, 32), build_dir + \"/end2end_cnv_w1a1_export.onnx\")\n", + "export_finn_onnx(cnv, torch.randn(1, 3, 32, 32), build_dir + \"/end2end_cnv_w1a1_export.onnx\")\n", "model = ModelWrapper(build_dir + \"/end2end_cnv_w1a1_export.onnx\")\n", "model = model.transform(InferShapes())\n", "model = model.transform(FoldConstants())\n", @@ -148,7 +149,7 @@ "# preprocessing: torchvision's ToTensor divides uint8 inputs by 255\n", "totensor_pyt = ToTensor()\n", "chkpt_preproc_name = build_dir+\"/end2end_cnv_w1a1_preproc.onnx\"\n", - "bo.export_finn_onnx(totensor_pyt, ishape, chkpt_preproc_name)\n", + "export_finn_onnx(totensor_pyt, torch.randn(ishape), chkpt_preproc_name)\n", "\n", "# join preprocessing and core model\n", "pre_model = ModelWrapper(chkpt_preproc_name)\n", @@ -240,7 +241,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We won't go into too much detail about what happens in each transformation and why they are called in the particular order they are (feel free to visualize the intermediate steps using Netron yourself if you are curious) but here is a brief summmmary:\n", + "We won't go into too much detail about what happens in each transformation and why they are called in the particular order they are (feel free to visualize the intermediate steps using Netron yourself if you are curious) but here is a brief summary:\n", "\n", "* `Streamline` moves floating point scaling and addition operations closer to the input of the nearest thresholding activation and absorbs them into thresholds\n", "* `LowerConvsToMatMul` converts ONNX `Conv` nodes into sequences of `Im2Col, MatMul` nodes as discussed above. `Im2Col` is a custom FINN ONNX high-level node type that implements the sliding window operator.\n", diff --git a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb index eec17b2fa7e8226cafe48f095aa38eb704b2812e..f99944e31f3e45f08b53bfd53b373fa726e09a49 100644 --- a/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb +++ b/notebooks/end2end_example/bnn-pynq/tfc_end2end_example.ipynb @@ -81,12 +81,13 @@ "metadata": {}, "outputs": [], "source": [ + "import torch\n", "import onnx\n", "from finn.util.test import get_test_model_trained\n", - "import brevitas.onnx as bo\n", + "from brevitas.export import export_finn_onnx\n", "\n", "tfc = get_test_model_trained(\"TFC\", 1, 1)\n", - "bo.export_finn_onnx(tfc, (1, 1, 28, 28), build_dir+\"/tfc_w1_a1.onnx\"); # semicolon added to suppress log" + "export_finn_onnx(tfc, torch.randn(1, 1, 28, 28), build_dir+\"/tfc_w1_a1.onnx\"); # semicolon added to suppress log" ] }, { @@ -267,7 +268,7 @@ "# preprocessing: torchvision's ToTensor divides uint8 inputs by 255\n", "totensor_pyt = ToTensor()\n", "chkpt_preproc_name = build_dir+\"/tfc_w1_a1_preproc.onnx\"\n", - "bo.export_finn_onnx(totensor_pyt, ishape, chkpt_preproc_name)\n", + "export_finn_onnx(totensor_pyt, torch.randn(ishape), chkpt_preproc_name)\n", "\n", "# join preprocessing and core model\n", "pre_model = ModelWrapper(chkpt_preproc_name)\n", diff --git a/notebooks/end2end_example/bnn-pynq/tfc_end2end_verification.ipynb b/notebooks/end2end_example/bnn-pynq/tfc_end2end_verification.ipynb index 6c3b7965098e013fa35ac5f5b2b481e678d68f5d..2f6cde6e5b895cfa9a4146bd11fca3e0d791c2f9 100644 --- a/notebooks/end2end_example/bnn-pynq/tfc_end2end_verification.ipynb +++ b/notebooks/end2end_example/bnn-pynq/tfc_end2end_verification.ipynb @@ -121,12 +121,11 @@ "output_dict = oxe.execute_onnx(model_for_sim, input_dict, return_full_exec_context=False)\n", "output_pysim = output_dict[list(output_dict.keys())[0]]\n", "\n", - "\n", - "\n", - "if np.isclose(output_pysim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all():\n", + "try:\n", + " assert np.isclose(output_pysim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all()\n", " print(\"Results are the same!\")\n", - "else:\n", - " print(\"The results are not the same!\")" + "except AssertionError:\n", + " assert False, \"The results are not the same!\"" ] }, { @@ -268,10 +267,11 @@ "output_dict = oxe.execute_onnx(parent_model, input_dict)\n", "output_cppsim = output_dict[list(output_dict.keys())[0]]\n", "\n", - "if np.isclose(output_cppsim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all():\n", + "try:\n", + " assert np.isclose(output_cppsim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all()\n", " print(\"Results are the same!\")\n", - "else:\n", - " print(\"The results are not the same!\")" + "except AssertionError:\n", + " assert False, \"The results are not the same!\"" ] }, { @@ -356,10 +356,11 @@ "output_dict = oxe.execute_onnx(model_for_rtlsim, input_dict)\n", "output_rtlsim = output_dict[list(output_dict.keys())[0]]\n", "\n", - "if np.isclose(output_rtlsim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all():\n", + "try:\n", + " assert np.isclose(output_rtlsim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all()\n", " print(\"Results are the same!\")\n", - "else:\n", - " print(\"The results are not the same!\")" + "except AssertionError:\n", + " assert False, \"The results are not the same!\"" ] }, { @@ -430,10 +431,11 @@ "metadata": {}, "outputs": [], "source": [ - "if np.isclose(output_rtlsim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all():\n", + "try:\n", + " assert np.isclose(output_rtlsim, np.where(output_golden[0]==np.amax(output_golden[0])), atol=1e-3).all()\n", " print(\"Results are the same!\")\n", - "else:\n", - " print(\"The results are not the same!\")" + "except AssertionError:\n", + " assert False, \"The results are not the same!\"" ] } ], diff --git a/notebooks/end2end_example/cybersecurity/1-train-mlp-with-brevitas.ipynb b/notebooks/end2end_example/cybersecurity/1-train-mlp-with-brevitas.ipynb index 3d77586258b9ddb64985e7f7b7a2215565839c50..0f90b8ee78f3113ce4f0a2fa5738771be60ed19e 100644 --- a/notebooks/end2end_example/cybersecurity/1-train-mlp-with-brevitas.ipynb +++ b/notebooks/end2end_example/cybersecurity/1-train-mlp-with-brevitas.ipynb @@ -62,8 +62,11 @@ "metadata": {}, "outputs": [], "source": [ + "import os\n", "import onnx\n", - "import torch" + "import torch\n", + "\n", + "model_dir = os.environ['FINN_ROOT'] + \"/notebooks/end2end_example/cybersecurity\"" ] }, { @@ -483,13 +486,14 @@ "metadata": {}, "outputs": [], "source": [ + "import os\n", "import torch\n", "\n", "# Make sure the model is on CPU before loading a pretrained state_dict\n", "model = model.cpu()\n", "\n", "# Load pretrained weights\n", - "trained_state_dict = torch.load(\"state_dict.pth\")[\"models_state_dict\"][0]\n", + "trained_state_dict = torch.load(model_dir + \"/state_dict.pth\")[\"models_state_dict\"][0]\n", "\n", "model.load_state_dict(trained_state_dict, strict=False)" ] @@ -677,10 +681,10 @@ "metadata": {}, "outputs": [], "source": [ - "import brevitas.onnx as bo\n", + "from brevitas.export import export_finn_onnx\n", "from brevitas.quant_tensor import QuantTensor\n", "\n", - "ready_model_filename = \"cybsec-mlp-ready.onnx\"\n", + "ready_model_filename = model_dir + \"/cybsec-mlp-ready.onnx\"\n", "input_shape = (1, 600)\n", "\n", "# create a QuantTensor instance to mark input as bipolar during export\n", @@ -696,7 +700,7 @@ "model_for_export.cpu()\n", "\n", "# Export to ONNX\n", - "bo.export_finn_onnx(\n", + "export_finn_onnx(\n", " model_for_export, export_path=ready_model_filename, input_t=input_qt\n", ")\n", "\n", diff --git a/notebooks/end2end_example/cybersecurity/2-import-into-finn-and-verify.ipynb b/notebooks/end2end_example/cybersecurity/2-import-into-finn-and-verify.ipynb index e4848a1f40bed5865eccc1d831a634ac5f54e965..5f4924b30989eb7089bc32117cb1e1a0d82f2e09 100644 --- a/notebooks/end2end_example/cybersecurity/2-import-into-finn-and-verify.ipynb +++ b/notebooks/end2end_example/cybersecurity/2-import-into-finn-and-verify.ipynb @@ -62,9 +62,11 @@ "metadata": {}, "outputs": [], "source": [ + "import os\n", "from qonnx.core.modelwrapper import ModelWrapper\n", "\n", - "ready_model_filename = \"cybsec-mlp-ready.onnx\"\n", + "model_dir = os.environ['FINN_ROOT'] + \"/notebooks/end2end_example/cybersecurity\"\n", + "ready_model_filename = model_dir + \"/cybsec-mlp-ready.onnx\"\n", "model_for_sim = ModelWrapper(ready_model_filename)" ] }, @@ -151,7 +153,7 @@ "model_for_sim = model_for_sim.transform(InferDataTypes())\n", "model_for_sim = model_for_sim.transform(RemoveStaticGraphInputs())\n", "\n", - "verif_model_filename = \"cybsec-mlp-verification.onnx\"\n", + "verif_model_filename = model_dir + \"/cybsec-mlp-verification.onnx\"\n", "model_for_sim.save(verif_model_filename)" ] }, @@ -258,7 +260,8 @@ "\n", "# replace this with your trained network checkpoint if you're not\n", "# using the pretrained weights\n", - "trained_state_dict = torch.load(\"state_dict.pth\")[\"models_state_dict\"][0]\n", + "trained_state_dict = torch.load(model_dir + \"/state_dict.pth\")[\"models_state_dict\"][0]\n", + "\n", "# Uncomment the following line if you previously chose to train the network yourself\n", "#trained_state_dict = torch.load(\"state_dict_self-trained.pth\")\n", "\n", @@ -365,10 +368,11 @@ "metadata": {}, "outputs": [], "source": [ - "if ok == n_verification_inputs:\n", + "try:\n", + " assert ok == n_verification_inputs\n", " print(\"Verification succeeded. Brevitas and FINN-ONNX execution outputs are identical\")\n", - "else:\n", - " print(\"Verification failed. Brevitas and FINN-ONNX execution outputs are NOT identical\")" + "except AssertionError:\n", + " assert False, \"Verification failed. Brevitas and FINN-ONNX execution outputs are NOT identical\"" ] }, { diff --git a/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb b/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb index a18cafd6044328d53139acafb2be2cf73a4ec9b6..80f3cd3819a76b5ad538f28f48e85fd22c660f20 100644 --- a/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb +++ b/notebooks/end2end_example/cybersecurity/3-build-accelerator-with-finn.ipynb @@ -115,7 +115,8 @@ "import os\n", "import shutil\n", "\n", - "model_file = \"cybsec-mlp-ready.onnx\"\n", + "model_dir = os.environ['FINN_ROOT'] + \"/notebooks/end2end_example/cybersecurity\"\n", + "model_file = model_dir + \"/cybsec-mlp-ready.onnx\"\n", "\n", "estimates_output_dir = \"output_estimates_only\"\n", "\n", @@ -148,6 +149,15 @@ "build.build_dataflow_cfg(model_file, cfg_estimates)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert os.path.exists(estimates_output_dir + \"/report/estimate_network_performance.json\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -272,7 +282,7 @@ "import os\n", "import shutil\n", "\n", - "model_file = \"cybsec-mlp-ready.onnx\"\n", + "model_file = model_dir + \"/cybsec-mlp-ready.onnx\"\n", "\n", "rtlsim_output_dir = \"output_ipstitch_ooc_rtlsim\"\n", "\n", @@ -305,6 +315,17 @@ "build.build_dataflow_cfg(model_file, cfg_stitched_ip)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "assert os.path.exists(rtlsim_output_dir + \"/report/ooc_synth_and_timing.json\")\n", + "assert os.path.exists(rtlsim_output_dir + \"/report/rtlsim_performance.json\")\n", + "assert os.path.exists(rtlsim_output_dir + \"/final_hw_config.json\")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -412,7 +433,7 @@ "import os\n", "import shutil\n", "\n", - "model_file = \"cybsec-mlp-ready.onnx\"\n", + "model_file = model_dir + \"/cybsec-mlp-ready.onnx\"\n", "\n", "final_output_dir = \"output_final\"\n", "\n", diff --git a/requirements.txt b/requirements.txt index 83aad07d729e30cbbbaf565b4332fb1f7ae6f014..223138932e4dbae3f67a70ee3bcb10ba905bf83f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ bitstring==3.1.7 clize==4.1.1 dataclasses-json==0.5.7 -docrep==0.2.7 gspread==3.6.0 +ipython==8.12.2 numpy==1.22.0 onnx==1.13.0 onnxoptimizer @@ -14,6 +14,8 @@ pyscaffold==3.2.1 scipy==1.5.2 setupext-janitor>=1.1.2 sigtools==2.0.3 +sphinx==5.0.2 +sphinx_rtd_theme==0.5.0 toposort==1.5 vcdvcd==1.0.5 wget==3.2 diff --git a/setup.cfg b/setup.cfg index 1893aa42316dad341fcedbd527f5abcf482e5cfb..50a91498ce994ba3672cd2e2bbc2aa73f7fa0406 100644 --- a/setup.cfg +++ b/setup.cfg @@ -81,6 +81,8 @@ docs = pytest netron vcdvcd + sphinx==5.0.2 + sphinx_rtd_theme==0.5.0 torchvision torch qonnx@git+https://github.com/fastmachinelearning/qonnx@main#egg=qonnx @@ -127,6 +129,7 @@ markers = transform: mark tests that test transformations (before hls layers) fpgadataflow: mark tests related to hls layers end2end: mark tests that run the end2end flow + notebooks: mark tests that execute all Jupyter notebooks norecursedirs = dist build diff --git a/src/finn/builder/build_dataflow_config.py b/src/finn/builder/build_dataflow_config.py index a38cb6e572d683871a924330742a1859b6fbe75d..4c3e4ff899513bf9939612e04621140d84be1bd1 100644 --- a/src/finn/builder/build_dataflow_config.py +++ b/src/finn/builder/build_dataflow_config.py @@ -119,6 +119,7 @@ default_build_dataflow_steps = [ "step_create_dataflow_partition", "step_target_fps_parallelization", "step_apply_folding_config", + "step_minimize_bit_width", "step_generate_estimate_reports", "step_hls_codegen", "step_hls_ipgen", @@ -140,6 +141,7 @@ estimate_only_dataflow_steps = [ "step_create_dataflow_partition", "step_target_fps_parallelization", "step_apply_folding_config", + "step_minimize_bit_width", "step_generate_estimate_reports", ] @@ -233,6 +235,12 @@ class DataflowBuildConfig: #: flexibility, and makes it possible to have runtime-writable thresholds. standalone_thresholds: Optional[bool] = False + #: (Optional) Whether optimizations that minimize the bit width of the + #: weights and accumulator will be applied. Because this optimization relies + #: on the the values of the weights, it will only be applied if runtime- + #: writeable weights is not enabled. + minimize_bit_width: Optional[bool] = True + #: Target board, only needed for generating full bitfiles where the FINN #: design is integrated into a shell. #: e.g. "Pynq-Z1" or "U250" diff --git a/src/finn/builder/build_dataflow_steps.py b/src/finn/builder/build_dataflow_steps.py index 2ee898bc7d50822f962b6a70cf86b2893e0937b7..a22b5adc9846af22833568065a0c31e6e9a4d111 100644 --- a/src/finn/builder/build_dataflow_steps.py +++ b/src/finn/builder/build_dataflow_steps.py @@ -89,6 +89,12 @@ from finn.transformation.fpgadataflow.insert_dwc import InsertDWC from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild +from finn.transformation.fpgadataflow.minimize_accumulator_width import ( + MinimizeAccumulatorWidth, +) +from finn.transformation.fpgadataflow.minimize_weight_bit_width import ( + MinimizeWeightBitWidth, +) from finn.transformation.fpgadataflow.prepare_cppsim import PrepareCppSim from finn.transformation.fpgadataflow.prepare_ip import PrepareIP from finn.transformation.fpgadataflow.prepare_rtlsim import PrepareRTLSim @@ -410,6 +416,7 @@ def step_target_fps_parallelization(model: ModelWrapper, cfg: DataflowBuildConfi hw_attrs = [ "PE", "SIMD", + "parallel_window", "ram_style", "resType", "mem_mode", @@ -477,6 +484,16 @@ def step_generate_estimate_reports(model: ModelWrapper, cfg: DataflowBuildConfig return model +def step_minimize_bit_width(model: ModelWrapper, cfg: DataflowBuildConfig): + """Tighten the weight and accumulator bit widths for each layer.""" + if cfg.minimize_bit_width: + model = model.transform(MinimizeWeightBitWidth()) + model = model.transform(MinimizeAccumulatorWidth()) + # make sure the changed datatypes are propagated through the network + model = model.transform(InferDataTypes()) + return model + + def step_hls_codegen(model: ModelWrapper, cfg: DataflowBuildConfig): "Generate Vivado HLS code to prepare HLSCustomOp nodes for IP generation." @@ -572,6 +589,7 @@ def step_set_fifo_depths(model: ModelWrapper, cfg: DataflowBuildConfig): hw_attrs = [ "PE", "SIMD", + "parallel_window", "ram_style", "depth", "impl_style", @@ -668,7 +686,6 @@ def step_measure_rtlsim_performance(model: ModelWrapper, cfg: DataflowBuildConfi rtlsim_bs = int(cfg.rtlsim_batch_size) orig_rtlsim_trace_depth = get_rtlsim_trace_depth() if force_python_rtlsim: - # run with single input to get latency assert rtlsim_bs > 0, "rtlsim batch size must be >0" if cfg.verify_save_rtlsim_waveforms: # set depth to 3 for layer-by-layer visibility @@ -680,9 +697,11 @@ def step_measure_rtlsim_performance(model: ModelWrapper, cfg: DataflowBuildConfi rtlsim_model.set_metadata_prop( "extra_verilator_args", str(["-CFLAGS", "-O3"]) ) + # run with single input to get latency + rtlsim_latency_dict = throughput_test_rtlsim(rtlsim_model, 1) + # run with batch to get stable-state throughput rtlsim_perf_dict = throughput_test_rtlsim(rtlsim_model, rtlsim_bs) - rtlsim_latency = rtlsim_perf_dict["cycles"] - rtlsim_perf_dict["latency_cycles"] = rtlsim_latency + rtlsim_perf_dict["latency_cycles"] = rtlsim_latency_dict["cycles"] else: rtlsim_perf_dict = verilator_fifosim(model, rtlsim_bs) # keep keys consistent between the Python and C++-styles @@ -696,6 +715,19 @@ def step_measure_rtlsim_performance(model: ModelWrapper, cfg: DataflowBuildConfi for (key, val) in rtlsim_perf_dict.items(): if "max_count" in key: del rtlsim_perf_dict[key] + # estimate stable-state throughput based on latency+throughput + if rtlsim_bs == 1: + rtlsim_perf_dict["stable_throughput[images/s]"] = rtlsim_perf_dict[ + "throughput[images/s]" + ] + else: + total_cycles = rtlsim_perf_dict["cycles"] + latency_cycles = rtlsim_perf_dict["latency_cycles"] + stablestate_cycles = total_cycles - latency_cycles + clk_ns = float(model.get_metadata_prop("clk_ns")) + fclk_mhz = 1 / (clk_ns * 0.001) + runtime_s = (stablestate_cycles * clk_ns) * (10**-9) + rtlsim_perf_dict["stable_throughput[images/s]"] = rtlsim_bs / runtime_s with open(report_dir + "/rtlsim_performance.json", "w") as f: json.dump(rtlsim_perf_dict, f, indent=2) @@ -823,6 +855,7 @@ build_dataflow_step_lookup = { "step_create_dataflow_partition": step_create_dataflow_partition, "step_target_fps_parallelization": step_target_fps_parallelization, "step_apply_folding_config": step_apply_folding_config, + "step_minimize_bit_width": step_minimize_bit_width, "step_generate_estimate_reports": step_generate_estimate_reports, "step_hls_codegen": step_hls_codegen, "step_hls_ipgen": step_hls_ipgen, diff --git a/src/finn/core/onnx_exec.py b/src/finn/core/onnx_exec.py index 2695113661ed286c94ae9cb5f20ca99cc1fced7f..daecb59743d1b843e9d7fd40fdbf5bf10fac2fe1 100644 --- a/src/finn/core/onnx_exec.py +++ b/src/finn/core/onnx_exec.py @@ -31,7 +31,6 @@ import numpy as np import qonnx.analysis.topology as ta from qonnx.core.onnx_exec import execute_onnx as execute_onnx_base -from finn.core.remote_exec import remote_exec from finn.core.rtlsim_exec import rtlsim_exec @@ -51,7 +50,6 @@ def execute_onnx( # check if model has an execution mode set # if None, execute model node using the QONNX-provided execute_onnx impl - # if set to "remote_pynq" execute model on PYNQ board # if set to "rtlsim" execute model using pyverilator model_exec_mode = model.get_metadata_prop("exec_mode") if (model_exec_mode is None) or (model_exec_mode == ""): @@ -91,22 +89,17 @@ def execute_onnx( # check if model has an execution mode set # if None, execute model node by node using execute_node() - # if set to "remote_pynq" execute model on PYNQ board # if set to "rtlsim" execute model using pyverilator model_exec_mode = model.get_metadata_prop("exec_mode") if (model_exec_mode is None) or (model_exec_mode == ""): return execute_onnx_base() - elif model_exec_mode == "remote_pynq": - # use remote exec metadata built into model to execute on a remote PYNQ - remote_exec(model, execution_context) elif model_exec_mode == "rtlsim": # use stitched IP for rtlsim rtlsim_exec(model, execution_context) else: raise Exception( - """Metadata property "exec_mode" is set to an unknown value. - Can be left unset or has to be set to "remote_pynq" for remote execution - on PYNQ board or "rtlsim" for execution using pyverilator!""" + """Metadata property "exec_mode" is set to an unknown value. Can be left + unset or has to be set to "rtlsim" for execution using pyverilator!""" ) if return_full_exec_context: diff --git a/src/finn/core/remote_exec.py b/src/finn/core/remote_exec.py deleted file mode 100644 index f487b48f86f1ef0440ed4a8bf371083369dd096c..0000000000000000000000000000000000000000 --- a/src/finn/core/remote_exec.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) 2020 Xilinx, Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of Xilinx nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import numpy as np -import os -import subprocess -import warnings - - -def remote_exec(model, execution_context): - """Executes the given model remotely on the pynq board. The metadata properties - related to the pynq board have to be set. The execution context contains the - input values.""" - # TODO fix for multi input-output - pynq_ip = model.get_metadata_prop("pynq_ip") - pynq_port = int(model.get_metadata_prop("pynq_port")) - pynq_username = model.get_metadata_prop("pynq_username") - pynq_password = model.get_metadata_prop("pynq_password") - pynq_target_dir = model.get_metadata_prop("pynq_target_dir") - deployment_dir = model.get_metadata_prop("pynq_deploy_dir") - platform = model.get_metadata_prop("platform") - assert platform in ["alveo", "zynq-iodma"] - bitfile = model.get_metadata_prop("bitfile") - bitfile = os.path.basename(bitfile) - if pynq_password == "": - if "zynq" in platform: - raise Exception("PYNQ board remote exec needs password for sudo") - else: - local_prefix = "" # assume we are using an ssh key - warnings.warn("Empty password, make sure you've set up an ssh key") - else: - local_prefix = "sshpass -p %s " % pynq_password - - if platform == "alveo": - # Alveo can run without sudo - remote_prefix = "" - elif "zynq" in platform: - # PYNQ Zynq boards need to execute with sudo - remote_prefix = "echo %s | sudo -S " % pynq_password - - inp = execution_context[model.graph.input[0].name] - # make copy of array before saving it - inp = inp.copy() - batchsize = inp.shape[0] - np.save(os.path.join(deployment_dir, "input.npy"), inp) - # extracting last folder of absolute path (deployment_dir) - deployment_folder = os.path.basename(os.path.normpath(deployment_dir)) - # copy input to PYNQ board - cmd = local_prefix + "scp -P{} -r {}/input.npy {}@{}:{}/{}".format( - pynq_port, - deployment_dir, - pynq_username, - pynq_ip, - pynq_target_dir, - deployment_folder, - ) - bash_command = ["/bin/bash", "-c", cmd] - process_scp_in = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_scp_in.communicate() - - # use platform attribute for correct remote execution - if platform == "alveo": - remote_cmd = "bash -ic 'bash alveo_run.sh execute %d' \"" % batchsize - else: - remote_cmd = ( - "python3.6 driver.py --exec_mode=execute --batchsize={} " - "--bitfile={} --inputfile=input.npy --outputfile=output.npy " - '--platform={} "' - ).format(batchsize, bitfile, platform) - cmd = ( - local_prefix + 'ssh {}@{} -p {} "cd {}/{}; ' + remote_prefix + remote_cmd - ).format(pynq_username, pynq_ip, pynq_port, pynq_target_dir, deployment_folder) - bash_command = ["/bin/bash", "-c", cmd] - process_exec_accel = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_exec_accel.communicate() - # remove stale output file from local dir, if any - try: - os.remove("{}/output.npy".format(deployment_dir)) - except FileNotFoundError: - pass - # copy generated output to local - cmd = local_prefix + "scp -P{} {}@{}:{}/{}/output.npy {}".format( - pynq_port, - pynq_username, - pynq_ip, - pynq_target_dir, - deployment_folder, - deployment_dir, - ) - bash_command = ["/bin/bash", "-c", cmd] - process_scp_out = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_scp_out.communicate() - outp = np.load("{}/output.npy".format(deployment_dir)) - execution_context[model.graph.output[0].name] = outp diff --git a/src/finn/core/throughput_test.py b/src/finn/core/throughput_test.py index 3533fd13399a4ba4392d66af785979afc32cab29..08633be33b8ab6d096275aca2c362a8bac43d704 100644 --- a/src/finn/core/throughput_test.py +++ b/src/finn/core/throughput_test.py @@ -28,90 +28,11 @@ import numpy as np import os -import subprocess -import warnings from qonnx.util.basic import gen_finn_dt_tensor from finn.core.rtlsim_exec import rtlsim_exec -def throughput_test_remote(model, batchsize=1000, timeout=None): - """Runs the throughput test for the given model remotely on the pynq board. - The metadata properties related to the pynq board have to be set. - Additionally a timeout for the SSH communication can be set. - Returns a dictionary with results of the throughput test. Returns None - if the test fails.""" - - pynq_ip = model.get_metadata_prop("pynq_ip") - pynq_port = int(model.get_metadata_prop("pynq_port")) - pynq_username = model.get_metadata_prop("pynq_username") - pynq_password = model.get_metadata_prop("pynq_password") - pynq_target_dir = model.get_metadata_prop("pynq_target_dir") - deployment_dir = model.get_metadata_prop("pynq_deploy_dir") - # extracting last folder of absolute path (deployment_dir) - deployment_folder = os.path.basename(os.path.normpath(deployment_dir)) - platform = model.get_metadata_prop("platform") - assert platform in ["alveo", "zynq-iodma"] - bitfile = model.get_metadata_prop("bitfile") - bitfile = os.path.basename(bitfile) - if pynq_password == "": - if "zynq" in platform: - raise Exception("PYNQ board remote exec needs password for sudo") - else: - local_prefix = "" # assume we are using an ssh key - warnings.warn("Empty password, make sure you've set up an ssh key") - else: - local_prefix = "sshpass -p %s " % pynq_password - - if platform == "alveo": - # Alveo can run without sudo but needs correct environment - remote_prefix = "conda activate finn-pynq-alveo; " - elif "zynq" in platform: - # PYNQ Zynq boards need to execute with sudo - remote_prefix = "echo %s | sudo -S " % pynq_password - - # use platform attribute for correct remote execution - if platform == "alveo": - remote_cmd = "bash -ic 'bash alveo_run.sh throughput_test %d' \"" % batchsize - else: - remote_cmd = ( - "python3.6 driver.py --exec_mode=throughput_test --batchsize={} " - "--bitfile={} --inputfile=input.npy --outputfile=output.npy " - '--platform={} "' - ).format(batchsize, bitfile, platform) - cmd = ( - local_prefix + 'ssh {}@{} -p {} "cd {}/{}; ' + remote_prefix + remote_cmd - ).format(pynq_username, pynq_ip, pynq_port, pynq_target_dir, deployment_folder) - bash_command = ["/bin/bash", "-c", cmd] - process_throughput_test = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_throughput_test.communicate(timeout=timeout) - - # remove any pre-existing metrics file - try: - os.remove("{}/nw_metrics.txt".format(deployment_dir)) - except FileNotFoundError: - pass - - cmd = local_prefix + "scp -P{} {}@{}:{}/{}/nw_metrics.txt {}".format( - pynq_port, - pynq_username, - pynq_ip, - pynq_target_dir, - deployment_folder, - deployment_dir, - ) - bash_command = ["/bin/bash", "-c", cmd] - process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_compile.communicate(timeout=timeout) - - try: - with open("{}/nw_metrics.txt".format(deployment_dir), "r") as file: - res = eval(file.read()) - return res - except FileNotFoundError: - return None - - def throughput_test_rtlsim(model, batchsize=100): """Runs a throughput test for the given IP-stitched model. When combined with tracing, useful to determine bottlenecks and required FIFO sizes.""" diff --git a/src/finn/custom_op/fpgadataflow/addstreams_batch.py b/src/finn/custom_op/fpgadataflow/addstreams_batch.py index cd0af6b3ab3d8250abbf7d48e004622e55f09f04..af106d9c0698d2d49bbd8f8998f57cad0b2e781e 100644 --- a/src/finn/custom_op/fpgadataflow/addstreams_batch.py +++ b/src/finn/custom_op/fpgadataflow/addstreams_batch.py @@ -38,8 +38,8 @@ from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy class AddStreams_Batch(HLSCustomOp): """Class that corresponds to finn-hlslib AddStreams_Batch function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = super().get_nodeattr_types() diff --git a/src/finn/custom_op/fpgadataflow/channelwise_op_batch.py b/src/finn/custom_op/fpgadataflow/channelwise_op_batch.py index 46adca680d3c96695eeb5a91be53ea158fc78f1f..7791647abf1c5a51c0425ae835471ec9ceb7c21c 100644 --- a/src/finn/custom_op/fpgadataflow/channelwise_op_batch.py +++ b/src/finn/custom_op/fpgadataflow/channelwise_op_batch.py @@ -39,8 +39,6 @@ from finn.util.data_packing import ( rtlsim_output_to_npy, ) -from . import templates - # ONNX i/o tensor shape assumptions for channelwise ops: # input 0 is the input tensor, shape (..., NumChannels) # input 1 is the channelwise parameter tensor, shape (NumChannels, params_per_channel) @@ -85,9 +83,8 @@ class ChannelwiseOp_Batch(HLSCustomOp): including Add, Mul and multi-thresholding. """ - def __init__(self, onnx_node): - super().__init__(onnx_node) - self.decoupled_wrapper = templates.decoupled_wrapper + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/checksum.py b/src/finn/custom_op/fpgadataflow/checksum.py index c927c07df21faf40ccbf9ddbe47e3f2f2ca61c89..99646274fa1bc5b710b23ea42a25d0fed0da529c 100644 --- a/src/finn/custom_op/fpgadataflow/checksum.py +++ b/src/finn/custom_op/fpgadataflow/checksum.py @@ -38,8 +38,8 @@ from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy class CheckSum(HLSCustomOp): """Class that corresponds to custom_hls checksum function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/concat.py b/src/finn/custom_op/fpgadataflow/concat.py index 4437bcd1984c5194b0a19b43d692babb7e3cd158..8b655b570d0396e253a1c98231702f816072da20 100644 --- a/src/finn/custom_op/fpgadataflow/concat.py +++ b/src/finn/custom_op/fpgadataflow/concat.py @@ -39,8 +39,8 @@ class StreamingConcat(HLSCustomOp): """Streaming concatenation node with dynamically generated HLS. Only supports concatenating along the last axis.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/convolutioninputgenerator.py b/src/finn/custom_op/fpgadataflow/convolutioninputgenerator.py index 1566445999a2c568b5c5a112d436bf05fd89aca5..6cc9208bb81ff68fe941c8d8d006c65b635eb437 100644 --- a/src/finn/custom_op/fpgadataflow/convolutioninputgenerator.py +++ b/src/finn/custom_op/fpgadataflow/convolutioninputgenerator.py @@ -54,8 +54,8 @@ class ConvolutionInputGenerator(HLSCustomOp): attributes (e.g. depthwise or not, whether k % stride is 0) a different variant will be picked for the actual HLS implementation.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/convolutioninputgenerator1d.py b/src/finn/custom_op/fpgadataflow/convolutioninputgenerator1d.py index f1c84662cc06e89df5bd7c0762ac47b8c5723502..6e792ca585718ff9690b0a2430fc09ba46e0a2ba 100644 --- a/src/finn/custom_op/fpgadataflow/convolutioninputgenerator1d.py +++ b/src/finn/custom_op/fpgadataflow/convolutioninputgenerator1d.py @@ -59,8 +59,8 @@ class ConvolutionInputGenerator1D(HLSCustomOp): attributes (e.g. depthwise or not, whether dilation is 0) a different variant will be picked for the actual HLS implementation.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/convolutioninputgenerator_rtl.py b/src/finn/custom_op/fpgadataflow/convolutioninputgenerator_rtl.py index 1afd23d3a1709a8929a03c21a6eba0a5a8cd6ba6..c54c4ac1c90ed72331c61b0a28cf6040b8d66881 100755 --- a/src/finn/custom_op/fpgadataflow/convolutioninputgenerator_rtl.py +++ b/src/finn/custom_op/fpgadataflow/convolutioninputgenerator_rtl.py @@ -29,6 +29,7 @@ import math import numpy as np import os +import shutil from qonnx.core.datatype import DataType from qonnx.custom_op.general import im2col from qonnx.custom_op.general.im2col import compute_conv_output_dim @@ -60,8 +61,8 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): (sliding window) function variants. Generates an RTL ConvolutionInputGenerator implementation based on (System-)Verilog templates, defined in finn-rtllib/swg.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { @@ -72,8 +73,8 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): "SIMD": ("i", True, 0), # additional parallelization parameter - not yet implemented "M": ("i", False, 1), - # alternative implementation style - not yet implemented - "parallel_window": ("i", False, 0, {0}), + # Enable parallel window output (requires full SIMD unfolding) + "parallel_window": ("i", False, 0, {0, 1}), "Stride": ("ints", True, []), # [H, W] = [Y, X] "Dilation": ("ints", True, []), # [H, W] = [Y, X] # FINN DataTypes for inputs, weights, outputs @@ -81,7 +82,7 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): "outputDataType": ("s", True, ""), "depthwise": ("i", False, 0, {0, 1}), # Enable reprogrammable implementation to change FM dimensions, - # stride, or dilation during runtime + # stride, or dilation during runtime (requires parallel_window = 0) "dynamic_mode": ("i", False, 0, {0, 1}), # FPGA resource type for ConvolutionInputGenerator input buffer # auto -- let Vivado decide @@ -188,6 +189,7 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): return self.get_instream_width() def get_number_input_values(self): + """Function to get the number of expected input values.""" folded_ishape = self.get_folded_input_shape() num_input_elems = np.prod(folded_ishape[:-1]) return num_input_elems @@ -198,6 +200,7 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): return num_output_elems def get_1d_conv_attrs_normalized(self): + """Returns normalized spatial attributes, where H=1 for the 1D case.""" # normalize FM dimensions so that: # [H, W] = [Y, X] = [1, D] or [D, 1] are always mapped to [1, D]. # The dummy ('1') dimension is the Y-dimension. @@ -218,6 +221,8 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): return (ifm_ch, ifm_dim, ofm_dim, k, stride, dilation) def get_buffer_depth(self): + """Returns total depth of the internal buffer, depending on + implementation style.""" ifm_ch = self.get_nodeattr("IFMChannels") k = self.get_nodeattr("ConvKernelDim") ifm_dim = self.get_nodeattr("IFMDim") @@ -233,13 +238,13 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): mmv_out = 1 channel_factor = int(ifm_ch / simd) + # compute minimal buffer length (assuming it holds 1 complete window) + buffer_min_size = ( + (k_h - 1) * dilation_h * w + (k_w - 1) * dilation_w + 1 + ) * channel_factor + impl_style = self.select_impl_style() if impl_style == "default": - # compute minimal buffer length (assuming it holds 1 complete window) - buffer_min_size = ( - (k_h - 1) * dilation_h * w + (k_w - 1) * dilation_w + 1 - ) * channel_factor - # add additional buffer space in case of stride > 1 # this minimizes cycle count as it allows an earlier pre-load of inputs buffer_depth = ( @@ -255,73 +260,89 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): * channel_factor, ) ) - else: - buffer_depth = 0 - raise Exception("Requested impl. style not implemented") + elif impl_style == "parallel": + buffer_depth = buffer_min_size + 1 return buffer_depth def get_exp_cycles(self): - simd = self.get_nodeattr("SIMD") - ifm_ch = self.get_nodeattr("IFMChannels") - k = self.get_nodeattr("ConvKernelDim") - ifm_dim = self.get_nodeattr("IFMDim") - ofm_dim = self.get_nodeattr("OFMDim") - stride = self.get_nodeattr("Stride") - dilation = self.get_nodeattr("Dilation") - depthwise = self.get_nodeattr("depthwise") - ifm_dim_h, ifm_dim_w = ifm_dim - ofm_dim_h, ofm_dim_w = ofm_dim - k_h, k_w = k - stride_h, stride_w = stride - dilation_h, dilation_w = dilation - - channel_factor = int(ifm_ch / simd) + impl_style = self.select_impl_style() - if ifm_dim_h == 1 or ifm_dim_w == 1: - # 1D case - ( - ifm_ch, - [ifm_dim_h, ifm_dim_w], - [ofm_dim_h, ofm_dim_w], - [k_h, k_w], - [stride_h, stride_w], - [dilation_h, dilation_w], - ) = self.get_1d_conv_attrs_normalized() - - if depthwise: - exp_cycles = ( - +ofm_dim_w * k_w * channel_factor - + channel_factor * (k_w - 1) * (stride_w - 1) - - (k_w - 1) - + 2 - ) + if impl_style == "parallel": + exp_cycles = self.get_number_input_values() + 2 + elif impl_style == "default": + simd = self.get_nodeattr("SIMD") + ifm_ch = self.get_nodeattr("IFMChannels") + k = self.get_nodeattr("ConvKernelDim") + ifm_dim = self.get_nodeattr("IFMDim") + ofm_dim = self.get_nodeattr("OFMDim") + stride = self.get_nodeattr("Stride") + dilation = self.get_nodeattr("Dilation") + depthwise = self.get_nodeattr("depthwise") + ifm_dim_h, ifm_dim_w = ifm_dim + ofm_dim_h, ofm_dim_w = ofm_dim + k_h, k_w = k + stride_h, stride_w = stride + dilation_h, dilation_w = dilation + + channel_factor = int(ifm_ch / simd) + if ifm_dim_h == 1 or ifm_dim_w == 1: + # 1D case + ( + ifm_ch, + [ifm_dim_h, ifm_dim_w], + [ofm_dim_h, ofm_dim_w], + [k_h, k_w], + [stride_h, stride_w], + [dilation_h, dilation_w], + ) = self.get_1d_conv_attrs_normalized() + + if depthwise: + exp_cycles = ( + +ofm_dim_w * k_w * channel_factor + + channel_factor * (k_w - 1) * (stride_w - 1) + - (k_w - 1) + + 2 + ) + else: + exp_cycles = ofm_dim_w * k_w * channel_factor + 2 else: - exp_cycles = ofm_dim_w * k_w * channel_factor + 2 - else: - # 2D case - buffer_min_size = ( - (k_h - 1) * dilation_h * ifm_dim_w + (k_w - 1) * dilation_w + 1 - ) * channel_factor - cycles_write_block = ofm_dim_w * k_w * k_h * channel_factor - cycles_read_block = stride_w * ifm_dim_w * channel_factor - max_cycles = max(cycles_write_block, cycles_read_block) - if depthwise: - max_cycles += ofm_dim_w * (stride_w - 1) * (channel_factor - 1) - exp_cycles = buffer_min_size + ofm_dim_h * max_cycles # initial buffering - if depthwise: - exp_cycles += (stride_h - 1) * ifm_dim_w * channel_factor + # 2D case + buffer_min_size = ( + (k_h - 1) * dilation_h * ifm_dim_w + (k_w - 1) * dilation_w + 1 + ) * channel_factor + cycles_write_block = ofm_dim_w * k_w * k_h * channel_factor + cycles_read_block = stride_w * ifm_dim_w * channel_factor + max_cycles = max(cycles_write_block, cycles_read_block) + if depthwise: + max_cycles += ofm_dim_w * (stride_w - 1) * (channel_factor - 1) + exp_cycles = buffer_min_size + ofm_dim_h * max_cycles + if depthwise: + exp_cycles += (stride_h - 1) * ifm_dim_w * channel_factor return int(exp_cycles) def bram_estimation(self): simd = self.get_nodeattr("SIMD") ram_style = self.get_nodeattr("ram_style") + impl_style = self.select_impl_style() + [k_h, k_w] = self.get_nodeattr("ConvKernelDim") + [ifm_dim_h, ifm_dim_w] = self.get_nodeattr("IFMDim") + [dilation_h, dilation_w] = self.get_nodeattr("Dilation") - # NOTE: Actual BRAM usage might be lower in some cases. - # This does not account for the exact Vivado behavior yet. - buffer_width = simd * self.get_input_datatype().bitwidth() - buffer_depth = self.get_buffer_depth() if ram_style == "block" or ram_style == "auto": + buffer_width = simd * self.get_input_datatype().bitwidth() + if impl_style == "default": + buffer_depth = self.get_buffer_depth() + buffer_count = 1 + elif impl_style == "parallel": + if ifm_dim_h == 1 or ifm_dim_w == 1: + return 0 # 1D case (no line buffers needed) + kernel_width = (k_w - 1) * dilation_w + 1 + buffer_depth = (ifm_dim_w - kernel_width) + ifm_dim_w * (dilation_h - 1) + buffer_count = k_h - 1 + + # NOTE: Actual BRAM usage might be lower in some cases + # due to imperfect modeling of Vivado behavior if buffer_depth <= 512: ram_width = 36 elif buffer_depth <= 1024: @@ -356,7 +377,9 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): remainder_cascade_width = math.ceil(buffer_width / remainder_width) cascade_savings = ram_cascade_width - remainder_cascade_width - return int(ram_cascade_depth * ram_cascade_width - cascade_savings) + return int( + (ram_cascade_depth * ram_cascade_width - cascade_savings) * buffer_count + ) else: return 0 @@ -374,15 +397,28 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): def uram_estimation(self): simd = self.get_nodeattr("SIMD") ram_style = self.get_nodeattr("ram_style") - buffer_width = simd * self.get_input_datatype().bitwidth() - buffer_depth = self.get_buffer_depth() + impl_style = self.select_impl_style() + [k_h, k_w] = self.get_nodeattr("ConvKernelDim") + [ifm_dim_h, ifm_dim_w] = self.get_nodeattr("IFMDim") + [dilation_h, dilation_w] = self.get_nodeattr("Dilation") if ram_style == "ultra": + buffer_width = simd * self.get_input_datatype().bitwidth() + if impl_style == "default": + buffer_depth = self.get_buffer_depth() + buffer_count = 1 + elif impl_style == "parallel": + if ifm_dim_h == 1 or ifm_dim_w == 1: + return 0 # 1D case (no line buffers needed) + kernel_width = (k_w - 1) * dilation_w + 1 + buffer_depth = (ifm_dim_w - kernel_width) + ifm_dim_w * (dilation_h - 1) + buffer_count = k_h - 1 + ram_depth = 4096 ram_width = 72 ram_cascade_depth = math.ceil(buffer_depth / ram_depth) ram_cascade_width = math.ceil(buffer_width / ram_width) - return int(ram_cascade_depth * ram_cascade_width) + return int(ram_cascade_depth * ram_cascade_width * buffer_count) else: return 0 @@ -457,8 +493,8 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): shape doesn't match expected shape (1, ofm_dim_h, ofm_dim_w, k_h*k_w*ifm_ch).""" def prepare_codegen_default(self): - # Default implementation style for MMV_out = 1: addressable cyclic buffer - # Computing incremental addressing scheme directly.. + """Fills code generation dict for the default implementation style by computing + the incremental addressing scheme for the circular buffer.""" if self.get_nodeattr("dynamic_mode"): template_select = "/finn-rtllib/swg/swg_template_default_dynamic.sv" else: @@ -528,13 +564,13 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): ) addr_incr_end_simd = -buffer_min_size + (channel_factor + 1) - # sanity check + # sanity check for wrap logic assert not ( abs(addr_incr_end_window) > buffer_actual_size - ), "ERROR: W increment > buffer size, wrap logic doesn't account for this" + ), "ERROR: W increment > buffer size, try setting parallel_window=1" assert not ( abs(addr_incr_end_row) > buffer_actual_size - ), "ERROR: H increment > buffer size, wrap logic doesn't account for this" + ), "ERROR: H increment > buffer size, try setting parallel_window=1" # set certain threshold indices to detect when reading/writing finishes code_gen_dict["$LAST_READ_ELEM$"] = [str(h * w * channel_factor - 1)] @@ -639,7 +675,275 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): return template_path, code_gen_dict + def prepare_codegen_parallel(self): + """Fills code generation dict for the parallel implementation style by computing + the loop controller configuration and partitioning the fixed buffer into + shift-registers (for parallel read access) and line buffers (for efficient + LUTRAM/BRAM/URAM implementation).""" + template_path = ( + os.environ["FINN_ROOT"] + "/finn-rtllib/swg/swg_template_parallel.sv" + ) + code_gen_dict = {} + + ifm_ch = self.get_nodeattr("IFMChannels") + k = self.get_nodeattr("ConvKernelDim") + ifm_dim = self.get_nodeattr("IFMDim") + stride = self.get_nodeattr("Stride") + dilation = self.get_nodeattr("Dilation") + simd = self.get_nodeattr("SIMD") + M = self.get_nodeattr("M") + + k_h, k_w = k + h, w = ifm_dim + pad = [0, 0, 0, 0] # padding happens in separate padding node for now + stride_h, stride_w = stride + dilation_h, dilation_w = dilation + pad_h = pad[0] + pad[2] + pad_w = pad[1] + pad[3] + out_dim_h = im2col.compute_conv_output_dim(h, k_h, stride_h, pad_h, dilation_h) + out_dim_w = im2col.compute_conv_output_dim(w, k_w, stride_w, pad_w, dilation_w) + mmv_in = M * 1 + mmv_out = M * k_h * k_w + channel_factor = int(ifm_ch / simd) + + # compute minimal buffer length (assuming it holds 1 complete window) + buffer_min_size = ( + (k_h - 1) * dilation_h * w + (k_w - 1) * dilation_w + 1 + ) * channel_factor + + buffer_actual_size = self.get_buffer_depth() + code_gen_dict["$BUF_ELEM_TOTAL$"] = [str(buffer_actual_size)] + + # compute some intermediate values, e.g., kernel "width" = k_w incl. dilation + # or cols/rows that are skipped due to imperfect stride<->dim combination + kernel_width = (k_w - 1) * dilation_w + 1 + kernel_height = (k_h - 1) * dilation_h + 1 + skip_columns = w % (kernel_width + (out_dim_w - 1) * stride_w) + skip_rows = h % (kernel_height + (out_dim_h - 1) * stride_h) + + # set certain threshold indices to detect when reading/writing finishes + code_gen_dict["$LAST_READ_ELEM$"] = [str(h * w * channel_factor - 1)] + code_gen_dict["$LAST_WRITE_ELEM$"] = [ + str(((h - skip_rows - 1) * w + (w - skip_columns)) * channel_factor - 1) + ] + + # re-use default controller loop structure + code_gen_dict["$IS_DEPTHWISE$"] = ["0"] + loop_h_iterations = out_dim_h + loop_w_iterations = out_dim_w # now the innermost loop + loop_kh_iterations = 1 + loop_kw_iterations = 1 + loop_simd_iterations = 1 + + if loop_w_iterations == 1: + code_gen_dict["$INNERMOST_STATE$"] = ["STATE_LOOP_H"] + loop_h_iterations -= 1 # -1 because state is initial state + else: + code_gen_dict["$INNERMOST_STATE$"] = ["STATE_LOOP_W"] + loop_w_iterations -= 1 # -1 because state is initial state + + # set head and tail address increment values + addr_incr_end_window = -buffer_min_size + stride_w * channel_factor + 1 + addr_incr_end_row = ( + -buffer_min_size + + ((skip_columns + kernel_width) * channel_factor) # remaining line + + ((stride_h - 1) * w * channel_factor) # skip lines + + 1 + ) + + tail_incr_w = addr_incr_end_window + buffer_min_size - 1 + tail_incr_h = addr_incr_end_row + buffer_min_size - 1 + tail_incr_last_window = stride_w + + addr_incr_end_simd = 1 + addr_incr_end_window_elem = 1 + addr_incr_end_window_row = 1 + addr_incr_end_window = tail_incr_w + addr_incr_end_row = tail_incr_h + + # add init value for CURRENT_ELEM counter = last elem of first window + code_gen_dict["$FIRST_WRITE_ELEM$"] = [str(buffer_min_size - 1)] + + cntr_bitwidth = math.ceil( + math.log2( + max( + loop_h_iterations - 2 + 1, + loop_w_iterations - 2 + 1, + loop_kh_iterations - 2 + 1, + loop_kw_iterations - 2 + 1, + loop_simd_iterations - 2 + 1, + ) + ) + ) + code_gen_dict["$CNTR_BITWIDTH$"] = [str(cntr_bitwidth)] + code_gen_dict["$LOOP_H_ITERATIONS$"] = [str(loop_h_iterations - 2)] + code_gen_dict["$LOOP_W_ITERATIONS$"] = [str(loop_w_iterations - 2)] + code_gen_dict["$LOOP_KH_ITERATIONS$"] = [str(loop_kh_iterations - 2)] + code_gen_dict["$LOOP_KW_ITERATIONS$"] = [str(loop_kw_iterations - 2)] + code_gen_dict["$LOOP_SIMD_ITERATIONS$"] = [str(loop_simd_iterations - 2)] + + incr_bitwidth = 1 + math.ceil( + math.log2( + max( + abs(addr_incr_end_simd) + 1, + abs(addr_incr_end_window_elem) + 1, + abs(addr_incr_end_window_row) + 1, + abs(addr_incr_end_window) + 1, + abs(addr_incr_end_row) + 1, + abs(tail_incr_w) + 1, + abs(tail_incr_h) + 1, + abs(tail_incr_last_window) + 1, + ) + ) + ) + code_gen_dict["$INCR_BITWIDTH$"] = [str(incr_bitwidth)] + code_gen_dict["$HEAD_INCR_SIMD$"] = [str(addr_incr_end_simd)] + code_gen_dict["$HEAD_INCR_KW$"] = [str(addr_incr_end_window_elem)] + code_gen_dict["$HEAD_INCR_KH$"] = [str(addr_incr_end_window_row)] + code_gen_dict["$HEAD_INCR_W$"] = [str(addr_incr_end_window)] + code_gen_dict["$HEAD_INCR_H$"] = [str(addr_incr_end_row)] + code_gen_dict["$TAIL_INCR_W$"] = [str(tail_incr_w)] + code_gen_dict["$TAIL_INCR_H$"] = [str(tail_incr_h)] + code_gen_dict["$TAIL_INCR_LAST$"] = [str(tail_incr_last_window)] + + code_gen_dict["$SIMD$"] = [str(simd)] + code_gen_dict["$MMV_IN$"] = [str(mmv_in)] + code_gen_dict["$MMV_OUT$"] = [str(mmv_out)] + + # prepare buffer partitioning into "reg_fifos" and "bram_fifos" + # use normalized ([H,W]=[1,W]) dimensions for 1D case + ( + ifm_ch, + [ifm_dim_h, ifm_dim_w], + [ofm_dim_h, ofm_dim_w], + [k_h, k_w], + [stride_h, stride_w], + [dilation_h, dilation_w], + ) = self.get_1d_conv_attrs_normalized() + + reg_fifos = [] + bram_fifos_depth = [] + + px_idx = 0 + for ky in range(k_h): + reg_fifo = [] + for kx in range(k_w): + reg_fifo.append(px_idx) + px_idx += 1 + if kx < (k_w - 1): + reg_fifo.extend([-1] * (dilation_w - 1)) + px_idx += dilation_w - 1 + reg_fifos.append(reg_fifo) + + if ky < (k_h - 1): + line_buffer_len = (w - kernel_width) + w * (dilation_h - 1) + bram_fifos_depth.append(line_buffer_len) + px_idx += line_buffer_len + + code_gen_dict["$GENERATE_REG_FIFOS$"] = [] + for i, reg_fifo in enumerate(reg_fifos): + code_gen_dict["$GENERATE_REG_FIFOS$"].append( + """ + wire [IN_WIDTH-1:0] reg_fifo_{id}_in; + wire [IN_WIDTH-1:0] reg_fifo_{id}_out; + wire [IN_WIDTH*{len}-1:0] reg_fifo_{id}; + swg_reg_buffer + #( + .WIDTH(IN_WIDTH), + .DEPTH({len}) + ) + reg_buffer_inst_{id} + ( + .clk(clk), + .shift_enable(shift_enable), + .shift_in(reg_fifo_{id}_in), + .shift_out(reg_fifo_{id}_out), + .data_out(reg_fifo_{id}) + );""".format( + id=i, + len=len(reg_fifo), + ) + ) + + code_gen_dict["$GENERATE_BRAM_FIFOS$"] = [] + for i, bram_fifo_depth in enumerate(bram_fifos_depth): + code_gen_dict["$GENERATE_BRAM_FIFOS$"].append( + """ + wire [IN_WIDTH-1:0] bram_fifo_{id}_in; + wire [IN_WIDTH-1:0] bram_fifo_{id}_out; + swg_ram_buffer + #( + .WIDTH(IN_WIDTH), + .DEPTH({len}), + .RAM_STYLE("{ram_style}") + ) + ram_buffer_inst_{id} + ( + .clk(clk), + .rst_n(rst_n), + .shift_enable(shift_enable), + .shift_in(bram_fifo_{id}_in), + .shift_out(bram_fifo_{id}_out) + );""".format( + id=i, + len=bram_fifo_depth, + ram_style=self.get_nodeattr("ram_style"), + ) + ) + + code_gen_dict["$GENERATE_OUTPUT_MAPPING$"] = [] + out_idx = mmv_out - 1 + for fifo_id, reg_fifo in enumerate(reg_fifos): + for fifo_idx, access_idx in enumerate(reg_fifo): + if access_idx != -1: + code_gen_dict["$GENERATE_OUTPUT_MAPPING$"].append( + """assign data_out[OUT_ELEM_WIDTH*{out_idx}+:OUT_ELEM_WIDTH] + = reg_fifo_{fifo_id}[{access_idx}*{mmv}*OUT_ELEM_WIDTH+ + OUT_ELEM_WIDTH*{mmv_idx}+:OUT_ELEM_WIDTH];""".format( + out_idx=out_idx, + fifo_id=fifo_id, + access_idx=len(reg_fifo) + - 1 + - int((max(reg_fifo) - access_idx) / M), + mmv_idx=(max(reg_fifo) - access_idx) % M, + mmv=M, + ) + ) + # reversal: out_idx=0 -> oldest buffer element -> highest access_idx + out_idx = out_idx - 1 + assert out_idx == -1, "ERROR: Not all output vector elements connected" + + code_gen_dict["$GENERATE_BUFFER_CONNECTION$"] = [] + for i in range(len(reg_fifos)): + if i == 0: + # first FIFO containing newest elements -> input comes from input reg + code_gen_dict["$GENERATE_BUFFER_CONNECTION$"].append( + """assign reg_fifo_{fifo_id}_in = data_in;""".format( + fifo_id=i, + ) + ) + else: + # other REG FIFOs -> input comes from connected BRAM FIFO (line buffer) + input_fifo_id = i - 1 + code_gen_dict["$GENERATE_BUFFER_CONNECTION$"].append( + """assign reg_fifo_{fifo_id}_in = bram_fifo_{input_fifo_id}_out; + """.format( + fifo_id=i, input_fifo_id=input_fifo_id + ) + ) + for i in range(len(bram_fifos_depth)): + input_fifo_id = i + code_gen_dict["$GENERATE_BUFFER_CONNECTION$"].append( + """assign bram_fifo_{fifo_id}_in = reg_fifo_{input_fifo_id}_out; + """.format( + fifo_id=i, input_fifo_id=input_fifo_id + ) + ) + + return template_path, code_gen_dict + def select_impl_style(self): + """Selects implementation style based on folding configuration.""" simd = self.get_nodeattr("SIMD") M = self.get_nodeattr("M") ifm_ch = self.get_nodeattr("IFMChannels") @@ -685,17 +989,20 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): else: impl_style = "default" - assert ( - impl_style == "default" - ), "ERROR: Parallel window mode not yet implemented" return impl_style def generate_hdl(self): + """Generates HDL code and wrapper for the IP, depending on required + implementation style.""" impl_style = self.select_impl_style() # prepare code generation by filling out dictionaries if impl_style == "default": template_path, code_gen_dict = self.prepare_codegen_default() + elif impl_style == "parallel": + template_path, code_gen_dict = self.prepare_codegen_parallel() + if self.get_nodeattr("dynamic_mode"): + raise Exception("Dynamic mode is not compatible with parallel_window") else: raise Exception("Requested impl. style not implemented") @@ -706,10 +1013,7 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): self.set_nodeattr("gen_top_module", self.get_verilog_top_module_name()) code_gen_dict["$BIT_WIDTH$"] = [str(self.get_input_datatype().bitwidth())] ram_style = self.get_nodeattr("ram_style") - if ram_style == "auto": - code_gen_dict["$RAM_STYLE$"] = [""] - else: - code_gen_dict["$RAM_STYLE$"] = ['(* ram_style = "{}" *)'.format(ram_style)] + code_gen_dict["$RAM_STYLE$"] = ['"{}"'.format(ram_style)] # apply code generation to templates code_gen_dir = self.get_nodeattr("code_gen_dir_ipgen") @@ -756,6 +1060,14 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): ) as f: f.write(template_axilite) + # Copy static source file for common core components + shutil.copy2( + os.environ["FINN_ROOT"] + "/finn-rtllib/swg/swg_common.sv", code_gen_dir + ) + shutil.copy2( + os.environ["FINN_ROOT"] + "/finn-rtllib/swg/swg_pkg.sv", code_gen_dir + ) + # set ipgen_path and ip_path so that HLS-Synth transformation # and stich_ip transformation do not complain self.set_nodeattr("ipgen_path", code_gen_dir) @@ -773,8 +1085,10 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): code_gen_dir = self.get_nodeattr("code_gen_dir_ipgen") verilog_paths = [code_gen_dir] verilog_files = [ + "swg_pkg.sv", self.get_nodeattr("gen_top_module") + "_wrapper.v", self.get_nodeattr("gen_top_module") + "_impl.sv", + "swg_common.sv", ] if self.get_nodeattr("dynamic_mode"): verilog_files.append(self.get_nodeattr("gen_top_module") + "_axilite.v") @@ -796,8 +1110,10 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): code_gen_dir = self.get_nodeattr("code_gen_dir_ipgen") sourcefiles = [ + "swg_pkg.sv", self.get_nodeattr("gen_top_module") + "_wrapper.v", self.get_nodeattr("gen_top_module") + "_impl.sv", + "swg_common.sv", ] if self.get_nodeattr("dynamic_mode"): @@ -835,6 +1151,8 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): apply (e.g. component must be synthesized for largest buffer size).""" # NOTE: For better driver integration, this functionality could be packaged # as a standalone function in the future + if self.select_impl_style() != "default": + raise Exception("Impl. style is incompatible with dynamic mode") if ifm_dim is None: ifm_dim = self.get_nodeattr("IFMDim") @@ -887,44 +1205,53 @@ class ConvolutionInputGenerator_rtl(HLSCustomOp): return config def code_generation_ipgen(self, model, fpgapart, clk): - """Normally: Generates C++ code and tcl script for IP generation. - Here: Generates (System-)Verilog code for IP generation.""" + """Generates (System-)Verilog code for IP generation (instead of HLS code).""" self.generate_hdl() def ipgen_singlenode_code(self): - """Normally: Builds the bash script for IP generation.""" + """Not implemented (RTL component).""" pass def code_generation_cppsim(self, model): - """Normally: Generates C++ code for simulation (cppsim).""" + """Not implemented (RTL component).""" pass def compile_singlenode_code(self): + """Not implemented (RTL component).""" pass def global_includes(self): + """Not implemented (RTL component).""" pass def defines(self, var): + """Not implemented (RTL component).""" pass def read_npy_data(self): + """Not implemented (RTL component).""" pass def strm_decl(self): + """Not implemented (RTL component).""" pass def docompute(self): + """Not implemented (RTL component).""" pass def dataoutstrm(self): + """Not implemented (RTL component).""" pass def save_as_npy(self): + """Not implemented (RTL component).""" pass def blackboxfunction(self): + """Not implemented (RTL component).""" pass def pragmas(self): + """Not implemented (RTL component).""" pass diff --git a/src/finn/custom_op/fpgadataflow/downsampler.py b/src/finn/custom_op/fpgadataflow/downsampler.py index b7efaff440dd5cc2160fbfb8050b30924460ffe6..255606ee7f1998586c2b357904bd32b9a5590c96 100644 --- a/src/finn/custom_op/fpgadataflow/downsampler.py +++ b/src/finn/custom_op/fpgadataflow/downsampler.py @@ -39,8 +39,8 @@ class DownSampler(HLSCustomOp): """Corresponds to finn-hlslib ConvolutionInputGenerator_*_kernel1 function. Basically performs a down sampling of the image removing rows and columns.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/duplicatestreams_batch.py b/src/finn/custom_op/fpgadataflow/duplicatestreams_batch.py index 93cde15ca7d42dbed12417837916359fdcc71b67..312f5e7e4a799d75aa0b9b7cd82b83c1b0e51dd9 100644 --- a/src/finn/custom_op/fpgadataflow/duplicatestreams_batch.py +++ b/src/finn/custom_op/fpgadataflow/duplicatestreams_batch.py @@ -38,8 +38,8 @@ from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy class DuplicateStreams_Batch(HLSCustomOp): """Class that corresponds to finn-hlslib function of the same name.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/eltwise.py b/src/finn/custom_op/fpgadataflow/eltwise.py index 68ed6546c741277bd8e962b6e80eda083cedba9c..c96f12f06bb1104152cecc6f5c6cdf5c0cc215f1 100644 --- a/src/finn/custom_op/fpgadataflow/eltwise.py +++ b/src/finn/custom_op/fpgadataflow/eltwise.py @@ -38,8 +38,8 @@ from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy class StreamingEltwise(HLSCustomOp): """Class that corresponds to finn-hlslib StreamingEltwise function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): diff --git a/src/finn/custom_op/fpgadataflow/fmpadding_batch.py b/src/finn/custom_op/fpgadataflow/fmpadding_batch.py index dfc55d283fa664e3b60fc7c4d5a056f53a119292..bdb5775c3eea84b09297025501f0116438b09ae7 100644 --- a/src/finn/custom_op/fpgadataflow/fmpadding_batch.py +++ b/src/finn/custom_op/fpgadataflow/fmpadding_batch.py @@ -39,8 +39,8 @@ class FMPadding_Batch(HLSCustomOp): """Corresponds to finn-hlslib FMPadding_Batch function. Pads input image by given amount.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/fmpadding_rtl.py b/src/finn/custom_op/fpgadataflow/fmpadding_rtl.py index 5650d218857a7c7ff86c15ac057c4ebbc18df5ca..9c2750322433627678d098c399b7a932eeac398d 100644 --- a/src/finn/custom_op/fpgadataflow/fmpadding_rtl.py +++ b/src/finn/custom_op/fpgadataflow/fmpadding_rtl.py @@ -49,8 +49,8 @@ class FMPadding_rtl(HLSCustomOp): Supports adjusting the padding amount and spatial feature sizes at runtime.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/globalaccpool_batch.py b/src/finn/custom_op/fpgadataflow/globalaccpool_batch.py index e7fa5bc0048b54a32ebc61482b96009fa019809e..220856922c1ed805ccfa60213dc0cf32f45573a1 100644 --- a/src/finn/custom_op/fpgadataflow/globalaccpool_batch.py +++ b/src/finn/custom_op/fpgadataflow/globalaccpool_batch.py @@ -38,8 +38,8 @@ from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy class GlobalAccPool_Batch(HLSCustomOp): """Class that corresponds to finn-hlslib AccPool_Batch function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/hlscustomop.py b/src/finn/custom_op/fpgadataflow/hlscustomop.py index d1326607aa0dc5c34eef105b2ceb8ed86c1a0458..d5d0c9ea6e77395d95b2f1a3b2b6ff0412d2a553 100644 --- a/src/finn/custom_op/fpgadataflow/hlscustomop.py +++ b/src/finn/custom_op/fpgadataflow/hlscustomop.py @@ -59,8 +59,8 @@ class HLSCustomOp(CustomOp): custom node should have. Some as abstract methods, these have to be filled when writing a new fpgadataflow custom op node.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) self.code_gen_dict = {} diff --git a/src/finn/custom_op/fpgadataflow/iodma.py b/src/finn/custom_op/fpgadataflow/iodma.py index 65683079fc6a648de31148e398ea498f38b8d3d9..8a756b630ddbd25d5740f0e46297a4ae6f686d2b 100644 --- a/src/finn/custom_op/fpgadataflow/iodma.py +++ b/src/finn/custom_op/fpgadataflow/iodma.py @@ -75,8 +75,8 @@ from finn.custom_op.fpgadataflow.hlscustomop import HLSCustomOp class IODMA(HLSCustomOp): """Class that corresponds to finn-hlslib DMA function(s).""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/labelselect_batch.py b/src/finn/custom_op/fpgadataflow/labelselect_batch.py index 03f89bd7ecac69a9097f4f35c42bd528be709515..492cd0107321f3abbfe02d5e456ee3732da982d0 100644 --- a/src/finn/custom_op/fpgadataflow/labelselect_batch.py +++ b/src/finn/custom_op/fpgadataflow/labelselect_batch.py @@ -39,8 +39,8 @@ from finn.util.data_packing import npy_to_rtlsim_input, rtlsim_output_to_npy class LabelSelect_Batch(HLSCustomOp): """Class that corresponds to finn-hlslib LabelSelect_Batch function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) odt_name = self.get_nodeattr("outputDataType") if odt_name == "": # If not provided compute min size diff --git a/src/finn/custom_op/fpgadataflow/lookup.py b/src/finn/custom_op/fpgadataflow/lookup.py index fd3e2b5b1cfa74eb4f957df4b568e6c46da47617..ed560ac962477965bae39d296287c09eb077eca0 100644 --- a/src/finn/custom_op/fpgadataflow/lookup.py +++ b/src/finn/custom_op/fpgadataflow/lookup.py @@ -44,8 +44,8 @@ from finn.util.data_packing import ( class Lookup(HLSCustomOp): "Streaming elementwise HLS lookup, mapping indices to values." - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/matrixvectoractivation.py b/src/finn/custom_op/fpgadataflow/matrixvectoractivation.py index 72128fda4cfe23db4858fe3ffe80a755733954cc..899bce98d2dd9572d1adf2f20910a0463f9d5994 100644 --- a/src/finn/custom_op/fpgadataflow/matrixvectoractivation.py +++ b/src/finn/custom_op/fpgadataflow/matrixvectoractivation.py @@ -46,8 +46,6 @@ from finn.util.data_packing import ( rtlsim_output_to_npy, ) -from . import templates - # ONNX i/o tensor shape assumptions for MatrixVectorActivation: # input 0 is the input tensor, shape (.., i_size) = (..., MW) # input 1 is the weight tensor, shape (i_size, o_size) = (MW, MH) @@ -60,9 +58,8 @@ class MatrixVectorActivation(HLSCustomOp): """Class that corresponds to finn-hls Matrix_Vector_Activate(_Stream)_Batch function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) - self.decoupled_wrapper = templates.decoupled_wrapper + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { @@ -350,13 +347,23 @@ class MatrixVectorActivation(HLSCustomOp): # adder tree addertree_luts = (W + A) * (2 * Q - 1) # accumulator - acc_bits = W + A + np.ceil(math.log(MW, 2)) + acc_datatype = self.get_accumulator_datatype() + # if accDataType is not set, then it will default to INT32, which would + # be a large overestimate in most (if not all) cases. In this scenario, + # we would use the minimum accumulator as determined by the data types + # bound, derived in https://arxiv.org/abs/2301.13376 + alpha = math.log(MW, 2) + W + A - 1 - int(idt.signed()) + acc_bits = min( + acc_datatype.bitwidth(), + np.ceil(alpha + math.log(1 + pow(2, -alpha), 2) + 1), + ) acc_luts = acc_bits # thresholds and threshold comparators thr_luts = 0 comp_luts = 0 noact = self.get_nodeattr("noActivation") - if noact == 0: + tmem_style = self.get_nodeattr("ram_style_thresholds") + if (noact == 0) and (tmem_style == "distributed"): odt = self.get_output_datatype() B = odt.bitwidth() thr_luts = (2**B - 1) * acc_bits * math.ceil(self.calc_tmem() / 64) @@ -405,6 +412,10 @@ class MatrixVectorActivation(HLSCustomOp): else: raise Exception("Undefined input ind for this layer type") + def get_accumulator_datatype(self): + """Returns FINN DataType of accumulator""" + return DataType[self.get_nodeattr("accDataType")] + def get_weight_datatype(self): """Returns FINN DataType of weights.""" return DataType[self.get_nodeattr("weightDataType")] @@ -575,6 +586,8 @@ class MatrixVectorActivation(HLSCustomOp): return ret def minimize_accumulator_width(self, model): + """Minimize the accumulator bit width according to the weight values, + input data types, and size of dot product""" weights = model.get_initializer(self.onnx_node.input[1]) # since in the calculation the values of the weight matrix are used, # for the bipolar case they need to be converted to bipolar @@ -585,8 +598,23 @@ class MatrixVectorActivation(HLSCustomOp): else: thresholds = None idt = self.get_input_datatype() - # calculate minimum and maximum values of accumulator - (acc_min, acc_max) = calculate_matvec_accumulator_range(weights, idt) + # if runtime-writeable weights, then the values of the weights can + # change and we need to use the worst-case values from the datatypes + if self.get_nodeattr("runtime_writeable_weights"): + wdt = self.get_weight_datatype() + lower_worst = wdt.min() * np.ones_like(weights) + lower_range = calculate_matvec_accumulator_range(lower_worst, idt) + upper_worst = wdt.max() * np.ones_like(weights) + upper_range = calculate_matvec_accumulator_range(upper_worst, idt) + acc_min = min(min(lower_range), min(upper_range)) + acc_max = max(max(upper_range), max(upper_range)) + # if not runtime-writeable weights, then we can calculate the min + # and max values of the accumulation range using knowledge of the + # weights and input data types since they are fixed + else: + (acc_min, acc_max) = calculate_matvec_accumulator_range(weights, idt) + # if the thresholds can be used to determine range, then adjust the range + # according to the known values of the thresholds if thresholds is not None: threshold_tensor = self.get_hls_compatible_threshold_tensor(thresholds) # set threshold datatype (and accumulator datatype implicitly) @@ -622,7 +650,7 @@ class MatrixVectorActivation(HLSCustomOp): self.onnx_node.name, str(tdt), ) - self.set_nodeattr("accDataType", tdt.name) + adt = tdt # Set activation datatype to the threshold datatype else: if acc_min < 0: if abs(acc_min) > acc_max: @@ -631,15 +659,33 @@ class MatrixVectorActivation(HLSCustomOp): adt = DataType.get_smallest_possible(-acc_max - 1) else: adt = DataType.get_smallest_possible(acc_max) - # ensure a datatype divisible by 8-bits in case this is the last node + # if this is the last node in the graph, then ensure the datatype is + # divisibly by 8 bits + if model.find_direct_successors(self.onnx_node) is None: bw = roundup_to_integer_multiple(adt.bitwidth(), 8) new_adt_name = adt.name.replace(str(adt.bitwidth()), str(bw)) adt = DataType[new_adt_name] - self.set_nodeattr("accDataType", adt.name) # for no-activation nodes, output dt = acc dt self.set_nodeattr("outputDataType", adt.name) + self.set_nodeattr("accDataType", adt.name) return DataType[self.get_nodeattr("accDataType")] + def minimize_weight_bit_width(self, model): + """Minimize the bit width based on the values of the weights""" + if not self.get_nodeattr("runtime_writeable_weights"): + weights = model.get_initializer(self.onnx_node.input[1]) + w_min = weights.min() + w_max = weights.max() + if w_min < 0: + if abs(w_min) > w_max: + wdt = DataType.get_smallest_possible(w_min) + else: + wdt = DataType.get_smallest_possible(-w_max - 1) + else: + wdt = DataType.get_smallest_possible(w_max) + self.set_nodeattr("weightDataType", wdt.name) + return DataType[self.get_nodeattr("weightDataType")] + def get_hls_compatible_threshold_tensor(self, orig_thres_matrix): """Convert the original numpy weight matrix orig_weight_matrix into a form suitable for passing to the hlslib call: @@ -671,13 +717,6 @@ class MatrixVectorActivation(HLSCustomOp): # ensure all thresholds are integer assert (orig_thres_matrix.astype(np.int32) == orig_thres_matrix).all() ret = orig_thres_matrix - # workaround for vivado_hls threshold bug - if ret[0][0] == 0 and n_thres_steps == 1: - ret = np.copy(ret) - ret[0][0] = 1 - warnings.warn( - "Setting 0-valued first threshold to 1 to avoid vivado_hls bug" - ) # ensure channels = mh , duplicating if necessary if ret.shape[0] == 1: ret = np.tile(ret, (mh, 1)) @@ -825,28 +864,10 @@ class MatrixVectorActivation(HLSCustomOp): self.make_weight_file(weights, "decoupled_npy", weight_filename_sim) if mem_mode == "decoupled": # also save weights as Verilog .dat file - # note that we provide two different .dat files, one for synth - # and one for synthesis. this is because URAM-based weights always - # need zero weights for synthesis, otherwise they get inferred - # as BRAM - weight_filename_rtl_synth = "{}/memblock_synth_0.dat".format( - code_gen_dir - ) - weight_filename_rtl_sim = "{}/memblock_sim_0.dat".format(code_gen_dir) - # sim weights are always the true weights - self.make_weight_file( - weights, "decoupled_verilog_dat", weight_filename_rtl_sim - ) - ram_style = self.get_nodeattr("ram_style") - if ram_style == "ultra": - # UltraRAM must have no memory initializer, or only zeroes - # otherwise BRAM will be inferred instead of URAM - # as a workaround we provide a zero-weight init here - synth_weights = np.zeros_like(weights, dtype=np.float32) - else: - synth_weights = weights + # This file will be ignored when synthesizing UltraScale memory. + weight_filename_rtl = "{}/memblock.dat".format(code_gen_dir) self.make_weight_file( - synth_weights, "decoupled_verilog_dat", weight_filename_rtl_synth + weights, "decoupled_verilog_dat", weight_filename_rtl ) else: raise Exception( @@ -1337,7 +1358,7 @@ class MatrixVectorActivation(HLSCustomOp): % (self.get_nodeattr("ip_vlnv"), node_name, node_name) ) # instantiate a streamer and connect it to the HLS IP - strm_vlnv = "xilinx.com:user:memstream:1.0" + strm_vlnv = "amd.com:finn:memstream:1.0" strm_inst = node_name + "_wstrm" cmd.append( "create_bd_cell -type ip -vlnv %s /%s/%s" @@ -1345,22 +1366,16 @@ class MatrixVectorActivation(HLSCustomOp): ) cmd.append( "set_property -dict [list " - "CONFIG.NSTREAMS {1} " - "CONFIG.MEM_DEPTH {%d} " - "CONFIG.MEM_WIDTH {%d} " - "CONFIG.MEM_INIT {%s} " + "CONFIG.DEPTH {%d} " + "CONFIG.WIDTH {%d} " + "CONFIG.INIT_FILE {%s} " "CONFIG.RAM_STYLE {%s} " - "CONFIG.STRM0_DEPTH {%d} " - "CONFIG.STRM0_WIDTH {%d} " - "CONFIG.STRM0_OFFSET {0} " "] [get_bd_cells /%s/%s]" % ( self.calc_wmem(), self.get_weightstream_width_padded(), - self.get_nodeattr("code_gen_dir_ipgen") + "/", + self.get_nodeattr("code_gen_dir_ipgen") + "/memblock.dat", self.get_nodeattr("ram_style"), - self.calc_wmem(), - self.get_weightstream_width_padded(), node_name, strm_inst, ) @@ -1371,11 +1386,11 @@ class MatrixVectorActivation(HLSCustomOp): % (node_name, strm_inst, node_name, node_name, sname) ) cmd.append( - "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/aresetn]" + "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/ap_rst_n]" % (node_name, rst_name, node_name, strm_inst) ) cmd.append( - "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/aclk]" + "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/ap_clk]" % (node_name, clk_name, node_name, strm_inst) ) cmd.append( diff --git a/src/finn/custom_op/fpgadataflow/streamingfifo.py b/src/finn/custom_op/fpgadataflow/streamingfifo.py index 522305327ff7c5f1356aad4fdf6b9e0a942eca72..34b1940fa1aa8e6c94d1a24cb069eb3d1a432274 100644 --- a/src/finn/custom_op/fpgadataflow/streamingfifo.py +++ b/src/finn/custom_op/fpgadataflow/streamingfifo.py @@ -41,8 +41,8 @@ from . import templates class StreamingFIFO(HLSCustomOp): - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) self.strm_fifo_wrapper = templates.strm_fifo_wrapper def get_nodeattr_types(self): diff --git a/src/finn/custom_op/fpgadataflow/templates.py b/src/finn/custom_op/fpgadataflow/templates.py index c7bbc3f139b64f57943b2b099083a9611951e9c4..4e03e6daf9aee9aee2318ac1bc840b6387a40ccd 100644 --- a/src/finn/custom_op/fpgadataflow/templates.py +++ b/src/finn/custom_op/fpgadataflow/templates.py @@ -109,107 +109,6 @@ export_design -format ip_catalog exit 0 """ -# verilog wrapper for decoupled mem mode -decoupled_wrapper = """ -module $TOPNAME$( -ap_clk, -ap_rst_n, -in0_$HLS_SNAME$_TDATA, -in0_$HLS_SNAME$_TVALID, -in0_$HLS_SNAME$_TREADY, -out_$HLS_SNAME$_TDATA, -out_$HLS_SNAME$_TVALID, -out_$HLS_SNAME$_TREADY -); - -input ap_clk; -input ap_rst_n; -input $IN_RANGE$ in0_$HLS_SNAME$_TDATA; -input in0_$HLS_SNAME$_TVALID; -output in0_$HLS_SNAME$_TREADY; -output $OUT_RANGE$ out_$HLS_SNAME$_TDATA; -output out_$HLS_SNAME$_TVALID; -input out_$HLS_SNAME$_TREADY; - -reg [31:0] config_address = 0; -reg config_ce = 0; -reg config_we = 0; -reg [31:0] config_d0 = 0; -wire [31:0] config_q0; - -//multiple wire AXI Streams -wire m_axis_0_afull; -// FIFO count to generate programmable full -wire [5:0] fifo_0_count; -wire m_axis_0_tready; -wire m_axis_0_tvalid; -wire $WEIGHT_RANGE$ m_axis_0_tdata; - -//memstream component - -memstream -#( -//parameters to enable/disable axi-mm, set number of streams, set readmemh for -// memory, set per-stream offsets in memory, set per-stream widths -.CONFIG_EN(1), -.NSTREAMS(1), -.MEM_DEPTH($MEM_DEPTH$), -.MEM_WIDTH($WEIGHT_WIDTH$), -.MEM_INIT("./"), -.RAM_STYLE("$RAM_STYLE$"), - -//widths per stream -.STRM0_WIDTH($WEIGHT_WIDTH$), - -//depths per stream -.STRM0_DEPTH($WSTREAM_DEPTH$), - -//offsets for each stream -.STRM0_OFFSET(0) -) -mem -( -.aclk(ap_clk), -.aresetn(ap_rst_n), - -//optional configuration interface compatible with ap_memory -.config_address(config_address), -.config_ce(config_ce), -.config_we(config_we), -.config_d0(config_d0), -.config_q0(config_q0), - -//multiple output AXI Streams, TDATA width rounded to multiple of 8 bits -.m_axis_0_afull(m_axis_0_afull), -.m_axis_0_tready(m_axis_0_tready), -.m_axis_0_tvalid(m_axis_0_tvalid), -.m_axis_0_tdata(m_axis_0_tdata) - - -); - - -//MVA_Stream_Unit - -$LAYER_NAME$ -MVA_Stream_U -( -.ap_clk(ap_clk), //input -.ap_rst_n(ap_rst_n), //input -.in0_$HLS_SNAME$_TDATA(in0_$HLS_SNAME$_TDATA), //$IN_RANGE$ input -.in0_$HLS_SNAME$_TVALID(in0_$HLS_SNAME$_TVALID), //input -.in0_$HLS_SNAME$_TREADY(in0_$HLS_SNAME$_TREADY), //output -.weights_$HLS_SNAME$_TDATA(m_axis_0_tdata), //$WEIGHT_RANGE$ input -.weights_$HLS_SNAME$_TVALID(m_axis_0_tvalid), //input -.weights_$HLS_SNAME$_TREADY(m_axis_0_tready), //output -.out_$HLS_SNAME$_TDATA(out_$HLS_SNAME$_TDATA), //$OUT_RANGE$ output -.out_$HLS_SNAME$_TVALID(out_$HLS_SNAME$_TVALID), //output -.out_$HLS_SNAME$_TREADY(out_$HLS_SNAME$_TREADY) //input -); - -endmodule -""" - ip_package_tcl = """ ## IP Info set Vendor "xilinx.com" diff --git a/src/finn/custom_op/fpgadataflow/thresholding_batch.py b/src/finn/custom_op/fpgadataflow/thresholding_batch.py index d9745acf63c4685b3369ac379abde0a6c5a3f157..12e635b3d612235f6b464e6f54d0f24011c7c907 100644 --- a/src/finn/custom_op/fpgadataflow/thresholding_batch.py +++ b/src/finn/custom_op/fpgadataflow/thresholding_batch.py @@ -45,8 +45,6 @@ from finn.util.data_packing import ( rtlsim_output_to_npy, ) -from . import templates - # ONNX i/o tensor shape assumptions for Thresholding: # input 0 is the input tensor, shape (..., NumChannels) # input 1 is the threshold tensor, shape (NumChannels, n_thres) @@ -57,9 +55,8 @@ from . import templates class Thresholding_Batch(HLSCustomOp): """Class that corresponds to finn-hls Thresholding_Batch function.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) - self.decoupled_wrapper = templates.decoupled_wrapper + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { @@ -319,13 +316,6 @@ class Thresholding_Batch(HLSCustomOp): np.mod(orig_thres_matrix, 1), 0 ).all(), "Need int threshold tensor" ret = orig_thres_matrix - # workaround for vivado_hls threshold bug - if ret[0][0] == 0 and n_thres_steps == 1: - ret = np.copy(ret) - ret[0][0] = 1 - warnings.warn( - "Setting 0-valued first threshold to 1 to avoid vivado_hls bug" - ) # ensure channels = mh , duplicating if necessary if ret.shape[0] == 1: ret = np.tile(ret, (mh, 1)) @@ -464,26 +454,10 @@ class Thresholding_Batch(HLSCustomOp): weight_filename_sim = "{}/thresholds.npy".format(code_gen_dir) self.make_weight_file(thresholds, "decoupled_npy", weight_filename_sim) # also save weights as Verilog .dat file - # note that we provide two different .dat files, one for synth - # and one for synthesis. this is because URAM-based weights always - # need zero weights for synthesis, otherwise they get inferred - # as BRAM - weight_filename_rtl_synth = "{}/memblock_synth_0.dat".format(code_gen_dir) - weight_filename_rtl_sim = "{}/memblock_sim_0.dat".format(code_gen_dir) - # sim weights are always the true weights - self.make_weight_file( - thresholds, "decoupled_verilog_dat", weight_filename_rtl_sim - ) - ram_style = self.get_nodeattr("ram_style") - if ram_style == "ultra": - # UltraRAM must have no memory initializer, or only zeroes - # otherwise BRAM will be inferred instead of URAM - # as a workaround we provide a zero-weight init here - synth_thresholds = np.zeros_like(thresholds, dtype=np.float32) - else: - synth_thresholds = thresholds + # This file will be ignored when synthesizing UltraScale memory. + weight_filename_rtl = "{}/memblock.dat".format(code_gen_dir) self.make_weight_file( - synth_thresholds, "decoupled_verilog_dat", weight_filename_rtl_synth + thresholds, "decoupled_verilog_dat", weight_filename_rtl ) else: raise Exception("Unrecognized mem_mode") @@ -850,7 +824,7 @@ class Thresholding_Batch(HLSCustomOp): % (self.get_nodeattr("ip_vlnv"), node_name, node_name) ) # instantiate a streamer and connect it to the HLS IP - strm_vlnv = "xilinx.com:user:memstream:1.0" + strm_vlnv = "amd.com:finn:memstream:1.0" strm_inst = node_name + "_wstrm" cmd.append( "create_bd_cell -type ip -vlnv %s /%s/%s" @@ -858,22 +832,16 @@ class Thresholding_Batch(HLSCustomOp): ) cmd.append( "set_property -dict [list " - "CONFIG.NSTREAMS {1} " - "CONFIG.MEM_DEPTH {%d} " - "CONFIG.MEM_WIDTH {%d} " - "CONFIG.MEM_INIT {%s} " + "CONFIG.DEPTH {%d} " + "CONFIG.WIDTH {%d} " + "CONFIG.INIT_FILE {%s} " "CONFIG.RAM_STYLE {%s} " - "CONFIG.STRM0_DEPTH {%d} " - "CONFIG.STRM0_WIDTH {%d} " - "CONFIG.STRM0_OFFSET {0} " "] [get_bd_cells /%s/%s]" % ( self.calc_tmem(), self.get_weightstream_width_padded(), - self.get_nodeattr("code_gen_dir_ipgen") + "/", + self.get_nodeattr("code_gen_dir_ipgen") + "/memblock.dat", self.get_nodeattr("ram_style"), - self.calc_tmem(), - self.get_weightstream_width_padded(), node_name, strm_inst, ) @@ -884,11 +852,11 @@ class Thresholding_Batch(HLSCustomOp): % (node_name, strm_inst, node_name, node_name, sname) ) cmd.append( - "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/aresetn]" + "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/ap_rst_n]" % (node_name, rst_name, node_name, strm_inst) ) cmd.append( - "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/aclk]" + "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/ap_clk]" % (node_name, clk_name, node_name, strm_inst) ) cmd.append( diff --git a/src/finn/custom_op/fpgadataflow/tlastmarker.py b/src/finn/custom_op/fpgadataflow/tlastmarker.py index 1bd32442a1986d6a86571e85a09322d6c15d8a78..895a2eedab51cee6322c7307ea1944d49a0dade5 100644 --- a/src/finn/custom_op/fpgadataflow/tlastmarker.py +++ b/src/finn/custom_op/fpgadataflow/tlastmarker.py @@ -37,8 +37,8 @@ class TLastMarker(HLSCustomOp): (needed by the FINN PYNQ shell) or at the beginning to remove the end-of-burst from DMA read.""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/upsampler.py b/src/finn/custom_op/fpgadataflow/upsampler.py index a018fd35aac4d63b365e97464dab0fd4a5fa13f2..b653b9386e940dd2220fa1fb0d198e63b81a356d 100644 --- a/src/finn/custom_op/fpgadataflow/upsampler.py +++ b/src/finn/custom_op/fpgadataflow/upsampler.py @@ -41,8 +41,8 @@ class UpsampleNearestNeighbour_Batch(HLSCustomOp): The layer expects square feature maps for the in and output. """ - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { diff --git a/src/finn/custom_op/fpgadataflow/vectorvectoractivation.py b/src/finn/custom_op/fpgadataflow/vectorvectoractivation.py index d5e29ca22acf89440c3c3a66101bec89d4a66d46..ede572f1a4c5fcf60476aa84beba8244eb9ec5a8 100644 --- a/src/finn/custom_op/fpgadataflow/vectorvectoractivation.py +++ b/src/finn/custom_op/fpgadataflow/vectorvectoractivation.py @@ -50,12 +50,13 @@ from finn.util.data_packing import ( class VectorVectorActivation(HLSCustomOp): """Class that corresponds to finn-hlslib Vector_Vector_Activate_Batch function""" - def __init__(self, onnx_node): - super().__init__(onnx_node) + def __init__(self, onnx_node, **kwargs): + super().__init__(onnx_node, **kwargs) def get_nodeattr_types(self): my_attrs = { "PE": ("i", True, 0), + "SIMD": ("i", False, 1), "Dim": ("ints", True, []), # [H, W] "Channels": ("i", True, 0), "Kernel": ("ints", True, []), # [H, W] @@ -104,18 +105,39 @@ class VectorVectorActivation(HLSCustomOp): return my_attrs def minimize_accumulator_width(self, model): + """Minimize the accumulator bit width according to the weight values, + input data types, and size of dot product""" weights = model.get_initializer(self.onnx_node.input[1]) k_h, k_w = self.get_nodeattr("Kernel") fm = self.get_nodeattr("Channels") # put weights into the shape expected by calculate_matvec_accumulator_range weights = weights.reshape(fm, k_h * k_w).transpose() + # since in the calculation the values of the weight matrix are used, + # for the bipolar case they need to be converted to bipolar + if self.get_nodeattr("binaryXnorMode"): + weights = 2 * weights - 1 if len(self.onnx_node.input) > 2: thresholds = model.get_initializer(self.onnx_node.input[2]) else: thresholds = None idt = self.get_input_datatype() - # calculate minimum and maximum values of accumulator - (acc_min, acc_max) = calculate_matvec_accumulator_range(weights, idt) + # if runtime-writeable weights, then the values of the weights can + # change and we need to use the worst-case values from the datatypes + if self.get_nodeattr("runtime_writeable_weights"): + wdt = self.get_weight_datatype() + lower_worst = wdt.min() * np.ones_like(weights) + lower_range = calculate_matvec_accumulator_range(lower_worst, idt) + upper_worst = wdt.max() * np.ones_like(weights) + upper_range = calculate_matvec_accumulator_range(upper_worst, idt) + acc_min = min(min(lower_range), min(upper_range)) + acc_max = max(max(upper_range), max(upper_range)) + # if not runtime-writeable weights, then we can calculate the min + # and max values of the accumulation range using knowledge of the + # weights and input data types since they are fixed + else: + (acc_min, acc_max) = calculate_matvec_accumulator_range(weights, idt) + # if the thresholds can be used to determine range, then adjust the range + # according to the known values of the thresholds if thresholds is not None: threshold_tensor = self.get_hls_compatible_threshold_tensor(thresholds) # set threshold datatype (and accumulator datatype implicitly) @@ -151,7 +173,7 @@ class VectorVectorActivation(HLSCustomOp): self.onnx_node.name, str(tdt), ) - self.set_nodeattr("accDataType", tdt.name) + adt = tdt # Set activation datatype to the threshold datatype else: if acc_min < 0: if abs(acc_min) > acc_max: @@ -160,21 +182,40 @@ class VectorVectorActivation(HLSCustomOp): adt = DataType.get_smallest_possible(-acc_max - 1) else: adt = DataType.get_smallest_possible(acc_max) - # ensure a datatype divisible by 8-bits in case this is the last node + # if this is the last node in the graph, then ensure the datatype is + # divisibly by 8 bits + if model.find_direct_successors(self.onnx_node) is None: bw = roundup_to_integer_multiple(adt.bitwidth(), 8) new_adt_name = adt.name.replace(str(adt.bitwidth()), str(bw)) adt = DataType[new_adt_name] - self.set_nodeattr("accDataType", adt.name) # for no-activation nodes, output dt = acc dt self.set_nodeattr("outputDataType", adt.name) + self.set_nodeattr("accDataType", adt.name) return DataType[self.get_nodeattr("accDataType")] + def minimize_weight_bit_width(self, model): + """Minimize the bit width based on the values of the weights""" + if not self.get_nodeattr("runtime_writeable_weights"): + weights = model.get_initializer(self.onnx_node.input[1]) + w_min = weights.min() + w_max = weights.max() + if w_min < 0: + if abs(w_min) > w_max: + wdt = DataType.get_smallest_possible(w_min) + else: + wdt = DataType.get_smallest_possible(-w_max - 1) + else: + wdt = DataType.get_smallest_possible(w_max) + self.set_nodeattr("weightDataType", wdt.name) + return DataType[self.get_nodeattr("weightDataType")] + def calc_wmem(self): """Calculates and returns WMEM.""" ch = self.get_nodeattr("Channels") k_h, k_w = self.get_nodeattr("Kernel") pe = self.get_nodeattr("PE") - wmem = k_h * k_w * ch // pe + simd = self.get_nodeattr("SIMD") + wmem = (k_h * k_w * ch // pe) // simd return wmem def calc_tmem(self): @@ -216,13 +257,19 @@ class VectorVectorActivation(HLSCustomOp): """Returns FINN DataType of weights.""" return DataType[self.get_nodeattr("weightDataType")] + def get_accumulator_datatype(self): + """Returns FINN DataType of accumulator""" + return DataType[self.get_nodeattr("accDataType")] + def get_output_datatype(self, ind=0): """Returns FINN DataType of output.""" return DataType[self.get_nodeattr("outputDataType")] def get_instream_width(self, ind=0): i_bits = self.get_input_datatype().bitwidth() - in_width = i_bits * self.get_nodeattr("PE") + simd = self.get_nodeattr("SIMD") + pe = self.get_nodeattr("PE") + in_width = i_bits * simd * pe return in_width def get_outstream_width(self, ind=0): @@ -232,15 +279,21 @@ class VectorVectorActivation(HLSCustomOp): def get_folded_input_shape(self, ind=0): k_h, k_w = self.get_nodeattr("Kernel") - sf = k_h * k_w dim_h, dim_w = self.get_nodeattr("Dim") ch = self.get_nodeattr("Channels") + simd = self.get_nodeattr("SIMD") pe = self.get_nodeattr("PE") + kernel_2 = k_h * k_w + assert ( + kernel_2 % simd == 0 + ), "Requirement kernel (k_h * k_w) divisable by SIMD is violated." + sf = kernel_2 // simd + assert ch % pe == 0, "Requirement Channels divisable by PE is violated." nf = ch // pe if ind == 0: # calculate shape of input 0 - folded_input_shape = tuple([1, dim_h, dim_w, sf * nf, pe]) + folded_input_shape = tuple([1, dim_h, dim_w, sf * nf, simd * pe]) elif ind == 1 and self.get_nodeattr("mem_mode") == "external": # calculate shape of input 1 (weights) folded_input_shape = tuple([1, sf * nf, pe]) @@ -276,6 +329,7 @@ class VectorVectorActivation(HLSCustomOp): def get_exp_cycles(self): pe = self.get_nodeattr("PE") + simd = self.get_nodeattr("SIMD") ch = self.get_nodeattr("Channels") dim_h, dim_w = self.get_nodeattr("Dim") k_h, k_w = self.get_nodeattr("Kernel") @@ -283,7 +337,7 @@ class VectorVectorActivation(HLSCustomOp): batch_size = 1 # since mmv != 1 is not supported yet, we set mmv for now to 1 mmv = 1 - exp_cycles = ((ch * k_h * k_w) / pe) * batch_size * (dim_h * dim_w) / mmv + exp_cycles = ((ch * k_h * k_w) / pe / simd) * batch_size * (dim_h * dim_w) / mmv return int(exp_cycles) def get_template_param_values(self): @@ -327,6 +381,7 @@ class VectorVectorActivation(HLSCustomOp): def get_hls_compatible_weight_tensor(self, orig_weight_matrix): pe = self.get_nodeattr("PE") + simd = self.get_nodeattr("SIMD") ch = self.get_nodeattr("Channels") k_h, k_w = self.get_nodeattr("Kernel") wmem = self.calc_wmem() @@ -338,10 +393,13 @@ class VectorVectorActivation(HLSCustomOp): ), """Weights matrix doesn't have expected shape (channels, 1, kernel_size, kernel_size)""" ret = orig_weight_matrix + if self.get_weight_datatype() == DataType["BIPOLAR"]: + # convert bipolar to binary + ret = (ret + 1) / 2 ret = ret.reshape(ch, k_h * k_w) # distribute rows between PEs ret = interleave_matrix_outer_dim_from_partitions(ret, pe) - ret = ret.reshape(1, pe, wmem, 1) + ret = ret.reshape(1, pe, wmem, simd) return ret def get_hls_compatible_threshold_tensor(self, orig_thres_matrix): @@ -375,13 +433,6 @@ class VectorVectorActivation(HLSCustomOp): # ensure all thresholds are integer assert (orig_thres_matrix.astype(np.int32) == orig_thres_matrix).all() ret = orig_thres_matrix - # workaround for vivado_hls threshold bug - if ret[0][0] == 0 and n_thres_steps == 1: - ret = np.copy(ret) - ret[0][0] = 1 - warnings.warn( - "Setting 0-valued first threshold to 1 to avoid vivado_hls bug" - ) # ensure channels = mh , duplicating if necessary if ret.shape[0] == 1: ret = np.tile(ret, (ch, 1)) @@ -432,7 +483,8 @@ class VectorVectorActivation(HLSCustomOp): f_weights = open(weight_file_name, "w") if export_wdt.bitwidth() != 1: f_weights.write( - "const FixedPointWeights<1,{},{},{}> weights = ".format( + "const FixedPointWeights<{},{},{},{}> weights = ".format( + self.get_nodeattr("SIMD"), export_wdt.get_hls_datatype_str(), self.get_nodeattr("PE"), self.calc_wmem(), @@ -440,7 +492,8 @@ class VectorVectorActivation(HLSCustomOp): ) else: f_weights.write( - "const BinaryWeights<1,{},{}> weights = ".format( + "const BinaryWeights<{},{},{}> weights = ".format( + self.get_nodeattr("SIMD"), self.get_nodeattr("PE"), self.calc_wmem(), ) @@ -457,7 +510,7 @@ class VectorVectorActivation(HLSCustomOp): weight_tensor_pe_flipped = np.flip(weight_tensor_unflipped, axis=-2) # reshape weight tensor (simd_flipped and pe_flipped) to desired shape pe = self.get_nodeattr("PE") - simd = 1 + simd = self.get_nodeattr("SIMD") # simd_flipped weight_tensor_simd_flipped = weight_tensor_simd_flipped.reshape( 1, -1, pe * simd @@ -527,28 +580,10 @@ class VectorVectorActivation(HLSCustomOp): self.make_weight_file(weights, "decoupled_npy", weight_filename_sim) if mem_mode == "decoupled": # also save weights as Verilog .dat file - # note that we provide two different .dat files, one for synth - # and one for synthesis. this is because URAM-based weights always - # need zero weights for synthesis, otherwise they get inferred - # as BRAM - weight_filename_rtl_synth = "{}/memblock_synth_0.dat".format( - code_gen_dir - ) - weight_filename_rtl_sim = "{}/memblock_sim_0.dat".format(code_gen_dir) - # sim weights are always the true weights - self.make_weight_file( - weights, "decoupled_verilog_dat", weight_filename_rtl_sim - ) - ram_style = self.get_nodeattr("ram_style") - if ram_style == "ultra": - # UltraRAM must have no memory initializer, or only zeroes - # otherwise BRAM will be inferred instead of URAM - # as a workaround we provide a zero-weight init here - synth_weights = np.zeros_like(weights, dtype=np.float32) - else: - synth_weights = weights + # This file will be ignored when synthesizing UltraScale memory. + weight_filename_rtl = "{}/memblock.dat".format(code_gen_dir) self.make_weight_file( - synth_weights, "decoupled_verilog_dat", weight_filename_rtl_synth + weights, "decoupled_verilog_dat", weight_filename_rtl ) else: raise Exception( @@ -636,6 +671,12 @@ class VectorVectorActivation(HLSCustomOp): not float32 as expected.""" expected_inp_shape = self.get_folded_input_shape() reshaped_input = context[inputs].reshape(expected_inp_shape) + if self.get_input_datatype() == DataType["BIPOLAR"]: + # store bipolar activations as binary + reshaped_input = (reshaped_input + 1) / 2 + export_idt = DataType["BINARY"] + else: + export_idt = self.get_input_datatype() # make copy before saving the array reshaped_input = reshaped_input.copy() np.save( @@ -651,14 +692,20 @@ class VectorVectorActivation(HLSCustomOp): super().exec_precompiled_singlenode_model() # load output npy file super().npy_to_dynamic_output(context) + # reinterpret binary output as bipolar where needed + if self.get_output_datatype() == DataType["BIPOLAR"]: + out = context[node.output[0]] + out = 2 * out - 1 + context[node.output[0]] = out assert ( context[node.output[0]].shape == self.get_normal_output_shape() ), "cppsim did not produce expected output shape" elif mode == "rtlsim": sim = self.get_rtlsim() nbits = self.get_instream_width() - idt = self.get_input_datatype() - inp = npy_to_rtlsim_input("{}/input_0.npy".format(code_gen_dir), idt, nbits) + inp = npy_to_rtlsim_input( + "{}/input_0.npy".format(code_gen_dir), export_idt, nbits + ) super().reset_rtlsim(sim) super().toggle_clk(sim) @@ -726,9 +773,10 @@ class VectorVectorActivation(HLSCustomOp): self.code_gen_dict["$DEFINES$"] = [ """#define Channels1 {}\n #define InnerProdDim {}\n - #define SIMD1 1\n #define PE1 {}\n #define numReps {}""".format( + #define SIMD1 {}\n #define PE1 {}\n #define numReps {}""".format( self.get_nodeattr("Channels"), innerProdDim, + self.get_nodeattr("SIMD"), self.get_nodeattr("PE"), numReps, ) @@ -742,6 +790,9 @@ class VectorVectorActivation(HLSCustomOp): def read_npy_data(self): code_gen_dir = self.get_nodeattr("code_gen_dir_cppsim") dtype = self.get_input_datatype() + if dtype == DataType["BIPOLAR"]: + # use binary for bipolar storage + dtype = DataType["BINARY"] elem_bits = dtype.bitwidth() packed_bits = self.get_instream_width() packed_hls_type = "ap_uint<%d>" % packed_bits @@ -839,6 +890,9 @@ class VectorVectorActivation(HLSCustomOp): def dataoutstrm(self): code_gen_dir = self.get_nodeattr("code_gen_dir_cppsim") dtype = self.get_output_datatype() + if dtype == DataType["BIPOLAR"]: + # use binary for bipolar storage + dtype = DataType["BINARY"] elem_bits = dtype.bitwidth() packed_bits = self.get_outstream_width() packed_hls_type = "ap_uint<%d>" % packed_bits @@ -996,7 +1050,7 @@ class VectorVectorActivation(HLSCustomOp): % (self.get_nodeattr("ip_vlnv"), node_name, node_name) ) # instantiate a streamer and connect it to the HLS IP - strm_vlnv = "xilinx.com:user:memstream:1.0" + strm_vlnv = "amd.com:finn:memstream:1.0" strm_inst = node_name + "_wstrm" cmd.append( "create_bd_cell -type ip -vlnv %s /%s/%s" @@ -1004,22 +1058,16 @@ class VectorVectorActivation(HLSCustomOp): ) cmd.append( "set_property -dict [list " - "CONFIG.NSTREAMS {1} " - "CONFIG.MEM_DEPTH {%d} " - "CONFIG.MEM_WIDTH {%d} " - "CONFIG.MEM_INIT {%s} " + "CONFIG.DEPTH {%d} " + "CONFIG.WIDTH {%d} " + "CONFIG.INIT_FILE {%s} " "CONFIG.RAM_STYLE {%s} " - "CONFIG.STRM0_DEPTH {%d} " - "CONFIG.STRM0_WIDTH {%d} " - "CONFIG.STRM0_OFFSET {0} " "] [get_bd_cells /%s/%s]" % ( self.calc_wmem(), self.get_weightstream_width_padded(), - self.get_nodeattr("code_gen_dir_ipgen") + "/", + self.get_nodeattr("code_gen_dir_ipgen") + "/memblock.dat", self.get_nodeattr("ram_style"), - self.calc_wmem(), - self.get_weightstream_width_padded(), node_name, strm_inst, ) @@ -1030,11 +1078,11 @@ class VectorVectorActivation(HLSCustomOp): % (node_name, strm_inst, node_name, node_name, sname) ) cmd.append( - "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/aresetn]" + "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/ap_rst_n]" % (node_name, rst_name, node_name, strm_inst) ) cmd.append( - "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/aclk]" + "connect_bd_net [get_bd_pins %s/%s] [get_bd_pins %s/%s/ap_clk]" % (node_name, clk_name, node_name, strm_inst) ) cmd.append( @@ -1080,7 +1128,7 @@ class VectorVectorActivation(HLSCustomOp): def uram_estimation(self): P = self.get_nodeattr("PE") - Q = 1 + Q = self.get_nodeattr("SIMD") wdt = self.get_weight_datatype() W = wdt.bitwidth() omega = self.calc_wmem() @@ -1089,7 +1137,7 @@ class VectorVectorActivation(HLSCustomOp): mstyle = self.get_nodeattr("ram_style") if ( (mmode == "decoupled" and mstyle != "ultra") - or (mmode == "const" and self.calc_wmem() <= 128) + or (mmode == "const") or (mmode == "external") ): return 0 @@ -1101,9 +1149,11 @@ class VectorVectorActivation(HLSCustomOp): """Calculates resource estimation for BRAM""" # TODO add in/out FIFO contributions P = self.get_nodeattr("PE") + Q = self.get_nodeattr("SIMD") wdt = self.get_weight_datatype() W = wdt.bitwidth() omega = self.calc_wmem() + mem_width = Q * W * P # assuming SDP mode RAMB18s (see UG573 Table 1-10) # since this is HLS memory, not using the full width of a BRAM # assuming memories up to 128 deep get implemented in LUTs @@ -1111,23 +1161,24 @@ class VectorVectorActivation(HLSCustomOp): mstyle = self.get_nodeattr("ram_style") if ( (mmode == "decoupled" and mstyle in ["distributed", "ultra"]) + or (mstyle == "auto" and self.calc_wmem() <= 128) or (mmode == "const" and self.calc_wmem() <= 128) or (mmode == "external") ): return 0 - if W == 1: - return math.ceil(omega / 16384) * P - elif W == 2: - return math.ceil(omega / 8192) * P - elif W <= 4: - return (math.ceil(omega / 4096)) * (math.ceil(W / 4)) * P - elif W <= 9: - return (math.ceil(omega / 2048)) * (math.ceil(W / 8)) * P - elif W <= 18 or omega > 512: - return (math.ceil(omega / 1024)) * (math.ceil(W / 16)) * P + if mem_width == 1: + return math.ceil(omega / 16384) + elif mem_width == 2: + return math.ceil(omega / 8192) + elif mem_width <= 4: + return (math.ceil(omega / 4096)) * (math.ceil(mem_width / 4)) + elif mem_width <= 9: + return (math.ceil(omega / 2048)) * (math.ceil(mem_width / 8)) + elif mem_width <= 18 or omega > 512: + return (math.ceil(omega / 1024)) * (math.ceil(mem_width / 16)) else: - return (math.ceil(omega / 512)) * (math.ceil(W / 32)) * P + return (math.ceil(omega / 512)) * (math.ceil(mem_width / 32)) def bram_efficiency_estimation(self): P = self.get_nodeattr("PE") @@ -1151,6 +1202,7 @@ class VectorVectorActivation(HLSCustomOp): """ # TODO add in/out FIFO contributions P = self.get_nodeattr("PE") + Q = self.get_nodeattr("SIMD") wdt = self.get_weight_datatype() W = wdt.bitwidth() # determine tdt with input and weight data types @@ -1165,29 +1217,46 @@ class VectorVectorActivation(HLSCustomOp): if (mmode == "decoupled" and mstyle == "distributed") or ( mmode == "const" and self.calc_wmem() <= 128 ): - c2 = (P * W) * math.ceil(self.calc_wmem() / 64) + c2 = (P * Q * W) * math.ceil(self.calc_wmem() / 64) # multiplication res_type = self.get_nodeattr("resType") if res_type == "dsp": mult_luts = 0 else: - mult_luts = (2 * math.ceil((W + A) / 6) - 1) * (W + A) + mult_luts = Q * (2 * math.ceil((W + A) / 6) - 1) * (W + A) + # adder tree + addertree_luts = (W + A) * (2 * Q - 1) # accumulator + acc_datatype = self.get_accumulator_datatype() + acc_bits = acc_datatype.bitwidth() k_h, k_w = self.get_nodeattr("Kernel") - acc_bits = W + A + math.ceil(math.log(k_h * k_w, 2)) + # if accDataType is not set, then it will default to INT32, which would + # be a large overestimate in most (if not all) cases. In this scenario, + # we would use the minimum accumulator as determined by the data types + # bound, derived in https://arxiv.org/abs/2301.13376 + alpha = math.log(k_h * k_w, 2) + W + A - 1 - int(idt.signed()) + acc_bits = min( + acc_datatype.bitwidth(), + np.ceil(alpha + math.log(1 + pow(2, -alpha), 2) + 1), + ) acc_luts = acc_bits # thresholds and threshold comparators thr_luts = 0 comp_luts = 0 noact = self.get_nodeattr("noActivation") + # TODO - add 'ram_style_threshold' node attribute if noact == 0: odt = self.get_output_datatype() B = odt.bitwidth() - thr_luts = (2**B - 1) * acc_bits * math.ceil(self.calc_tmem() / 64) + thr_luts = (2**B - 1) * acc_bits * self.calc_tmem() / 64 comp_luts = (2**B - 1) * acc_bits - return int(c0 + c1 * (P * (mult_luts + acc_luts + thr_luts + comp_luts)) + c2) + return int( + c0 + + c1 * (P * (mult_luts + addertree_luts + acc_luts + thr_luts + comp_luts)) + + c2 + ) def dsp_estimation(self): # multiplication @@ -1209,9 +1278,10 @@ class VectorVectorActivation(HLSCustomOp): self.get_nodeattr("mem_mode") == "decoupled" or self.get_nodeattr("mem_mode") == "external" ): + simd = self.get_nodeattr("SIMD") pe = self.get_nodeattr("PE") wp = self.get_weight_datatype().bitwidth() - w_width = pe * wp + w_width = simd * pe * wp return w_width else: return 0 diff --git a/src/finn/qnn-data/templates/driver/driver_base.py b/src/finn/qnn-data/templates/driver/driver_base.py index 2096760580b4f33ba1ab09564ebba1601c4dc23c..5f6f00da13de551a3e3cd234ad28458ca3cc6287 100644 --- a/src/finn/qnn-data/templates/driver/driver_base.py +++ b/src/finn/qnn-data/templates/driver/driver_base.py @@ -189,14 +189,10 @@ class FINNExampleOverlay(Overlay): layer_ind = int(w_filename.split("_")[1]) rt_weight_dict[(sdp_ind, layer_ind)] = layer_w for sdp_ind, layer_ind in rt_weight_dict.keys(): - cand_if_name = "StreamingDataflowPartition_%d/s_axilite_%d" % ( - sdp_ind, - layer_ind, - ) + cand_if_name = "StreamingDataflowPartition_%d" % sdp_ind if cand_if_name in self.ip_dict.keys(): layer_mmio = getattr( - getattr(self, "StreamingDataflowPartition_%d" % sdp_ind), - "s_axilite_%d" % layer_ind, + self, "StreamingDataflowPartition_%d" % sdp_ind ).mmio layer_w = rt_weight_dict[(sdp_ind, layer_ind)] layer_mmio.write_mm(0, layer_w.tobytes()) diff --git a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py index 7b8a1bf6b83175cfda041cfc49a22273fd696d8e..fcfe9e7727b60bd33fea7a56d312cf4789c0189a 100644 --- a/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py +++ b/src/finn/transformation/fpgadataflow/convert_to_hls_layers.py @@ -40,10 +40,6 @@ from qonnx.transformation.infer_shapes import InferShapes from qonnx.util.basic import get_by_name from qonnx.util.onnx import nchw_to_nhwc -from finn.transformation.fpgadataflow.minimize_accumulator_width import ( - MinimizeAccumulatorWidth, -) - class InferConvInpGen(Transformation): """Convert Im2Col layers to ConvolutionInputGenerator layers.""" @@ -144,19 +140,7 @@ class InferConvInpGen(Transformation): k_h > 1 and k_w == 1 and ifm_dim_w == 1 ) - # Ensure that RTL variant is not inserted for unsupported configuration - is_rtl_variant_compatible = True - if is_kernel_pointwise: - is_rtl_variant_compatible = False - if self.use_rtl_variant: - warnings.warn( - """%s : RTL ConvInpGen requested for unsupported - configuration. Falling back to HLS implementation.""" - % n.name - ) - - if self.use_rtl_variant and is_rtl_variant_compatible: - + if self.use_rtl_variant: ConvInpGen_node = helper.make_node( "ConvolutionInputGenerator_rtl", [ConvInpGen_input], @@ -761,7 +745,6 @@ class InferBinaryMatrixVectorActivation(Transformation): graph.node.remove(n) graph_modified = True if graph_modified: - model = model.transform(MinimizeAccumulatorWidth()) model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) return (model, graph_modified) @@ -904,7 +887,6 @@ class InferQuantizedMatrixVectorActivation(Transformation): graph.node.remove(n) graph_modified = True if graph_modified: - model = model.transform(MinimizeAccumulatorWidth()) model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) return (model, graph_modified) @@ -1057,7 +1039,6 @@ class InferVectorVectorActivation(Transformation): graph.node.remove(n) graph_modified = True if graph_modified: - model = model.transform(MinimizeAccumulatorWidth()) model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) return (model, graph_modified) @@ -1135,7 +1116,8 @@ class InferThresholdingLayer(Transformation): PE=pe, numSteps=thl_thres_shape[1], inputDataType=idt.name, - weightDataType=idt.name, # will be set by MinimizeAccumulatorWidth + # weightDataType can be tightened by MinimizeAccumulatorWidth + weightDataType=idt.name, outputDataType=odt.name, numInputVectors=list(thl_in_shape[:-1]), ActVal=actval, @@ -1148,7 +1130,6 @@ class InferThresholdingLayer(Transformation): graph_modified = True if graph_modified: - model = model.transform(MinimizeAccumulatorWidth()) model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) return (model, graph_modified) diff --git a/src/finn/transformation/fpgadataflow/create_stitched_ip.py b/src/finn/transformation/fpgadataflow/create_stitched_ip.py index 8e2c69bad4b0a6749c605bea9ee21d6408c904c0..ef1afb95ca5fb97add4092d75d40b65c81349195 100644 --- a/src/finn/transformation/fpgadataflow/create_stitched_ip.py +++ b/src/finn/transformation/fpgadataflow/create_stitched_ip.py @@ -385,6 +385,8 @@ class CreateStitchedIP(Transformation): "create_project %s %s -part %s" % (prjname, vivado_stitch_proj_dir, self.fpgapart) ) + # no warnings on long module names + tcl.append("set_msg_config -id {[BD 41-1753]} -suppress") # add all the generated IP dirs to ip_repo_paths ip_dirs_str = " ".join(ip_dirs) tcl.append("set_property ip_repo_paths [%s] [current_project]" % ip_dirs_str) @@ -397,8 +399,9 @@ class CreateStitchedIP(Transformation): fclk_mhz = 1 / (self.clk_ns * 0.001) fclk_hz = fclk_mhz * 1000000 model.set_metadata_prop("clk_ns", str(self.clk_ns)) - tcl.append("set_property CONFIG.FREQ_HZ %f [get_bd_ports /ap_clk]" % fclk_hz) - tcl.append("regenerate_bd_layout") + tcl.append( + "set_property CONFIG.FREQ_HZ %d [get_bd_ports /ap_clk]" % round(fclk_hz) + ) tcl.append("validate_bd_design") tcl.append("save_bd_design") # create wrapper hdl (for rtlsim later on) @@ -412,7 +415,7 @@ class CreateStitchedIP(Transformation): wrapper_filename = "%s/hdl/%s_wrapper.v" % (bd_base, block_name) tcl.append("add_files -norecurse %s" % wrapper_filename) model.set_metadata_prop("wrapper_filename", wrapper_filename) - tcl.append("set_property top finn_design_wrapper [current_fileset]") + tcl.append("set_property top %s_wrapper [current_fileset]" % block_name) # synthesize to DCP and export stub, DCP and constraints if self.vitis: tcl.append( @@ -450,6 +453,8 @@ class CreateStitchedIP(Transformation): ) % (vivado_stitch_proj_dir, block_vendor, block_library, block_name) ) + # Allow user to customize clock in deployment of stitched IP + tcl.append("set_property ipi_drc {ignore_freq_hz true} [ipx::current_core]") # in some cases, the IP packager seems to infer an aperture of 64K or 4G, # preventing address assignment of the DDR_LOW and/or DDR_HIGH segments # the following is a hotfix to remove this aperture during IODMA packaging @@ -544,20 +549,83 @@ class CreateStitchedIP(Transformation): # add a rudimentary driver mdd to get correct ranges in xparameters.h later on example_data_dir = pk.resource_filename("finn.qnn-data", "mdd-data/") copytree(example_data_dir, vivado_stitch_proj_dir + "/data") - tcl.append("file copy -force data ip/") - tcl.append("ipx::add_file_group -type software_driver {} [ipx::current_core]") - tcl.append( - "set_property type mdd [ipx::add_file data/finn_design.mdd " - "[ipx::get_file_groups xilinx_softwaredriver -of_objects " - "[ipx::current_core]]]" - ) + + ##### + # Core Cleanup Operations tcl.append( - "set_property type tclSource [ipx::add_file data/finn_design.tcl " - "[ipx::get_file_groups xilinx_softwaredriver -of_objects " - "[ipx::current_core]]]" + """ +set core [ipx::current_core] + +# Add rudimentary driver +file copy -force data ip/ +set file_group [ipx::add_file_group -type software_driver {} $core] +set_property type mdd [ipx::add_file data/finn_design.mdd $file_group] +set_property type tclSource [ipx::add_file data/finn_design.tcl $file_group] + +# Remove all XCI references to subcores +set impl_files [ipx::get_file_groups xilinx_implementation -of $core] +foreach xci [ipx::get_files -of $impl_files {*.xci}] { + ipx::remove_file [get_property NAME $xci] $impl_files +} + +# Construct a single flat memory map for each AXI-lite interface port +foreach port [get_bd_intf_ports -filter {CONFIG.PROTOCOL==AXI4LITE}] { + set pin $port + set awidth "" + while { $awidth == "" } { + set pins [get_bd_intf_pins -of [get_bd_intf_nets -boundary_type lower -of $pin]] + set kill [lsearch $pins $pin] + if { $kill >= 0 } { set pins [lreplace $pins $kill $kill] } + if { [llength $pins] != 1 } { break } + set pin [lindex $pins 0] + set awidth [get_property CONFIG.ADDR_WIDTH $pin] + } + if { $awidth == "" } { + puts "CRITICAL WARNING: Unable to construct address map for $port." + } { + set range [expr 2**$awidth] + puts "INFO: Building address map for $port: 0+:$range" + set name [get_property NAME $port] + set addr_block [ipx::add_address_block Reg0 [ipx::add_memory_map $name $core]] + set_property range $range $addr_block + set_property slave_memory_map_ref $name [ipx::get_bus_interfaces $name -of $core] + } +} + +# Finalize and Save +ipx::update_checksums $core +ipx::save_core $core + +# Remove stale subcore references from component.xml +file rename -force ip/component.xml ip/component.bak +set ifile [open ip/component.bak r] +set ofile [open ip/component.xml w] +set buf [list] +set kill 0 +while { [eof $ifile] != 1 } { + gets $ifile line + if { [string match {*<spirit:fileSet>*} $line] == 1 } { + foreach l $buf { puts $ofile $l } + set buf [list $line] + } elseif { [llength $buf] > 0 } { + lappend buf $line + + if { [string match {*</spirit:fileSet>*} $line] == 1 } { + if { $kill == 0 } { foreach l $buf { puts $ofile $l } } + set buf [list] + set kill 0 + } elseif { [string match {*<xilinx:subCoreRef>*} $line] == 1 } { + set kill 1 + } + } else { + puts $ofile $line + } +} +close $ifile +close $ofile +""" ) - tcl.append("ipx::update_checksums [ipx::find_open_core %s]" % block_vlnv) - tcl.append("ipx::save_core [ipx::find_open_core %s]" % block_vlnv) + # export list of used Verilog files (for rtlsim later on) tcl.append( "set all_v_files [get_files -filter {USED_IN_SYNTHESIS == 1 " diff --git a/src/finn/transformation/fpgadataflow/insert_dwc.py b/src/finn/transformation/fpgadataflow/insert_dwc.py index 632d1f813b4d2509407930bc9294f7531d4c90af..cff8b602674fec41a1e6fd1d467acdc989b4afe2 100644 --- a/src/finn/transformation/fpgadataflow/insert_dwc.py +++ b/src/finn/transformation/fpgadataflow/insert_dwc.py @@ -81,15 +81,11 @@ class InsertDWC(Transformation): dwc_in_width = n0.get_outstream_width() # determine dwc outwidth dwc_out_width = n1.get_instream_width() - larger_width = max(dwc_in_width, dwc_out_width) - smaller_width = min(dwc_in_width, dwc_out_width) - both_8bit_aligned = (larger_width % 8 == 0) and ( - smaller_width % 8 == 0 - ) - if both_8bit_aligned: - impl_style = "vivado" - else: - impl_style = "hls" + # use hls mode by default since it supports more configs + # vivado mode can be manually enabled by user, but does not + # support e.g. node-by-node rtlsim neded for + # characterization-based FIFO sizing + impl_style = "hls" # determine shape for dwc dwc_shape = n0.get_normal_output_shape() diff --git a/src/finn/transformation/fpgadataflow/make_deployment.py b/src/finn/transformation/fpgadataflow/make_deployment.py deleted file mode 100644 index d4684dc83ce1f22ecae2ca04af5e5973519db4f6..0000000000000000000000000000000000000000 --- a/src/finn/transformation/fpgadataflow/make_deployment.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import os -import subprocess -from distutils.dir_util import copy_tree -from qonnx.transformation.base import Transformation -from shutil import copy - -import finn.transformation.fpgadataflow.templates as templates -from finn.util.basic import make_build_dir - - -class DeployToPYNQ(Transformation): - """Collects all necessary files for deployment and copies them to the PYNQ board. - Expects information about PYNQ board to make scp possible: - - IP address of board, username and password for board and target directory where - the files are stored on the board""" - - def __init__(self, ip, port, username, password, target_dir): - super().__init__() - self.ip = ip - self.port = port - self.username = username - self.password = password - self.target_dir = target_dir - - def apply(self, model): - # set metadata properties accordingly to user input specifications - model.set_metadata_prop("pynq_ip", self.ip) - model.set_metadata_prop("pynq_port", str(self.port)) - model.set_metadata_prop("pynq_username", self.username) - model.set_metadata_prop("pynq_password", self.password) - model.set_metadata_prop("pynq_target_dir", self.target_dir) - - # create directory for deployment files - deployment_dir = make_build_dir(prefix="pynq_deployment_") - model.set_metadata_prop("pynq_deployment_dir", deployment_dir) - - # get and copy necessary files - # .bit and .hwh file - bitfile = model.get_metadata_prop("bitfile") - hwh_file = model.get_metadata_prop("hw_handoff") - deploy_files = [bitfile, hwh_file] - - for dfile in deploy_files: - if dfile is not None: - copy(dfile, deployment_dir) - - # helper script for Alveo - platform = model.get_metadata_prop("platform") - if platform == "alveo": - alveo_run_sh = templates.alveo_run_sh_template - fill_dict = { - "$REMOTE_DEPLOY_DIR$": self.target_dir - + "/" - + os.path.basename(deployment_dir), - "$CONDA_ENV_NAME$": "finn-pynq-alveo", - "$REMOTE_XRT$": os.environ["XILINX_XRT"], - "$REMOTE_PLATFORM_REPO_PATHS$": os.environ["PLATFORM_REPO_PATHS"], - "$BITFILE$": os.path.basename(bitfile), - } - for key, value in fill_dict.items(): - alveo_run_sh = alveo_run_sh.replace(key, value) - alveo_run_sh_path = deployment_dir + "/alveo_run.sh" - with open(alveo_run_sh_path, "w") as f: - f.write(alveo_run_sh) - - # driver.py and python libraries - pynq_driver_dir = model.get_metadata_prop("pynq_driver_dir") - copy_tree(pynq_driver_dir, deployment_dir) - model.set_metadata_prop("pynq_deploy_dir", deployment_dir) - model.set_metadata_prop("exec_mode", "remote_pynq") - - # create target directory on PYNQ board - cmd = 'ssh {}@{} -p {} "mkdir -p {}"'.format( - self.username, self.ip, self.port, self.target_dir - ) - bash_command = ["/bin/bash", "-c", cmd] - process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_compile.communicate() - # copy directory to PYNQ board using scp - cmd = "scp -P{} -r {} {}@{}:{}".format( - self.port, deployment_dir, self.username, self.ip, self.target_dir - ) - bash_command = ["/bin/bash", "-c", cmd] - process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) - process_compile.communicate() - - return (model, False) diff --git a/tests/end2end/test_end2end_access_board.py b/src/finn/transformation/fpgadataflow/minimize_weight_bit_width.py similarity index 64% rename from tests/end2end/test_end2end_access_board.py rename to src/finn/transformation/fpgadataflow/minimize_weight_bit_width.py index ba3c49195b298059149303c63ef2db8ab6e16039..32871cc44a886fddcf3363fc06a3c6831a3d92bc 100644 --- a/tests/end2end/test_end2end_access_board.py +++ b/src/finn/transformation/fpgadataflow/minimize_weight_bit_width.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, Xilinx +# Copyright (C) 2023, Advanced Micro Devices, Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -26,31 +26,24 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import pytest +from qonnx.custom_op.registry import getCustomOp +from qonnx.transformation.base import Transformation -import subprocess +from finn.util.fpgadataflow import is_fpgadataflow_node -from finn.util.test import get_build_env +class MinimizeWeightBitWidth(Transformation): + """For relevant nodes, call the weight bit width minimization + functions to save on resources. May alter tensor weightDataType + if the node does not have runtime writeable weights.""" -@pytest.mark.board -@pytest.mark.end2end -def test_end2end_access_board(): - build_env = get_build_env("zynq", 5) - if build_env["ip"] == "": - pytest.skip("PYNQ board IP address not specified") - remote_cmd_base = [ - "ssh", - "-o", - "PreferredAuthentications=publickey", - "-o", - "PasswordAuthentication=no", - "%s@%s" % (build_env["username"], build_env["ip"]), - ] - test_text = "BoardIsAccessible" - touch_cmd = remote_cmd_base + ["echo %s" % test_text] - verif_res = subprocess.run( - touch_cmd, stdout=subprocess.PIPE, universal_newlines=True - ) - assert verif_res.returncode == 0 - assert verif_res.stdout.split("\n")[0] == test_text + def __init__(self): + super().__init__() + + def apply(self, model): + for node in model.graph.node: + if is_fpgadataflow_node(node) is True: + inst = getCustomOp(node) + if hasattr(inst, "minimize_weight_bit_width"): + inst.minimize_weight_bit_width(model) + return (model, False) diff --git a/src/finn/transformation/fpgadataflow/set_folding.py b/src/finn/transformation/fpgadataflow/set_folding.py index 2301fccdd4fff6310340ffe1dd8de7732a4f9bd4..0a466afe13fa3b96b7bcfefede16cfdf2cb5449c 100644 --- a/src/finn/transformation/fpgadataflow/set_folding.py +++ b/src/finn/transformation/fpgadataflow/set_folding.py @@ -75,7 +75,9 @@ class SetFolding(Transformation): * the producer of the node is expected to be a ConvolutionInputGenerator with depthwise=1, whose SIMD value will be set equal to the PE value of its consumer node - + * the VVAU also supports SIMD ("input window") parallelism next to + PE ("channels"), but current ConvInpGen limitations require PE to be fully + unfolded before SIMD is increased """ def __init__( @@ -106,7 +108,9 @@ class SetFolding(Transformation): "Thresholding_Batch", ] # these ops use SIMD parallelism, up to a max value of NumChannels - # ConvolutionInputGenerator has a special case when depthwise=1 + # ConvolutionInputGenerator* has a special case when depthwise=1 + # ConvolutionInputGenerator_rtl supports additional parallelism by + # setting parallel_window=1 mode after maxing out SIMD simd_ops = [ "DownSampler", "FMPadding_Batch", @@ -154,15 +158,36 @@ class SetFolding(Transformation): max_pe = node_inst.get_nodeattr("Labels") self.optimize_attribute_val(node_inst, max_pe, "PE") elif op_type in depthwise_op_exceptions: + # init/reset SIMD of VVAU + if op_type == "VectorVectorActivation": + node_inst.set_nodeattr("SIMD", 1) max_pe = node_inst.get_nodeattr("Channels") self.optimize_attribute_val(node_inst, max_pe, "PE") + # increase SIMD for VVAU once PE is exhausted + pe = node_inst.get_nodeattr("PE") + cyc = node_inst.get_exp_cycles() + if ( + op_type == "VectorVectorActivation" + and pe == max_pe + and cyc > self.target_cycles_per_frame + ): + max_simd = np.prod(node_inst.get_nodeattr("Kernel")) + self.optimize_attribute_val(node_inst, max_simd, "SIMD") # also set the folding of the upsteam DW SWU # which must be identical to this node swu_node = model.find_producer(node.input[0]) if swu_node.op_type.startswith("ConvolutionInputGenerator"): swu_node_inst = getCustomOp(swu_node) - pe = node_inst.get_nodeattr("PE") swu_node_inst.set_nodeattr("SIMD", pe) + # enable parallel_window mode of RTL SWG if needed + if swu_node.op_type == "ConvolutionInputGenerator_rtl": + if ( + op_type == "VectorVectorActivation" + and node_inst.get_nodeattr("SIMD") > 1 + ): + swu_node_inst.set_nodeattr("parallel_window", 1) + else: + swu_node_inst.set_nodeattr("parallel_window", 0) else: if op_type == "VectorVectorActivation": ksize = np.prod(node_inst.get_nodeattr("Kernel")) @@ -179,7 +204,19 @@ class SetFolding(Transformation): depthwise = node_inst.get_nodeattr("depthwise") if depthwise == 0: max_simd = node_inst.get_nodeattr("IFMChannels") + # init/reset parallel_window mode of RTL SWG + if op_type == "ConvolutionInputGenerator_rtl": + node_inst.set_nodeattr("parallel_window", 0) self.optimize_attribute_val(node_inst, max_simd, "SIMD") + # enable parallel_window mode of RTL SWG if needed + simd = node_inst.get_nodeattr("SIMD") + cyc = node_inst.get_exp_cycles() + if ( + op_type == "ConvolutionInputGenerator_rtl" + and simd == max_simd + and cyc > self.target_cycles_per_frame + ): + node_inst.set_nodeattr("parallel_window", 1) else: # depthwise SWGs are handled separately continue diff --git a/src/finn/transformation/fpgadataflow/template_driver.py b/src/finn/transformation/fpgadataflow/template_driver.py index 05ee6ad920d7e921dc9611a7936e28288ba53a0a..158825191e3372fc133e01b47ea7138b0aba899f 100644 --- a/src/finn/transformation/fpgadataflow/template_driver.py +++ b/src/finn/transformation/fpgadataflow/template_driver.py @@ -135,5 +135,5 @@ if __name__ == "__main__": file.close() print("Results written to nw_metrics.txt") else: - raise Exception("Exec mode has to be set to remote_pynq or throughput_test") + raise Exception("Exec mode has to be set to execute or throughput_test") """ diff --git a/src/finn/transformation/fpgadataflow/templates.py b/src/finn/transformation/fpgadataflow/templates.py index f52bad0ffb35ae4714acc24aef368d01967db426..ce1545b5be88d87f65d5c39028057b5f4b651a41 100644 --- a/src/finn/transformation/fpgadataflow/templates.py +++ b/src/finn/transformation/fpgadataflow/templates.py @@ -242,22 +242,6 @@ report_utilization -hierarchical -hierarchical_depth 4 -file synth_report.xml -f close_project """ -alveo_run_sh_template = """#!/bin/bash - -if [ "$#" -ne 2 ]; then - echo "Usage: alveo_run.sh <exec_mode={execute, throughput_test}> <batch_size>" - exit -1 -fi - -cd $REMOTE_DEPLOY_DIR$ -eval "$(conda shell.bash hook)" -conda activate $CONDA_ENV_NAME$ -source $REMOTE_XRT$/setup.sh -export PLATFORM_REPO_PATHS=$REMOTE_PLATFORM_REPO_PATHS$ -python3.6 driver.py --exec_mode=$1 --batchsize=$2 --bitfile=$BITFILE$ \ - --inputfile=input.npy --outputfile=output.npy --platform=alveo -""" - vitis_gen_xml_report_tcl_template = """ open_project $VITIS_PROJ_PATH$/_x/link/vivado/vpl/prj/prj.xpr open_run impl_1 diff --git a/src/finn/transformation/qonnx/infer_quant_avg_pool_2d.py b/src/finn/transformation/qonnx/infer_quant_avg_pool_2d.py index 5a3f176f1ff41f17bdf684d59433d4f184a7ccb5..d2aaee59a4767f36d2e948fa97b1deecb7f365ac 100644 --- a/src/finn/transformation/qonnx/infer_quant_avg_pool_2d.py +++ b/src/finn/transformation/qonnx/infer_quant_avg_pool_2d.py @@ -46,7 +46,7 @@ def _get_signed_from_upstream(model, trunc_node): # Check if the input of this node already has a FINN datatype signed = None inp_dt = model.get_tensor_datatype(node.input[0]) - if inp_dt is not None and inp_dt is not DataType["FLOAT32"]: + if inp_dt is not None and inp_dt != DataType["FLOAT32"]: signed = inp_dt.signed() # Go further up the graph, since the datatype inference works top down # these nodes should either be sign preserving ops or they already have a @@ -67,23 +67,23 @@ def _get_signed_from_upstream(model, trunc_node): ) next_node = next_node[0] out_dt = model.get_tensor_datatype(next_node.output[0]) - if out_dt is not None and out_dt is not DataType["FLOAT32"]: + if out_dt is not None and out_dt != DataType["FLOAT32"]: signed = out_dt.signed() break # Special cases where the node has an internal or intrinsic datatype. if next_node.op_type == "MultiThreshold": - mt_inst = getCustomOp(next_node) + mt_inst = getCustomOp(next_node, onnx_opset_version=9) out_dt = DataType[mt_inst.get_nodeattr("out_dtype")] - if out_dt is not None and out_dt is not DataType["FLOAT32"]: + if out_dt is not None and out_dt != DataType["FLOAT32"]: signed = out_dt.signed() break if next_node.op_type == "BipolarQuant": signed = True break if next_node.op_type == "Quant": - q_inst = getCustomOp(next_node) + q_inst = getCustomOp(next_node, onnx_opset_version=9) out_dt = q_inst.get_integer_datatype(model) - if out_dt is not None and out_dt is not DataType["FLOAT32"]: + if out_dt is not None and out_dt != DataType["FLOAT32"]: signed = out_dt.signed() break diff --git a/src/finn/transformation/qonnx/qonnx_activation_handlers.py b/src/finn/transformation/qonnx/qonnx_activation_handlers.py index 9819086d826a51d1df5240d88c4fda8513cc9ba6..bbe5e1a0e319a8f62e9a1bcd4f0857f36295049e 100644 --- a/src/finn/transformation/qonnx/qonnx_activation_handlers.py +++ b/src/finn/transformation/qonnx/qonnx_activation_handlers.py @@ -286,6 +286,7 @@ class QuantReluHandler(QuantActBaseHandler): def valid_predecessor_op_types(self): return [ "Relu", + "Selu", ] def _check_compatibility(self): @@ -293,16 +294,19 @@ class QuantReluHandler(QuantActBaseHandler): q_inst = getCustomOp(self._q_node) narrow = q_inst.get_nodeattr("narrow") signed = q_inst.get_nodeattr("signed") - if signed or narrow: - raise ValueError( - "FINN only supports unsigned and non-narrow Quant nodes " - "for Relu activations." - ) if not self._model.get_initializer(self._q_node.input[2]) == 0: raise ValueError( "Only Quant nodes with zero-point == 0 " "are currently supported for ReLu activations." ) + act_node = self._model.find_direct_predecessors(self._q_node) + act_node = act_node[0] + if act_node.op_type == "Relu": + if signed or narrow: + raise ValueError( + "FINN only supports unsigned and non-narrow Quant nodes " + "for Relu activations." + ) elif self._q_node.op_type == "BipolarQuant": return else: @@ -312,7 +316,31 @@ class QuantReluHandler(QuantActBaseHandler): # No bias allowed for Relu activations, see: https://github.com/Xilinx/ # brevitas/blob/a5bfd6dc5e030f0047ac1ee47932b60e8e873e17/src/brevitas/ # export/onnx/finn/handler/act.py#L48 - bias = np.array([0.0], dtype=np_default_dtype) + act_node = self._model.find_direct_predecessors(self._q_node) + act_node = act_node[0] + if act_node.op_type == "Relu": + bias = np.array([0.0], dtype=np_default_dtype) + elif act_node.op_type == "Selu": + # Gather parameters + q_inst = getCustomOp(self._q_node) + if self._q_node.op_type == "Quant": + bit_width = self._model.get_initializer(self._q_node.input[3]) + narrow = q_inst.get_nodeattr("narrow") + elif self._q_node.op_type == "BipolarQuant": + bit_width = 1.0 + else: + raise RuntimeError("Got an unexpected quantizer node type") + # Calculate bias, see: https://github.com/Xilinx/brevitas/blob/ + # a5bfd6dc5e030f0047ac1ee47932b60e8e873e17/src/brevitas/export/ + # onnx/finn/handler/act.py#L64 + if bit_width == 1.0: + bias = np.array([-0.5], dtype=np_default_dtype) + else: + if narrow: + min_non_scaled_val = -(2 ** (bit_width - 1) - 1) + else: + min_non_scaled_val = -(2 ** (bit_width - 1)) + bias = np.array([min_non_scaled_val], dtype=np_default_dtype) return bias def _calculate_thresholds(self): @@ -326,24 +354,53 @@ class QuantReluHandler(QuantActBaseHandler): quant_scale = self._model.get_initializer(self._q_node.input[1]).astype( np.float32 ) - # q_inst = getCustomOp(self._q_node) - # narrow = q_inst.get_nodeattr("narrow") + act_node = self._model.find_direct_predecessors(self._q_node) + act_node = act_node[0] + if act_node.op_type == "Relu": - # Calculate thersholds, see: https://github.com/Xilinx/brevitas/blob/ - # a5bfd6dc5e030f0047ac1ee47932b60e8e873e17/src/brevitas/export/ - # onnx/finn/handler/act.py#L21 - num_distinct_values = 2**bit_width - num_thresholds = int(num_distinct_values - 1) - flat_scale = quant_scale.flatten().astype(np.float32) - num_scale_channels = flat_scale.shape[0] - step = np.abs(flat_scale).astype(np.float32) - min_threshold = step / 2 - thresholds = np.empty( - (num_scale_channels, num_thresholds), dtype=np_default_dtype - ) - for c in range(num_scale_channels): - for t in range(num_thresholds): - thresholds[c][t] = min_threshold[c] + step[c] * t + # Calculate thersholds, see: https://github.com/Xilinx/brevitas/blob/ + # a5bfd6dc5e030f0047ac1ee47932b60e8e873e17/src/brevitas/export/ + # onnx/finn/handler/act.py#L21 + num_distinct_values = 2**bit_width + num_thresholds = int(num_distinct_values - 1) + flat_scale = quant_scale.flatten().astype(np.float32) + num_scale_channels = flat_scale.shape[0] + step = np.abs(flat_scale).astype(np.float32) + min_threshold = step / 2 + thresholds = np.empty( + (num_scale_channels, num_thresholds), dtype=np_default_dtype + ) + for c in range(num_scale_channels): + for t in range(num_thresholds): + thresholds[c][t] = min_threshold[c] + step[c] * t + + elif act_node.op_type == "Selu": + q_inst = getCustomOp(self._q_node) + narrow = q_inst.get_nodeattr("narrow") + if narrow: + num_distinct_values = 2**bit_width - 1 + else: + num_distinct_values = 2**bit_width + + num_thresholds = int(num_distinct_values - 1) + flat_scale = quant_scale.flatten().astype(np.float32) + num_scale_channels = flat_scale.shape[0] + scale = np.abs(flat_scale).astype(np.float32) + half_scale = scale / 2 + # alpha and lambda + # from https://pytorch.org/docs/stable/generated/torch.nn.SELU.html + alpha = 1.6732632423543772848170429916717 + selu_scale = 1.0507009873554804934193349852946 + thresholds = np.empty( + (num_scale_channels, num_thresholds), dtype=np_default_dtype + ) + for c in range(num_scale_channels): + for t in range(num_thresholds): + step = -1.0 + half_scale + scale[c] * t + if step <= 0: + thresholds[c][t] = np.log(step / (alpha * selu_scale) + 1) + else: + thresholds[c][t] = step / selu_scale # ToDo: The index 1 needs to be changed to -1 for the channels last format num_output_channels = self._model.get_tensor_shape(self._q_node.output[0])[1] @@ -371,10 +428,10 @@ class QuantReluHandler(QuantActBaseHandler): "the Quant node must exist." ) act_node = act_node[0] - if not act_node.op_type == "Relu": + if act_node.op_type not in self.valid_predecessor_op_types(): raise RuntimeError( - "The predecesor of the Quant node must be Relu for handling " - "of Relu activations." + "The predecesor of the Quant node must be Relu or Selu for handling " + "of activations." ) # Reroute upstream tensor diff --git a/src/finn/util/data_packing.py b/src/finn/util/data_packing.py index 797dad32a2cfeb3e00e224f264d91b5ee0e9247b..a41fe882e543db3f6809f0bc269b81c1e8a22ab5 100644 --- a/src/finn/util/data_packing.py +++ b/src/finn/util/data_packing.py @@ -220,7 +220,7 @@ def unpack_innermost_dim_from_hex_string( if conv_dtype == DataType["BIPOLAR"]: ar_list = [2 * x - 1 for x in ar_list] # interpret values as signed values - elif conv_dtype.name.startswith("INT"): + elif conv_dtype.signed() and conv_dtype.is_integer(): mask = 2 ** (conv_dtype.bitwidth() - 1) ar_list = [-(x & mask) + (x & ~mask) for x in ar_list] diff --git a/src/finn/util/gdrive.py b/src/finn/util/gdrive.py deleted file mode 100644 index d525437300b6aee081bb073d40a517b5e3aa14be..0000000000000000000000000000000000000000 --- a/src/finn/util/gdrive.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2020, Xilinx -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of FINN nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import gspread -import os -import warnings -from datetime import datetime - -from finn.util.basic import get_finn_root - - -def upload_to_end2end_dashboard(data_dict): - gdrive_key = get_finn_root() + "/gdrive-key/service_account.json" - if not os.path.isfile(gdrive_key): - warnings.warn("Google Drive key not found, skipping dashboard upload") - return - gc = gspread.service_account(filename=gdrive_key) - spreadsheet = gc.open("finn-end2end-dashboard") - worksheet = spreadsheet.get_worksheet(0) - keys = list(data_dict.keys()) - vals = list(data_dict.values()) - # check against existing header - existing_keys = worksheet.row_values(1) - if not set(existing_keys).issuperset(set(keys)): - # create new worksheet - dtstr = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - worksheet = spreadsheet.add_worksheet( - title="Dashboard " + dtstr, rows=10, cols=len(keys), index=0 - ) - # create header row with keys - worksheet.update("A1:1", [keys]) - # freeze and make header bold - worksheet.freeze(rows=1) - worksheet.format("A1:1", {"textFormat": {"bold": True}}) - # insert values into new row at appropriate positions - worksheet.insert_row([], index=2) - for i in range(len(keys)): - colind = existing_keys.index(keys[i]) - col_letter = chr(ord("A") + colind) - worksheet.update("%s2" % col_letter, vals[i]) diff --git a/src/finn/util/pyverilator.py b/src/finn/util/pyverilator.py index 8d188585694c172d97d73fa6b5820edb7b48a948..74523945242c1de68e56659087c87c349ebae4bc 100644 --- a/src/finn/util/pyverilator.py +++ b/src/finn/util/pyverilator.py @@ -118,6 +118,8 @@ def prepare_stitched_ip_for_verilator(model): if not remove_entry: filtered_verilog_files.append(vfile) remove_entry = True + elif "swg_pkg" in vfile: + continue else: filtered_verilog_files.append(vfile) @@ -315,8 +317,10 @@ def pyverilate_stitched_ip( xpm_cdc = f"{vivado_path}/data/ip/xpm/xpm_cdc/hdl/xpm_cdc.sv" xpm_fifo = f"{vivado_path}/data/ip/xpm/xpm_fifo/hdl/xpm_fifo.sv" + swg_pkg = os.environ["FINN_ROOT"] + "/finn-rtllib/swg/swg_pkg.sv" + sim = PyVerilator.build( - [top_module_file_name, xpm_fifo, xpm_memory, xpm_cdc], + [swg_pkg, top_module_file_name, xpm_fifo, xpm_memory, xpm_cdc], verilog_path=[vivado_stitch_proj_dir, verilog_header_dir], build_dir=build_dir, trace_depth=get_rtlsim_trace_depth(), diff --git a/src/finn/util/test.py b/src/finn/util/test.py index bd8bde2820fa87ed972d699cae905d7f6cc310ff..4250079ef3e994f62a3e9f9150eb5b66371b5895 100644 --- a/src/finn/util/test.py +++ b/src/finn/util/test.py @@ -114,25 +114,14 @@ def get_build_env(kind, target_clk_ns): if kind == "zynq": ret["board"] = os.getenv("PYNQ_BOARD", default="Pynq-Z1") ret["part"] = pynq_part_map[ret["board"]] - ret["ip"] = os.getenv("PYNQ_IP", "") - ret["username"] = os.getenv("PYNQ_USERNAME", "xilinx") - ret["password"] = os.getenv("PYNQ_PASSWORD", "xilinx") - ret["port"] = os.getenv("PYNQ_PORT", 22) - ret["target_dir"] = os.getenv("PYNQ_TARGET_DIR", "/home/xilinx/finn") ret["build_fxn"] = ZynqBuild(ret["board"], target_clk_ns) elif kind == "alveo": ret["board"] = os.getenv("ALVEO_BOARD", default="U250") ret["part"] = alveo_part_map[ret["board"]] - ret["platform"] = alveo_default_platform[ret["board"]] - ret["ip"] = os.getenv("ALVEO_IP", "") - ret["username"] = os.getenv("ALVEO_USERNAME", "") - ret["password"] = os.getenv("ALVEO_PASSWORD", "") - ret["port"] = os.getenv("ALVEO_PORT", 22) - ret["target_dir"] = os.getenv("ALVEO_TARGET_DIR", "/tmp/finn_alveo_deploy") ret["build_fxn"] = VitisBuild( ret["part"], target_clk_ns, - ret["platform"], + alveo_default_platform[ret["board"]], strategy=VitisOptStrategy.BUILD_SPEED, ) else: diff --git a/tests/brevitas/king_charles.jpg b/tests/brevitas/king_charles.jpg index c1400a484e686c3efe045c56e4fe02f3e0f8d17b..d3639a69e953954008284cb68cc6da1083e54710 100755 Binary files a/tests/brevitas/king_charles.jpg and b/tests/brevitas/king_charles.jpg differ diff --git a/tests/brevitas/test_brevitas_avg_pool_export.py b/tests/brevitas/test_brevitas_avg_pool_export.py index 669601ecb6ebfd6758d3382ab097a1e93dc848c7..9550031b326b8002c74b943529ba24a193e26a5d 100644 --- a/tests/brevitas/test_brevitas_avg_pool_export.py +++ b/tests/brevitas/test_brevitas_avg_pool_export.py @@ -30,10 +30,8 @@ import pytest import numpy as np import os import torch -from brevitas.export import FINNManager -from brevitas.export.onnx.generic.manager import BrevitasONNXManager -from brevitas.nn import QuantAvgPool2d -from brevitas.quant_tensor import QuantTensor +from brevitas.export import export_qonnx +from brevitas.nn import QuantAvgPool2d, QuantIdentity, QuantReLU from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.infer_datatypes import InferDataTypes @@ -48,10 +46,9 @@ base_export_onnx_path = "test_brevitas_avg_pool_export.onnx" @pytest.mark.brevitas_export -@pytest.mark.parametrize("QONNX_export", [False, True]) @pytest.mark.parametrize("kernel_size", [2, 3]) @pytest.mark.parametrize("stride", [1, 2]) -@pytest.mark.parametrize("signed", [True, False]) +@pytest.mark.parametrize("signed", [True]) # TODO: Add unsigned test case @pytest.mark.parametrize("bit_width", [2, 4]) @pytest.mark.parametrize("input_bit_width", [4, 8, 16]) @pytest.mark.parametrize("channels", [2, 4]) @@ -64,79 +61,56 @@ def test_brevitas_avg_pool_export( input_bit_width, channels, idim, - QONNX_export, ): - export_onnx_path = base_export_onnx_path.replace( - ".onnx", f"test_QONNX-{QONNX_export}.onnx" - ) + export_onnx_path = base_export_onnx_path.replace(".onnx", "test_QONNX.onnx") + if signed: + quant_node = QuantIdentity( + bit_width=input_bit_width, + return_quant_tensor=True, + ) + else: + quant_node = QuantReLU( + bit_width=input_bit_width, + return_quant_tensor=True, + ) quant_avgpool = QuantAvgPool2d( kernel_size=kernel_size, stride=stride, bit_width=bit_width, return_quant_tensor=False, + float_to_int_impl_type="FLOOR", ) - quant_avgpool.eval() + model_brevitas = torch.nn.Sequential(quant_node, quant_avgpool) + model_brevitas.eval() # determine input - prefix = "INT" if signed else "UINT" - dt_name = prefix + str(input_bit_width) - dtype = DataType[dt_name] input_shape = (1, channels, idim, idim) - input_array = gen_finn_dt_tensor(dtype, input_shape) - # Brevitas QuantAvgPool layers need QuantTensors to export correctly - # which requires setting up a QuantTensor instance with the scale - # factor, zero point, bitwidth and signedness - scale_array = np.ones((1, channels, 1, 1)).astype(np.float32) - scale_array *= 0.5 - input_tensor = torch.from_numpy(input_array * scale_array).float() - scale_tensor = torch.from_numpy(scale_array).float() - zp = torch.tensor(0.0) - input_quant_tensor = QuantTensor( - input_tensor, scale_tensor, zp, input_bit_width, signed, training=False - ) + input_array = gen_finn_dt_tensor(DataType["FLOAT32"], input_shape) - # export - if QONNX_export: - BrevitasONNXManager.export( - quant_avgpool, - export_path=export_onnx_path, - input_t=input_quant_tensor, - ) - model = ModelWrapper(export_onnx_path) + input_tensor = torch.from_numpy(input_array).float() - # Statically set the additional inputs generated by the BrevitasONNXManager - model.graph.input.remove(model.graph.input[3]) - model.graph.input.remove(model.graph.input[2]) - model.graph.input.remove(model.graph.input[1]) - model.set_initializer("1", scale_array) - model.set_initializer("2", np.array(0.0).astype(np.float32)) - model.set_initializer("3", np.array(input_bit_width).astype(np.float32)) - model.save(export_onnx_path) + # export + export_qonnx( + model_brevitas, + export_path=export_onnx_path, + input_t=input_tensor, + ) + model = ModelWrapper(export_onnx_path) + model.save(export_onnx_path) - qonnx_cleanup(export_onnx_path, out_file=export_onnx_path) - model = ModelWrapper(export_onnx_path) - model = model.transform(ConvertQONNXtoFINN()) - model.save(export_onnx_path) - else: - FINNManager.export( - quant_avgpool, export_path=export_onnx_path, input_t=input_quant_tensor - ) + qonnx_cleanup(export_onnx_path, out_file=export_onnx_path) model = ModelWrapper(export_onnx_path) + model = model.transform(ConvertQONNXtoFINN()) model = model.transform(InferShapes()) model = model.transform(InferDataTypes()) # reference brevitas output - ref_output_array = quant_avgpool(input_quant_tensor).detach().numpy() + ref_output_array = model_brevitas(input_tensor).detach().numpy() # finn output - if QONNX_export: - # Manually apply the Quant tensor scaling for QONNX - idict = {model.graph.input[0].name: input_array * scale_array} - else: - idict = {model.graph.input[0].name: input_array} + idict = {model.graph.input[0].name: input_array} odict = oxe.execute_onnx(model, idict, True) finn_output = odict[model.graph.output[0].name] # compare outputs assert np.isclose(ref_output_array, finn_output).all() # cleanup - # assert False os.remove(export_onnx_path) diff --git a/tests/brevitas/test_brevitas_cnv.py b/tests/brevitas/test_brevitas_cnv.py index 62aab2e3c2b85c6462c24194c917bdc2d8eec448..1a96815105b70a9bc58d51a8214c15bbc09aa69c 100644 --- a/tests/brevitas/test_brevitas_cnv.py +++ b/tests/brevitas/test_brevitas_cnv.py @@ -30,11 +30,10 @@ import pkg_resources as pk import pytest -import brevitas.onnx as bo import numpy as np import os import torch -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants from qonnx.transformation.general import GiveUniqueNodeNames, RemoveStaticGraphInputs @@ -58,13 +57,13 @@ def test_brevitas_cnv_export_exec(wbits, abits, QONNX_export): cnv = get_test_model_trained("CNV", wbits, abits) ishape = (1, 3, 32, 32) if QONNX_export: - BrevitasONNXManager.export(cnv, ishape, export_onnx_path) + export_qonnx(cnv, torch.randn(ishape), export_onnx_path) qonnx_cleanup(export_onnx_path, out_file=export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(ConvertQONNXtoFINN()) model.save(export_onnx_path) else: - bo.export_finn_onnx(cnv, ishape, export_onnx_path) + export_finn_onnx(cnv, torch.randn(ishape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(GiveUniqueNodeNames()) model = model.transform(InferShapes()) diff --git a/tests/brevitas/test_brevitas_debug.py b/tests/brevitas/test_brevitas_debug.py index 181d610fff7a703a8ccbcf3bbb19bed2e5d7e89d..547c026e2174e1b46a0e72967076f32db73b18a5 100644 --- a/tests/brevitas/test_brevitas_debug.py +++ b/tests/brevitas/test_brevitas_debug.py @@ -34,7 +34,7 @@ import onnx import onnx.numpy_helper as nph import os import torch -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants @@ -58,7 +58,7 @@ def test_brevitas_debug(QONNX_export, QONNX_FINN_conversion): ishape = (1, 1, 28, 28) if QONNX_export: dbg_hook = bo.enable_debug(fc, proxy_level=True) - BrevitasONNXManager.export(fc, ishape, finn_onnx) + export_qonnx(fc, torch.randn(ishape), finn_onnx) # DebugMarkers have the brevitas.onnx domain, so that needs adjusting model = ModelWrapper(finn_onnx) dbg_nodes = model.get_nodes_by_op_type("DebugMarker") @@ -72,7 +72,7 @@ def test_brevitas_debug(QONNX_export, QONNX_FINN_conversion): model.save(finn_onnx) else: dbg_hook = bo.enable_debug(fc) - bo.export_finn_onnx(fc, ishape, finn_onnx) + export_finn_onnx(fc, torch.randn(ishape), finn_onnx) model = ModelWrapper(finn_onnx) # DebugMarkers have the brevitas.onnx domain, so that needs adjusting # ToDo: We should probably have transformation pass, which does this diff --git a/tests/brevitas/test_brevitas_fc.py b/tests/brevitas/test_brevitas_fc.py index 211fdb629b7c0465a145a094bab428064227afc9..3aaa96f9a5f74112cdfe2a90c425eec55661a3b1 100644 --- a/tests/brevitas/test_brevitas_fc.py +++ b/tests/brevitas/test_brevitas_fc.py @@ -28,12 +28,11 @@ import pytest -import brevitas.onnx as bo import numpy as np import onnx import onnx.numpy_helper as nph import torch -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants @@ -68,13 +67,13 @@ def test_brevitas_fc_onnx_export_and_exec(size, wbits, abits, QONNX_export): fc = get_test_model_trained(size, wbits, abits) ishape = (1, 1, 28, 28) if QONNX_export: - BrevitasONNXManager.export(fc, ishape, finn_onnx) + export_qonnx(fc, torch.randn(ishape), finn_onnx) qonnx_cleanup(finn_onnx, out_file=finn_onnx) model = ModelWrapper(finn_onnx) model = model.transform(ConvertQONNXtoFINN()) model.save(finn_onnx) else: - bo.export_finn_onnx(fc, ishape, finn_onnx) + export_finn_onnx(fc, torch.randn(ishape), finn_onnx) model = ModelWrapper(finn_onnx) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/brevitas/test_brevitas_mobilenet.py b/tests/brevitas/test_brevitas_mobilenet.py index b1475b6f4ec8c4a6ed34b4249b961031780d4be8..b469b197fa7b86db32c6ea9a79485c3794b8b3c4 100644 --- a/tests/brevitas/test_brevitas_mobilenet.py +++ b/tests/brevitas/test_brevitas_mobilenet.py @@ -28,9 +28,9 @@ import pytest -import brevitas.onnx as bo import numpy as np import torch +from brevitas.export import export_finn_onnx from PIL import Image from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper @@ -76,7 +76,7 @@ def test_brevitas_mobilenet(): std = 0.226 ch = 3 preproc = NormalizePreProc(mean, std, ch) - bo.export_finn_onnx(preproc, (1, 3, 224, 224), preproc_onnx) + export_finn_onnx(preproc, torch.randn(1, 3, 224, 224), preproc_onnx) preproc_model = ModelWrapper(preproc_onnx) # set input finn datatype to UINT8 preproc_model.set_tensor_datatype( @@ -89,7 +89,7 @@ def test_brevitas_mobilenet(): finn_onnx = export_onnx_path + "/quant_mobilenet_v1_4b_exported.onnx" mobilenet = get_test_model_trained("mobilenet", 4, 4) - bo.export_finn_onnx(mobilenet, (1, 3, 224, 224), finn_onnx) + export_finn_onnx(mobilenet, torch.randn(1, 3, 224, 224), finn_onnx) # do forward pass in PyTorch/Brevitas input_tensor = preproc.forward(img_torch) @@ -121,4 +121,6 @@ def test_brevitas_mobilenet(): produced = odict[model.graph.output[0].name] produced_prob = odict["TopK_0_out0"] * a0 assert (produced.flatten() == expected_top5).all() - assert np.isclose(produced_prob.flatten(), expected_top5_prob).all() + assert np.isclose( + produced_prob.flatten(), expected_top5_prob, atol=2.2 * 1e-1 + ).all() diff --git a/tests/brevitas/test_brevitas_non_scaled_quanthardtanh_export.py b/tests/brevitas/test_brevitas_non_scaled_quanthardtanh_export.py index 5d70acb10264dc10a3681589075507f06a9c903b..ad6a7e53de993b76f5b35dadd4e257c8bd88f4de 100644 --- a/tests/brevitas/test_brevitas_non_scaled_quanthardtanh_export.py +++ b/tests/brevitas/test_brevitas_non_scaled_quanthardtanh_export.py @@ -28,7 +28,6 @@ import pytest -import brevitas.onnx as bo import numpy as np import onnx # noqa import os @@ -36,7 +35,7 @@ import torch from brevitas.core.quant import QuantType from brevitas.core.restrict_val import RestrictValueType from brevitas.core.scaling import ScalingImplType -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from brevitas.nn import QuantHardTanh from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.infer_shapes import InferShapes @@ -78,13 +77,13 @@ def test_brevitas_act_export_qhardtanh_nonscaled( ) if QONNX_export: m_path = export_onnx_path - BrevitasONNXManager.export(b_act, ishape, m_path) + export_qonnx(b_act, torch.randn(ishape), m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: - bo.export_finn_onnx(b_act, ishape, export_onnx_path) + export_finn_onnx(b_act, torch.randn(ishape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype( diff --git a/tests/brevitas/test_brevitas_qconv2d.py b/tests/brevitas/test_brevitas_qconv2d.py index 214c55e5fd8b8c25c1ccca880f76690556af6397..faeb3ff48e2d7157008a87eab544766c83dc37d2 100644 --- a/tests/brevitas/test_brevitas_qconv2d.py +++ b/tests/brevitas/test_brevitas_qconv2d.py @@ -28,7 +28,6 @@ import pytest -import brevitas.onnx as bo import numpy as np import os import torch @@ -36,7 +35,7 @@ from brevitas.core.quant import QuantType from brevitas.core.restrict_val import RestrictValueType from brevitas.core.scaling import ScalingImplType from brevitas.core.stats import StatsOp -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from brevitas.nn import QuantConv2d from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper @@ -96,13 +95,13 @@ def test_brevitas_QConv2d(dw, bias, in_channels, QONNX_export): b_conv.eval() if QONNX_export: m_path = export_onnx_path - BrevitasONNXManager.export(b_conv, ishape, m_path) + export_qonnx(b_conv, torch.randn(ishape), m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: - bo.export_finn_onnx(b_conv, ishape, export_onnx_path) + export_finn_onnx(b_conv, torch.randn(ishape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = np.random.uniform(low=-1.0, high=1.0, size=ishape).astype(np.float32) diff --git a/tests/brevitas/test_brevitas_qlinear.py b/tests/brevitas/test_brevitas_qlinear.py index bcd75a545544122c1faacf4c321b19a489defe85..1ad52fb5df9fff6584fb6b649481377f32fa666d 100644 --- a/tests/brevitas/test_brevitas_qlinear.py +++ b/tests/brevitas/test_brevitas_qlinear.py @@ -28,12 +28,11 @@ import pytest -import brevitas.onnx as bo import numpy as np import os import torch from brevitas.core.quant import QuantType -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from brevitas.nn import QuantLinear from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper @@ -75,13 +74,13 @@ def test_brevitas_qlinear( b_linear.eval() if QONNX_export: m_path = export_onnx_path - BrevitasONNXManager.export(b_linear, i_shape, m_path) + export_qonnx(b_linear, torch.randn(i_shape), m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: - bo.export_finn_onnx(b_linear, i_shape, export_onnx_path) + export_finn_onnx(b_linear, torch.randn(i_shape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = gen_finn_dt_tensor(i_dtype, i_shape) diff --git a/tests/brevitas/test_brevitas_relu_act_export.py b/tests/brevitas/test_brevitas_relu_act_export.py index 3dc46ec31e49d7115b19b3373d54be6ddc29bb80..a4657d79248c00227c12e9c2bdf7453dfc49706a 100644 --- a/tests/brevitas/test_brevitas_relu_act_export.py +++ b/tests/brevitas/test_brevitas_relu_act_export.py @@ -28,20 +28,16 @@ import pytest -import brevitas.onnx as bo import numpy as np import onnx # noqa import os import torch -from brevitas.core.quant import QuantType -from brevitas.core.restrict_val import RestrictValueType from brevitas.core.scaling import ScalingImplType -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from brevitas.nn import QuantReLU from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.infer_shapes import InferShapes from qonnx.util.cleanup import cleanup as qonnx_cleanup -from torch import nn import finn.core.onnx_exec as oxe from finn.transformation.qonnx.convert_qonnx_to_finn import ConvertQONNXtoFINN @@ -51,63 +47,35 @@ export_onnx_path = "test_brevitas_relu_act_export.onnx" @pytest.mark.brevitas_export @pytest.mark.parametrize("abits", [2, 4, 8]) -@pytest.mark.parametrize("max_val", [1.0, 1.5, 1 - 2 ** (-7)]) -@pytest.mark.parametrize( - "scaling_impl_type", [ScalingImplType.CONST, ScalingImplType.PARAMETER] -) +@pytest.mark.parametrize("ishape", [(1, 15), (1, 32, 1, 1)]) @pytest.mark.parametrize("QONNX_export", [False, True]) -def test_brevitas_act_export_relu(abits, max_val, scaling_impl_type, QONNX_export): - min_val = -1.0 - ishape = (1, 15) +def test_brevitas_act_export_relu( + abits, + ishape, + QONNX_export, +): b_act = QuantReLU( bit_width=abits, - max_val=max_val, - scaling_impl_type=scaling_impl_type, - restrict_scaling_type=RestrictValueType.LOG_FP, - quant_type=QuantType.INT, ) - if scaling_impl_type == ScalingImplType.PARAMETER: - checkpoint = { - "act_quant_proxy.fused_activation_quant_proxy.tensor_quant.\ -scaling_impl.learned_value": torch.tensor( - 0.49 - ).type( - torch.FloatTensor - ) - } - b_act.load_state_dict(checkpoint) if QONNX_export: m_path = export_onnx_path - BrevitasONNXManager.export(b_act, ishape, m_path) + export_qonnx(b_act, torch.randn(ishape), m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: - bo.export_finn_onnx(b_act, ishape, export_onnx_path) + export_finn_onnx(b_act, torch.randn(ishape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) - inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype( - np.float32 - ) + inp_tensor = np.random.uniform(low=-1.0, high=6.0, size=ishape).astype(np.float32) idict = {model.graph.input[0].name: inp_tensor} odict = oxe.execute_onnx(model, idict, True) produced = odict[model.graph.output[0].name] inp_tensor = torch.from_numpy(inp_tensor).float() b_act.eval() expected = b_act.forward(inp_tensor).detach().numpy() - if not np.isclose(produced, expected, atol=1e-3).all(): - print(abits, max_val, scaling_impl_type) - print("scale: ", b_act.quant_act_scale().type(torch.FloatTensor).detach()) - if abits < 5: - print( - "thres:", - ", ".join(["{:8.4f}".format(x) for x in b_act.export_thres[0]]), - ) - print("input:", ", ".join(["{:8.4f}".format(x) for x in inp_tensor[0]])) - print("prod :", ", ".join(["{:8.4f}".format(x) for x in produced[0]])) - print("expec:", ", ".join(["{:8.4f}".format(x) for x in expected[0]])) assert np.isclose(produced, expected, atol=1e-3).all() os.remove(export_onnx_path) @@ -115,148 +83,40 @@ scaling_impl.learned_value": torch.tensor( @pytest.mark.brevitas_export @pytest.mark.parametrize("abits", [2, 4, 8]) -@pytest.mark.parametrize("max_val", [1.0, 1.5, 1 - 2 ** (-7)]) -@pytest.mark.parametrize("scaling_per_channel", [True, False]) +@pytest.mark.parametrize("ishape", [(1, 15, 4, 4), (1, 32, 1, 1)]) @pytest.mark.parametrize("QONNX_export", [False, True]) -def test_brevitas_act_export_relu_imagenet( - abits, max_val, scaling_per_channel, QONNX_export +def test_brevitas_act_export_relu_channel( + abits, + ishape, + QONNX_export, ): - out_channels = 32 - ishape = (1, out_channels, 1, 1) - min_val = -1.0 + + ch = ishape[1] b_act = QuantReLU( bit_width=abits, - quant_type=QuantType.INT, - scaling_impl_type=ScalingImplType.PARAMETER, - scaling_per_channel=scaling_per_channel, - restrict_scaling_type=RestrictValueType.LOG_FP, - scaling_min_val=2e-16, max_val=6.0, - return_quant_tensor=False, - per_channel_broadcastable_shape=(1, out_channels, 1, 1), + scaling_impl_type=ScalingImplType.CONST, + scaling_per_output_channel=True, + per_channel_broadcastable_shape=(1, ch, 1, 1), ) - if scaling_per_channel is True: - rand_tensor = (2) * torch.rand((1, out_channels, 1, 1)) - else: - rand_tensor = torch.tensor(1.2398) - checkpoint = { - "act_quant_proxy.fused_activation_quant_proxy.tensor_quant.\ -scaling_impl.learned_value": rand_tensor.type( - torch.FloatTensor - ) - } - b_act.load_state_dict(checkpoint) if QONNX_export: m_path = export_onnx_path - BrevitasONNXManager.export(b_act, ishape, m_path) + export_qonnx(b_act, torch.randn(ishape), m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: - bo.export_finn_onnx(b_act, ishape, export_onnx_path) + export_finn_onnx(b_act, torch.randn(ishape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) - inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype( - np.float32 - ) + inp_tensor = np.random.uniform(low=-1.0, high=6.0, size=ishape).astype(np.float32) idict = {model.graph.input[0].name: inp_tensor} odict = oxe.execute_onnx(model, idict, True) produced = odict[model.graph.output[0].name] inp_tensor = torch.from_numpy(inp_tensor).float() b_act.eval() expected = b_act.forward(inp_tensor).detach().numpy() - if not np.isclose(produced, expected, atol=1e-3).all(): - print(abits, max_val) - print("scale: ", b_act.quant_act_scale().type(torch.FloatTensor).detach()) - if abits < 5: - print( - "thres:", - ", ".join(["{:8.4f}".format(x) for x in b_act.export_thres[0]]), - ) - print("input:", ", ".join(["{:8.4f}".format(x) for x in inp_tensor[0]])) - print("prod :", ", ".join(["{:8.4f}".format(x) for x in produced[0]])) - print("expec:", ", ".join(["{:8.4f}".format(x) for x in expected[0]])) - - assert np.isclose(produced, expected, atol=1e-3).all() - os.remove(export_onnx_path) - - -class PyTorchTestModel(nn.Module): - def __init__(self, abits): - super(PyTorchTestModel, self).__init__() - out_channels = 32 - self.b_act = QuantReLU( - bit_width=abits, - quant_type=QuantType.INT, - scaling_impl_type=ScalingImplType.PARAMETER, - scaling_per_channel=True, - restrict_scaling_type=RestrictValueType.LOG_FP, - scaling_min_val=2e-16, - max_val=6.0, - return_quant_tensor=False, - per_channel_broadcastable_shape=(1, out_channels, 1, 1), - ) - - def forward(self, x): - act_out = self.b_act(x) - y0 = act_out * 2.0 - y1 = act_out * -1.0 - y = y0 + y1 - return y - - -@pytest.mark.brevitas_export -@pytest.mark.parametrize("abits", [2, 4, 8]) -@pytest.mark.parametrize("max_val", [1.0, 1.5, 1 - 2 ** (-7)]) -@pytest.mark.parametrize("scaling_per_channel", [True]) -@pytest.mark.parametrize("QONNX_export", [True]) -def test_brevitas_act_export_relu_forking( - abits, max_val, scaling_per_channel, QONNX_export -): - out_channels = 32 - ishape = (1, out_channels, 1, 1) - min_val = -1.0 - model_pyt = PyTorchTestModel(abits) - - rand_tensor = (2) * torch.rand((1, out_channels, 1, 1)) - - checkpoint = { - "b_act.act_quant_proxy.fused_activation_quant_proxy." - "tensor_quant.scaling_impl.learned_value": rand_tensor.type(torch.FloatTensor) - } - model_pyt.load_state_dict(checkpoint) - - if QONNX_export: - m_path = export_onnx_path - BrevitasONNXManager.export(model_pyt, ishape, m_path) - qonnx_cleanup(m_path, out_file=m_path) - model = ModelWrapper(m_path) - model = model.transform(ConvertQONNXtoFINN()) - model.save(m_path) - - model = ModelWrapper(export_onnx_path) - model = model.transform(InferShapes()) - inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype( - np.float32 - ) - idict = {model.graph.input[0].name: inp_tensor} - odict = oxe.execute_onnx(model, idict, True) - produced = odict[model.graph.output[0].name] - inp_tensor = torch.from_numpy(inp_tensor).float() - model_pyt.eval() - expected = model_pyt.forward(inp_tensor).detach().numpy() - if not np.isclose(produced, expected, atol=1e-3).all(): - print(abits, max_val) - print("scale: ", model_pyt.quant_act_scale().type(torch.FloatTensor).detach()) - if abits < 5: - print( - "thres:", - ", ".join(["{:8.4f}".format(x) for x in model_pyt.export_thres[0]]), - ) - print("input:", ", ".join(["{:8.4f}".format(x) for x in inp_tensor[0]])) - print("prod :", ", ".join(["{:8.4f}".format(x) for x in produced[0]])) - print("expec:", ", ".join(["{:8.4f}".format(x) for x in expected[0]])) assert np.isclose(produced, expected, atol=1e-3).all() os.remove(export_onnx_path) diff --git a/tests/brevitas/test_brevitas_scaled_qhardtanh_export.py b/tests/brevitas/test_brevitas_scaled_qhardtanh_export.py index 403d406105e8e60e6ef87f833c495dc2974de68c..d35cc8d2dda58f2be188622cdac59c19cee25e13 100644 --- a/tests/brevitas/test_brevitas_scaled_qhardtanh_export.py +++ b/tests/brevitas/test_brevitas_scaled_qhardtanh_export.py @@ -28,7 +28,6 @@ import pytest -import brevitas.onnx as bo import numpy as np import onnx # noqa import os @@ -36,7 +35,7 @@ import torch from brevitas.core.quant import QuantType from brevitas.core.restrict_val import RestrictValueType from brevitas.core.scaling import ScalingImplType -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from brevitas.nn import QuantHardTanh from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.infer_shapes import InferShapes @@ -91,13 +90,13 @@ tensor_quant.scaling_impl.learned_value": torch.tensor( b_act.load_state_dict(checkpoint) if QONNX_export: m_path = export_onnx_path - BrevitasONNXManager.export(b_act, ishape, m_path) + export_qonnx(b_act, torch.randn(ishape), m_path) qonnx_cleanup(m_path, out_file=m_path) model = ModelWrapper(m_path) model = model.transform(ConvertQONNXtoFINN()) model.save(m_path) else: - bo.export_finn_onnx(b_act, ishape, export_onnx_path) + export_finn_onnx(b_act, torch.randn(ishape), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) inp_tensor = np.random.uniform(low=min_val, high=max_val, size=ishape).astype( diff --git a/tests/brevitas/test_brevitas_selu_act_export.py b/tests/brevitas/test_brevitas_selu_act_export.py new file mode 100644 index 0000000000000000000000000000000000000000..3f4807c5d7286c265a856d73e9aaa886f342555e --- /dev/null +++ b/tests/brevitas/test_brevitas_selu_act_export.py @@ -0,0 +1,74 @@ +# Copyright (c) 2023, Advanced Micro Devices, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of Xilinx nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import pytest + +import numpy as np +import onnx # noqa +import os +import torch +from brevitas.export import export_qonnx +from brevitas.nn import QuantIdentity +from qonnx.core.modelwrapper import ModelWrapper +from qonnx.util.basic import get_preferred_onnx_opset +from qonnx.util.cleanup import cleanup as qonnx_cleanup + +import finn.core.onnx_exec as oxe +from finn.transformation.qonnx.convert_qonnx_to_finn import ConvertQONNXtoFINN + + +@pytest.mark.brevitas_export +@pytest.mark.parametrize("abits", [2, 4, 8]) +@pytest.mark.parametrize("ishape", [(1, 15), (1, 32, 1, 1)]) +@pytest.mark.parametrize("narrow", [True, False]) +def test_brevitas_act_export_selu(abits, ishape, narrow): + export_path = "test_brevitas_selu_act_export_%s.onnx" % str(abits) + b_act = torch.nn.Sequential( + torch.nn.SELU(), QuantIdentity(bit_width=abits, narrow=narrow) + ) + + export_qonnx( + b_act, + torch.randn(ishape), + export_path, + opset_version=get_preferred_onnx_opset(), + ) + qonnx_cleanup(export_path, out_file=export_path) + model = ModelWrapper(export_path) + model = model.transform(ConvertQONNXtoFINN()) + + inp_tensor = np.random.uniform(low=-1.0, high=6.0, size=ishape).astype(np.float32) + idict = {model.graph.input[0].name: inp_tensor} + odict = oxe.execute_onnx(model, idict, True) + produced = odict[model.graph.output[0].name] + inp_tensor = torch.from_numpy(inp_tensor).float() + b_act.eval() + expected = b_act.forward(inp_tensor).detach().numpy() + + assert np.isclose(produced, expected, atol=1e-3).all() + os.remove(export_path) diff --git a/tests/brevitas/test_brevitas_validate_mobilenet.py b/tests/brevitas/test_brevitas_validate_mobilenet.py index 55915838e8a10d19d3aa6446d0bb667785bbd905..20e8ddad501e8b07502decef6eacd4afe061917a 100644 --- a/tests/brevitas/test_brevitas_validate_mobilenet.py +++ b/tests/brevitas/test_brevitas_validate_mobilenet.py @@ -35,6 +35,7 @@ import os import torch import torchvision.datasets as datasets import torchvision.transforms as transforms +from brevitas.export import export_finn_onnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants from qonnx.transformation.general import ( @@ -113,7 +114,7 @@ def test_brevitas_compare_exported_mobilenet(): # export preprocessing preproc_onnx = export_onnx_path + "/quant_mobilenet_v1_4b_preproc.onnx" preproc = NormalizePreProc(mean, std, ch) - bo.export_finn_onnx(preproc, (1, 3, 224, 224), preproc_onnx) + export_finn_onnx(preproc, torch.randn(1, 3, 224, 224), preproc_onnx) preproc_model = ModelWrapper(preproc_onnx) preproc_model = preproc_model.transform(InferShapes()) preproc_model = preproc_model.transform(GiveUniqueNodeNames()) @@ -124,7 +125,7 @@ def test_brevitas_compare_exported_mobilenet(): mobilenet = get_test_model_trained("mobilenet", 4, 4) if debug_mode: dbg_hook = bo.enable_debug(mobilenet) - bo.export_finn_onnx(mobilenet, (1, 3, 224, 224), finn_onnx) + export_finn_onnx(mobilenet, torch.randn(1, 3, 224, 224), finn_onnx) model = ModelWrapper(finn_onnx) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/end2end/test_end2end_bnn_pynq.py b/tests/end2end/test_end2end_bnn_pynq.py index 858363d6d31c7c17803bffdb87e7b168dec4b76d..27aaa1986df15b4d6f51462679b1f8e7179684cb 100644 --- a/tests/end2end/test_end2end_bnn_pynq.py +++ b/tests/end2end/test_end2end_bnn_pynq.py @@ -28,20 +28,16 @@ import pytest -import brevitas.onnx as bo import numpy as np # as of Feb'20 there is a bug that segfaults ONNX shape inference if we # import pytorch before onnx, so we make sure to import onnx first import onnx # NOQA import os -import subprocess import torch import warnings -from brevitas.export.onnx.generic.manager import BrevitasONNXManager -from collections import OrderedDict +from brevitas.export import export_finn_onnx, export_qonnx from dataset_loading import cifar, mnist -from datetime import datetime from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp @@ -60,13 +56,12 @@ from qonnx.transformation.insert_topk import InsertTopK from qonnx.transformation.lower_convs_to_matmul import LowerConvsToMatMul from qonnx.transformation.merge_onnx_models import MergeONNXModels from qonnx.util.cleanup import cleanup as qonnx_cleanup -from scipy.stats import linregress import finn.transformation.fpgadataflow.convert_to_hls_layers as to_hls import finn.transformation.streamline.absorb as absorb from finn.analysis.fpgadataflow.dataflow_performance import dataflow_performance from finn.core.onnx_exec import execute_onnx -from finn.core.throughput_test import throughput_test_remote, throughput_test_rtlsim +from finn.core.throughput_test import throughput_test_rtlsim from finn.transformation.fpgadataflow.annotate_cycles import AnnotateCycles from finn.transformation.fpgadataflow.annotate_resources import AnnotateResources from finn.transformation.fpgadataflow.compile_cppsim import CompileCppSim @@ -76,8 +71,13 @@ from finn.transformation.fpgadataflow.create_dataflow_partition import ( from finn.transformation.fpgadataflow.create_stitched_ip import CreateStitchedIP from finn.transformation.fpgadataflow.hlssynth_ip import HLSSynthIP from finn.transformation.fpgadataflow.insert_dwc import InsertDWC -from finn.transformation.fpgadataflow.make_deployment import DeployToPYNQ from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver +from finn.transformation.fpgadataflow.minimize_accumulator_width import ( + MinimizeAccumulatorWidth, +) +from finn.transformation.fpgadataflow.minimize_weight_bit_width import ( + MinimizeWeightBitWidth, +) from finn.transformation.fpgadataflow.prepare_cppsim import PrepareCppSim from finn.transformation.fpgadataflow.prepare_ip import PrepareIP from finn.transformation.fpgadataflow.set_exec_mode import SetExecMode @@ -90,7 +90,6 @@ from finn.transformation.streamline.reorder import ( MoveScalarLinearPastInvariants, ) from finn.util.basic import get_finn_root -from finn.util.gdrive import upload_to_end2end_dashboard from finn.util.pytorch import ToTensor from finn.util.test import ( execute_parent, @@ -117,24 +116,6 @@ def get_checkpoint_name(topology, wbits, abits, QONNX_export, step): ) -def get_dashboard_data(topology, wbits, abits): - stats_file = build_dir + "/end2end_%s_w%da%d.txt" % (topology, wbits, abits) - stats_dict = OrderedDict() - if os.path.isfile(stats_file): - with open(stats_file, "r") as f: - stats_dict_txt = f.read() - stats_dict = eval(stats_dict_txt) - return stats_dict - - -def update_dashboard_data(topology, wbits, abits, key, val): - stats_dict = get_dashboard_data(topology, wbits, abits) - stats_dict[key] = val - stats_file = build_dir + "/end2end_%s_w%da%d.txt" % (topology, wbits, abits) - with open(stats_file, "w") as f: - f.write(str(stats_dict)) - - def fold_tfc(model): fc_layers = model.get_nodes_by_op_type("MatrixVectorActivation") # (PE, SIMD, ramstyle) for each layer @@ -269,7 +250,7 @@ def measure_top1_accuracy(model_chkpt, dataset, parent_chkpt=None): raise Exception("Unrecognized dataset") # move from dataset_loader layout to ONNX layout: NHWC -> NCHW testx = testx.transpose(0, 3, 1, 2) - model = ModelWrapper(model_chkpt) + model = load_test_checkpoint_or_skip(model_chkpt) iname = model.graph.input[0].name oname = model.graph.output[0].name if parent_chkpt is None: @@ -323,22 +304,13 @@ class TestEnd2End: (model, ishape) = get_trained_network_and_ishape(topology, wbits, abits) chkpt_name = get_checkpoint_name(topology, wbits, abits, QONNX_export, "export") if QONNX_export: - BrevitasONNXManager.export(model, ishape, chkpt_name) + export_qonnx(model, torch.randn(ishape), chkpt_name) qonnx_cleanup(chkpt_name, out_file=chkpt_name) model = ModelWrapper(chkpt_name) model = model.transform(ConvertQONNXtoFINN()) model.save(chkpt_name) else: - bo.export_finn_onnx(model, ishape, chkpt_name) - nname = "%s_w%da%d" % (topology, wbits, abits) - update_dashboard_data(topology, wbits, abits, "network", nname) - dtstr = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - update_dashboard_data(topology, wbits, abits, "datetime", dtstr) - finn_commit = subprocess.check_output( - ["git", "rev-parse", "HEAD"], cwd=get_finn_root() - ) - finn_commit = finn_commit.decode("utf-8").strip() - update_dashboard_data(topology, wbits, abits, "finn-commit", finn_commit) + export_finn_onnx(model, torch.randn(ishape), chkpt_name) assert os.path.isfile(chkpt_name) def test_import_and_tidy(self, topology, wbits, abits, QONNX_export): @@ -369,7 +341,7 @@ class TestEnd2End: chkpt_preproc_name = get_checkpoint_name( topology, wbits, abits, QONNX_export, "preproc" ) - bo.export_finn_onnx(totensor_pyt, ishape, chkpt_preproc_name) + export_finn_onnx(totensor_pyt, torch.randn(ishape), chkpt_preproc_name) assert os.path.isfile(chkpt_preproc_name) # join preprocessing and core model pre_model = ModelWrapper(chkpt_preproc_name) @@ -511,11 +483,23 @@ class TestEnd2End: model = folding_fxn(model) model.save(get_checkpoint_name(topology, wbits, abits, QONNX_export, "fold")) + def test_minimize_bit_width(self, topology, wbits, abits, QONNX_export): + prev_chkpt_name = get_checkpoint_name( + topology, wbits, abits, QONNX_export, "fold" + ) + model = load_test_checkpoint_or_skip(prev_chkpt_name) + model = model.transform(MinimizeAccumulatorWidth()) + model = model.transform(MinimizeWeightBitWidth()) + curr_chkpt_name = get_checkpoint_name( + topology, wbits, abits, QONNX_export, "minimize_bit_width" + ) + model.save(curr_chkpt_name) + @pytest.mark.slow @pytest.mark.vivado def test_cppsim(self, topology, wbits, abits, QONNX_export): prev_chkpt_name = get_checkpoint_name( - topology, wbits, abits, QONNX_export, "fold" + topology, wbits, abits, QONNX_export, "minimize_bit_width" ) model = load_test_checkpoint_or_skip(prev_chkpt_name) model = model.transform(PrepareCppSim()) @@ -627,10 +611,6 @@ class TestEnd2End: ret = throughput_test_rtlsim(model, batchsize=batchsize) res_cycles = ret["cycles"] est_cycles = latency + cycles_per_sample_est * batchsize - # warnings.warn("Estimated & rtlsim performance: " + str(perf)) - # for (k, v) in perf.items(): - # update_dashboard_data(topology, wbits, abits, k, v) - update_dashboard_data(topology, wbits, abits, "cycles_rtlsim", latency) assert (abs(res_cycles - est_cycles) / res_cycles) < 0.15 @pytest.mark.slow @@ -674,10 +654,6 @@ class TestEnd2End: cfg = get_build_env(kind, target_clk_ns) model = model.transform(cfg["build_fxn"]) model = model.transform(AnnotateResources("synth")) - synth_dct = eval(model.get_metadata_prop("res_total_top_synth")) - for (k, v) in synth_dct.items(): - update_dashboard_data(topology, wbits, abits, k, v) - update_dashboard_data(topology, wbits, abits, "board", cfg["board"]) model.save( get_checkpoint_name(topology, wbits, abits, QONNX_export, "build_" + kind) ) @@ -698,121 +674,3 @@ class TestEnd2End: model.save( get_checkpoint_name(topology, wbits, abits, QONNX_export, "driver_" + kind) ) - - @pytest.mark.parametrize("kind", ["zynq", "alveo"]) - def test_deploy(self, topology, wbits, abits, QONNX_export, kind): - prev_chkpt_name = get_checkpoint_name( - topology, wbits, abits, QONNX_export, "driver_" + kind - ) - model = load_test_checkpoint_or_skip(prev_chkpt_name) - cfg = get_build_env(kind, target_clk_ns) - if cfg["ip"] == "": - pytest.skip("PYNQ board IP address not specified") - model = model.transform( - DeployToPYNQ( - cfg["ip"], - cfg["port"], - cfg["username"], - cfg["password"], - cfg["target_dir"], - ) - ) - # save the model to be able to link it to the parent - model.save( - get_checkpoint_name(topology, wbits, abits, QONNX_export, "deploy_" + kind) - ) - - @pytest.mark.parametrize("kind", ["zynq", "alveo"]) - def test_run_on_hw(self, topology, wbits, abits, QONNX_export, kind): - prev_chkpt_name = get_checkpoint_name( - topology, wbits, abits, QONNX_export, "deploy_" + kind - ) - model = load_test_checkpoint_or_skip(prev_chkpt_name) # NOQA - cfg = get_build_env(kind, target_clk_ns) - if cfg["ip"] == "": - pytest.skip("PYNQ board IP address not specified") - (input_tensor_npy, output_tensor_npy) = get_golden_io_pair( - topology, wbits, abits, return_topk=1 - ) - parent_model = load_test_checkpoint_or_skip( - get_checkpoint_name(topology, wbits, abits, QONNX_export, "dataflow_parent") - ) - iname = parent_model.graph.input[0].name - oname = parent_model.graph.output[0].name - sdp_node = parent_model.get_nodes_by_op_type("StreamingDataflowPartition")[0] - sdp_node = getCustomOp(sdp_node) - sdp_node.set_nodeattr("model", prev_chkpt_name) - ret = execute_onnx(parent_model, {iname: input_tensor_npy}, True) - y = ret[oname] - assert np.isclose(y, output_tensor_npy).all() - - @pytest.mark.parametrize("kind", ["zynq", "alveo"]) - def test_throughput_hw(self, topology, wbits, abits, QONNX_export, kind): - prev_chkpt_name = get_checkpoint_name( - topology, wbits, abits, QONNX_export, "deploy_" + kind - ) - end2end_example = "%s_w%da%d_%s" % (topology, wbits, abits, kind) - model = load_test_checkpoint_or_skip(prev_chkpt_name) # NOQA - cfg = get_build_env(kind, target_clk_ns) - if cfg["ip"] == "": - pytest.skip("PYNQ board IP address not specified") - ret = dict() - # try a range of batch sizes, some may fail due to insufficient DMA - # buffers - bsize_range_in = [8**i for i in range(5)] - bsize_range = [] - for bsize in bsize_range_in: - res = throughput_test_remote(model, bsize) - if res is not None: - ret[bsize] = res - bsize_range.append(bsize) - else: - # assume we reached largest possible N - break - y = [ret[key]["runtime[ms]"] for key in bsize_range] - lrret = linregress(bsize_range, y) - ret_str = "" - ret_str += "\n" + "%s Throughput Test Results" % end2end_example - ret_str += "\n" + "-----------------------------" - ret_str += "\n" + "From linear regression:" - ret_str += "\n" + "Invocation overhead: %f ms" % lrret.intercept - ret_str += "\n" + "Time per sample: %f ms" % lrret.slope - ret_str += "\n" + "Raw data:" - - ret_str += "\n" + "{:<8} {:<16} {:<16} {:<16} {:<16} {:<16}".format( - "N", "runtime[ms]", "fclk[mhz]", "fps", "DRAM rd[MB/s]", "DRAM wr[MB/s]" - ) - for k in bsize_range: - v = ret[k] - ret_str += "\n" + "{:<8} {:<16} {:<16} {:<16} {:<16} {:<16}".format( - k, - np.round(v["runtime[ms]"], 4), - v["fclk[mhz]"], - np.round(v["throughput[images/s]"], 2), - np.round(v["DRAM_in_bandwidth[MB/s]"], 2), - np.round(v["DRAM_out_bandwidth[MB/s]"], 2), - ) - ret_str += "\n" + "-----------------------------" - warnings.warn(ret_str) - largest_bsize = bsize_range[-1] - update_dashboard_data( - topology, wbits, abits, "fclk[mhz]", ret[largest_bsize]["fclk[mhz]"] - ) - update_dashboard_data( - topology, - wbits, - abits, - "throughput[images/s]", - ret[largest_bsize]["throughput[images/s]"], - ) - - def test_upload_results_to_dashboard(self, topology, wbits, abits, QONNX_export): - # ToDo: Extend the dashboard to also upload QONNX exported models? - if QONNX_export: - pytest.skip("Dashboard data upload is disabled for QONNX exported models.") - else: - dashboard_data = get_dashboard_data(topology, wbits, abits) - if len(dashboard_data.keys()) > 0: - upload_to_end2end_dashboard(dashboard_data) - else: - pytest.skip("No data to upload to dashboard") diff --git a/tests/end2end/test_end2end_cybsec_mlp.py b/tests/end2end/test_end2end_cybsec_mlp.py index 290afc308498490cbee2fc75c30e22bb474eb96a..5e402bdeb44a73a0cb750083cd6f4d431e9bb7c9 100644 --- a/tests/end2end/test_end2end_cybsec_mlp.py +++ b/tests/end2end/test_end2end_cybsec_mlp.py @@ -30,17 +30,14 @@ import pkg_resources as pk import pytest -import brevitas.onnx as bo import json import numpy as np import os import shutil -import subprocess import torch import torch.nn as nn -import wget from brevitas.core.quant import QuantType -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +from brevitas.export import export_finn_onnx, export_qonnx from brevitas.nn import QuantIdentity, QuantLinear, QuantReLU from brevitas.quant_tensor import QuantTensor from qonnx.core.datatype import DataType @@ -133,10 +130,10 @@ def test_end2end_cybsec_mlp_export(QONNX_export): ) if QONNX_export: - # With the BrevitasONNXManager we need to manually set + # With the onnx export from Brevitas we need to manually set # the FINN DataType at the input - BrevitasONNXManager.export( - model_for_export, input_shape, export_path=export_onnx_path + export_qonnx( + model_for_export, torch.randn(input_shape), export_path=export_onnx_path ) model = ModelWrapper(export_onnx_path) model.set_tensor_datatype(model.graph.input[0].name, DataType["BIPOLAR"]) @@ -146,7 +143,7 @@ def test_end2end_cybsec_mlp_export(QONNX_export): model = model.transform(ConvertQONNXtoFINN()) model.save(export_onnx_path) else: - bo.export_finn_onnx( + export_finn_onnx( model_for_export, export_path=export_onnx_path, input_t=input_qt ) assert os.path.isfile(export_onnx_path) @@ -223,65 +220,6 @@ def test_end2end_cybsec_mlp_build(QONNX_export): assert est_cycles_dict["MatrixVectorActivation_1"] == 64 with open(est_res_report, "r") as f: est_res_dict = json.load(f) - assert est_res_dict["total"]["LUT"] == 11360.0 + assert est_res_dict["total"]["LUT"] == 7904.0 assert est_res_dict["total"]["BRAM_18K"] == 36.0 shutil.copytree(output_dir + "/deploy", get_checkpoint_name("build", QONNX_export)) - - -@pytest.mark.end2end -@pytest.mark.xfail -@pytest.mark.parametrize("QONNX_export", [False, True]) -def test_end2end_cybsec_mlp_run_on_hw(QONNX_export): - build_env = get_build_env(build_kind, target_clk_ns) - assets_dir = pk.resource_filename("finn.qnn-data", "cybsec-mlp/") - deploy_dir = get_checkpoint_name("build", QONNX_export) - if not os.path.isdir(deploy_dir): - pytest.skip(deploy_dir + " not found from previous test step, skipping") - driver_dir = deploy_dir + "/driver" - assert os.path.isdir(driver_dir) - # put all assets into driver dir - shutil.copy(assets_dir + "/validate-unsw-nb15.py", driver_dir) - # put a copy of binarized dataset into driver dir - dataset_url = ( - "https://zenodo.org/record/4519767/files/unsw_nb15_binarized.npz?download=1" - ) - dataset_local = driver_dir + "/unsw_nb15_binarized.npz" - if not os.path.isfile(dataset_local): - wget.download(dataset_url, out=dataset_local) - assert os.path.isfile(dataset_local) - # create a shell script for running validation: 10 batches x 10 imgs - with open(driver_dir + "/validate.sh", "w") as f: - f.write( - """#!/bin/bash -cd %s/driver -echo %s | sudo -S python3.6 validate-unsw-nb15.py --batchsize=10 --limit_batches=10 - """ - % ( - build_env["target_dir"] + "/end2end_cybsecmlp_build", - build_env["password"], - ) - ) - # set up rsync command - remote_target = "%s@%s:%s" % ( - build_env["username"], - build_env["ip"], - build_env["target_dir"], - ) - rsync_res = subprocess.run(["rsync", "-avz", deploy_dir, remote_target]) - assert rsync_res.returncode == 0 - remote_verif_cmd = [ - "ssh", - "%s@%s" % (build_env["username"], build_env["ip"]), - "sh", - build_env["target_dir"] + "/end2end_cybsecmlp_build/driver/validate.sh", - ] - verif_res = subprocess.run( - remote_verif_cmd, - stdout=subprocess.PIPE, - universal_newlines=True, - input=build_env["password"], - ) - assert verif_res.returncode == 0 - log_output = verif_res.stdout.split("\n") - assert log_output[-3] == "batch 10 / 10 : total OK 93 NOK 7" - assert log_output[-2] == "Final accuracy: 93.000000" diff --git a/tests/end2end/test_end2end_mobilenet_v1.py b/tests/end2end/test_end2end_mobilenet_v1.py index 2f4df956acb79c2c4047e6430ccb6f17b76be2e0..3a3c0fe237a34bbd59f5c1de82232c429060f280 100644 --- a/tests/end2end/test_end2end_mobilenet_v1.py +++ b/tests/end2end/test_end2end_mobilenet_v1.py @@ -27,11 +27,11 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import pytest -import brevitas.onnx as bo import numpy as np import os import time import torch +from brevitas.export import export_finn_onnx from PIL import Image from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper @@ -95,7 +95,7 @@ def test_end2end_mobilenet_export(): std = 0.226 ch = 3 preproc = NormalizePreProc(mean, std, ch) - bo.export_finn_onnx(preproc, (1, 3, 224, 224), preproc_onnx) + export_finn_onnx(preproc, torch.randn(1, 3, 224, 224), preproc_onnx) preproc_model = ModelWrapper(preproc_onnx) # set input finn datatype to UINT8 preproc_model.set_tensor_datatype( @@ -111,7 +111,7 @@ def test_end2end_mobilenet_export(): # export mobilenet finn_onnx = build_dir + "/end2end_mobilenet_export.onnx" mobilenet = get_test_model_trained("mobilenet", 4, 4) - bo.export_finn_onnx(mobilenet, (1, 3, 224, 224), finn_onnx) + export_finn_onnx(mobilenet, torch.randn(1, 3, 224, 224), finn_onnx) # calculate golden output with pytorch/brevitas and save as .npy # get single image as input and prepare image diff --git a/tests/end2end/test_ext_weights.py b/tests/end2end/test_ext_weights.py index 0a92c74a38d64ade37d576f3830f3a5628c94d88..bef2e0ffa77cb96ff45956e380aeb376def61228 100644 --- a/tests/end2end/test_ext_weights.py +++ b/tests/end2end/test_ext_weights.py @@ -110,69 +110,3 @@ def test_end2end_ext_weights_build(): if os.path.isdir(get_checkpoint_name("build")): shutil.rmtree(get_checkpoint_name("build")) shutil.copytree(output_dir + "/deploy", get_checkpoint_name("build")) - - -@pytest.mark.board -@pytest.mark.end2end -@pytest.mark.xfail -def test_end2end_ext_weights_dataset(): - # make sure we have local copies of mnist dataset files - subprocess.check_output(["mkdir", "-p", mnist_local]) - for f in mnist_files: - if not os.path.isfile(mnist_local + "/" + f): - wget.download(mnist_url + "/" + f, out=mnist_local + "/" + f) - assert os.path.isfile(mnist_local + "/" + f) - # rsync to board - build_env = get_build_env(build_kind, target_clk_ns) - mnist_target = "%s@%s:%s" % (build_env["username"], build_env["ip"], "/tmp/") - - rsync_dataset_cmd = ["rsync", "-rv", mnist_local + "/", mnist_target] - subprocess.check_output(rsync_dataset_cmd) - - -@pytest.mark.end2end -@pytest.mark.xfail -def test_end2end_ext_weights_run_on_hw(): - build_env = get_build_env(build_kind, target_clk_ns) - deploy_dir = get_checkpoint_name("build") - if not os.path.isdir(deploy_dir): - pytest.skip(deploy_dir + " not found from previous test step, skipping") - driver_dir = deploy_dir + "/driver" - assert os.path.isdir(driver_dir) - # create a shell script for running validation: 10 batches x 10 imgs - with open(driver_dir + "/validate.sh", "w") as f: - f.write( - """#!/bin/bash -cd %s/driver -echo %s | sudo -S python3.6 validate.py --dataset mnist --bitfile %s - """ - % ( - build_env["target_dir"] + "/end2end_ext_weights_build", - build_env["password"], - "../bitfile/finn-accel.bit", - ) - ) - # set up rsync command - remote_target = "%s@%s:%s" % ( - build_env["username"], - build_env["ip"], - build_env["target_dir"], - ) - rsync_res = subprocess.run(["rsync", "-avz", deploy_dir, remote_target]) - assert rsync_res.returncode == 0 - remote_verif_cmd = [ - "ssh", - "%s@%s" % (build_env["username"], build_env["ip"]), - "sh", - build_env["target_dir"] + "/end2end_ext_weights_build/driver/validate.sh", - ] - verif_res = subprocess.run( - remote_verif_cmd, - stdout=subprocess.PIPE, - universal_newlines=True, - input=build_env["password"], - ) - assert verif_res.returncode == 0 - log_output = verif_res.stdout.split("\n") - assert log_output[-3] == "batch 100 / 100 : total OK 9296 NOK 704" - assert log_output[-2] == "Final accuracy: 92.960000" diff --git a/tests/fpgadataflow/test_convert_to_hls_conv_layer.py b/tests/fpgadataflow/test_convert_to_hls_conv_layer.py index de31ef0f125cb96ea82f953eadb9d5ccf7aab16c..7b2793712d41d2a73545c1b5632071334a2ac694 100644 --- a/tests/fpgadataflow/test_convert_to_hls_conv_layer.py +++ b/tests/fpgadataflow/test_convert_to_hls_conv_layer.py @@ -73,9 +73,6 @@ def test_convert_to_hls_conv_layer(conv_config, depthwise, use_rtl_swg, exec_mod if use_rtl_swg and exec_mode == "cppsim": pytest.skip("cppsim not supported for RTL SWG") - if use_rtl_swg and kernel_size == 1: - pytest.skip("1x1 kernel not supported by current RTL SWG") - if depthwise is True: group = out_chn = in_chn conv_param_shape = [out_chn, 1, kernel_size, kernel_size] @@ -164,7 +161,7 @@ def test_convert_to_hls_conv_layer(conv_config, depthwise, use_rtl_swg, exec_mod inp_dict = {model.graph.input[0].name: x} assert oxe.compare_execution(model, new_model, inp_dict) - if kernel_size == 1 and stride > 1 and pad == 0: + if not use_rtl_swg and kernel_size == 1 and stride > 1 and pad == 0: assert new_model.graph.node[1].op_type == "DownSampler" if exec_mode == "rtlsim": node = new_model.get_nodes_by_op_type("DownSampler")[0] diff --git a/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py b/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py index 9997f28438db113e85ce92138b3c08b223185a2c..73721b6cc5744bb1345815e4bcf1c98aadb2d4f1 100644 --- a/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py +++ b/tests/fpgadataflow/test_convert_to_hls_layers_cnv.py @@ -30,9 +30,10 @@ import pkg_resources as pk import pytest -import brevitas.onnx as bo import numpy as np import os +import torch +from brevitas.export import export_finn_onnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.bipolar_to_xnor import ConvertBipolarMatMulToXnorPopcount @@ -61,7 +62,7 @@ export_onnx_path_cnv = "test_convert_to_hls_layers_cnv.onnx" @pytest.mark.parametrize("fused_activation", [True, False]) def test_convert_to_hls_layers_cnv_w1a1(fused_activation): cnv = get_test_model_trained("CNV", 1, 1) - bo.export_finn_onnx(cnv, (1, 3, 32, 32), export_onnx_path_cnv) + export_finn_onnx(cnv, torch.randn(1, 3, 32, 32), export_onnx_path_cnv) model = ModelWrapper(export_onnx_path_cnv) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/fpgadataflow/test_convert_to_hls_layers_fc.py b/tests/fpgadataflow/test_convert_to_hls_layers_fc.py index fd4e3679d7f19471509f8144ac72b4964f5b4a52..5a45638ba1908582a0dd62c9d69f259b85376145 100644 --- a/tests/fpgadataflow/test_convert_to_hls_layers_fc.py +++ b/tests/fpgadataflow/test_convert_to_hls_layers_fc.py @@ -28,12 +28,12 @@ import pytest -import brevitas.onnx as bo import numpy as np import onnx import onnx.numpy_helper as nph import os import torch +from brevitas.export import export_finn_onnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp @@ -59,7 +59,7 @@ export_onnx_path = "test_convert_to_hls_layers_fc.onnx" @pytest.mark.vivado def test_convert_to_hls_layers_tfc_w1a1(): tfc = get_test_model_trained("TFC", 1, 1) - bo.export_finn_onnx(tfc, (1, 1, 28, 28), export_onnx_path) + export_finn_onnx(tfc, torch.randn(1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) @@ -130,7 +130,7 @@ def test_convert_to_hls_layers_tfc_w1a1(): @pytest.mark.vivado def test_convert_to_hls_layers_tfc_w1a2(): tfc = get_test_model_trained("TFC", 1, 2) - bo.export_finn_onnx(tfc, (1, 1, 28, 28), export_onnx_path) + export_finn_onnx(tfc, torch.randn(1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/fpgadataflow/test_fifosizing.py b/tests/fpgadataflow/test_fifosizing.py index f4f2b8dbfff0d720ec4eb901704581b096c0ea40..922232c2c2453902b4ed1c4b96b5d9b0f187690a 100644 --- a/tests/fpgadataflow/test_fifosizing.py +++ b/tests/fpgadataflow/test_fifosizing.py @@ -31,7 +31,8 @@ import pytest import json import shutil -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +import torch +from brevitas.export import export_qonnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp @@ -45,7 +46,7 @@ def fetch_test_model(topology, wbits=2, abits=2): tmp_output_dir = make_build_dir("build_fifosizing_%s_" % topology) (model, ishape) = get_trained_network_and_ishape(topology, wbits, abits) chkpt_name = tmp_output_dir + "/model.onnx" - BrevitasONNXManager.export(model, ishape, chkpt_name) + export_qonnx(model, torch.randn(ishape), chkpt_name) return tmp_output_dir @@ -55,7 +56,7 @@ def fetch_test_model(topology, wbits=2, abits=2): @pytest.mark.parametrize( "method", ["largefifo_rtlsim_python", "largefifo_rtlsim_cpp", "characterize"] ) -@pytest.mark.parametrize("topology", ["tfc"]) +@pytest.mark.parametrize("topology", ["tfc", "cnv"]) def test_fifosizing_linear(method, topology): force_python_rtlsim = "python" in method method_key = "largefifo_rtlsim" if "largefifo_rtlsim" in method else "characterize" @@ -68,7 +69,7 @@ def test_fifosizing_linear(method, topology): force_python_rtlsim=force_python_rtlsim, synth_clk_period_ns=10.0, board="Pynq-Z1", - rtlsim_batch_size=100, + rtlsim_batch_size=100 if topology == "tfc" else 2, shell_flow_type=build_cfg.ShellFlowType.VIVADO_ZYNQ, generate_outputs=[ build_cfg.DataflowOutputType.ESTIMATE_REPORTS, @@ -83,7 +84,7 @@ def test_fifosizing_linear(method, topology): with open(tmp_output_dir + "/report/rtlsim_performance.json") as f: sim_data = json.load(f) assert ( - float(sim_data["throughput[images/s]"]) + float(sim_data["stable_throughput[images/s]"]) / float(est_data["estimated_throughput_fps"]) > 0.9 ) diff --git a/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl.py b/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl.py index 58fc5ec04cc471b0e8f201e235ac9bd033e3f5c4..e8236c0c6b8c7fbb9e4394fb3cada785f9c050ac 100755 --- a/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl.py +++ b/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl.py @@ -140,9 +140,9 @@ def prepare_inputs(input_tensor): # input datatype @pytest.mark.parametrize("idt", [DataType["UINT4"]]) # kernel size -@pytest.mark.parametrize("k", [[2, 2], [3, 3], [1, 3]]) +@pytest.mark.parametrize("k", [[3, 3], [1, 5]]) # input dimension -@pytest.mark.parametrize("ifm_dim", [[24, 24], [15, 6], [13, 13], [1, 14]]) +@pytest.mark.parametrize("ifm_dim", [[13, 13], [1, 21]]) # input channels @pytest.mark.parametrize("ifm_ch", [6]) # Stride @@ -152,9 +152,9 @@ def prepare_inputs(input_tensor): # depthwise @pytest.mark.parametrize("dw", [0, 1]) # input channel parallelism ("SIMD") -@pytest.mark.parametrize("simd", [1, 2, 3, 6]) +@pytest.mark.parametrize("simd", [1, 3, 6]) # parallel_window enable (MMV_out = M*K) -@pytest.mark.parametrize("parallel_window", [0]) +@pytest.mark.parametrize("parallel_window", [0, 1]) # in/out MMV ("M") @pytest.mark.parametrize("m", [1]) # Flip dimensions @@ -198,12 +198,13 @@ def test_fpgadataflow_slidingwindow_rtl( pytest.skip( "Illegal convolution configuration: kernel or stride > FM dimension" ) - if (k_h == 1 and (stride_h != 1 or dilation_h != 1)) or ( - k_w == 1 and (stride_w != 1 or dilation_w != 1) - ): + if (k_h == 1 and dilation_h != 1) or (k_w == 1 and dilation_w != 1): pytest.skip( - """Illegal convolution configuration: - stride or dilation defined for unitary kernel dim""" + "Illegal convolution configuration: dilation for unitary kernel dim" + ) + if (stride_h > k_h) or (stride_w > k_w) and not parallel_window: + pytest.skip( + "Not all combinations for stride > k edge case supported in default mode" ) if k_h == 1 and k_w == 1 and simd != ifm_ch: pytest.skip("1x1 Kernel only supported in parallel mode (SIMD=C)") diff --git a/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl_dynamic.py b/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl_dynamic.py index 7f7bf649a9284e7716aec5adfb91957fdabb55d5..e586984b31f741d2b4744acd1886e76a4179a59e 100644 --- a/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl_dynamic.py +++ b/tests/fpgadataflow/test_fpgadataflow_convinputgenerator_rtl_dynamic.py @@ -189,6 +189,10 @@ cfg0 = { "ofm": 64, "depthwise": True, "pad_mode": "SAME_UPPER", + # run synthesis for one configuration + # this helped expose a bug in enum decls previously + # (which config the synth runs on does not matter) + "do_synth": True, } cfg1 = { "idims": [(32, 16), (16, 8)], @@ -198,6 +202,7 @@ cfg1 = { "ofm": 8, "depthwise": False, "pad_mode": "SAME_UPPER", + "do_synth": False, } cfg2 = { "idims": [(64, 128), (2, 4)], @@ -207,6 +212,7 @@ cfg2 = { "ofm": 64, "depthwise": True, "pad_mode": "SAME_UPPER", + "do_synth": False, } @@ -215,6 +221,7 @@ cfg2 = { @pytest.mark.vivado @pytest.mark.fpgadataflow def test_fpgadataflow_conv_dynamic(cfg): + do_synth = cfg["do_synth"] pad_mode = cfg["pad_mode"] depthwise = cfg["depthwise"] idims = cfg["idims"] @@ -292,7 +299,7 @@ def test_fpgadataflow_conv_dynamic(cfg): model = model.transform(GiveReadableTensorNames()) model = model.transform(PrepareIP("xc7z020clg400-1", 5)) model = model.transform(HLSSynthIP()) - model = model.transform(CreateStitchedIP("xc7z020clg400-1", 5)) + model = model.transform(CreateStitchedIP("xc7z020clg400-1", 5, vitis=do_synth)) model.set_metadata_prop("exec_mode", "rtlsim") # loop through experiment configurations diff --git a/tests/fpgadataflow/test_fpgadataflow_ipstitch.py b/tests/fpgadataflow/test_fpgadataflow_ipstitch.py index b220338e6919e8eeaeef0f6e5343fed9b1dfca10..7e4069f5c481344560509d17c086ca2cbdbd0fda 100644 --- a/tests/fpgadataflow/test_fpgadataflow_ipstitch.py +++ b/tests/fpgadataflow/test_fpgadataflow_ipstitch.py @@ -206,7 +206,6 @@ def test_fpgadataflow_ipstitch_gen_model(mem_mode): assert sdp_node.__class__.__name__ == "StreamingDataflowPartition" assert os.path.isfile(sdp_node.get_nodeattr("model")) model = load_test_checkpoint_or_skip(sdp_node.get_nodeattr("model")) - model.set_metadata_prop("exec_mode", "remote_pynq") model = model.transform(InsertTLastMarker()) model = model.transform(GiveUniqueNodeNames()) model = model.transform(PrepareIP(test_fpga_part, 5)) diff --git a/tests/fpgadataflow/test_fpgadataflow_res_estimate.py b/tests/fpgadataflow/test_fpgadataflow_res_estimate.py index b3cf7b4229c39f27c7f3689ef51fb7d22c7aa0f2..2ff7dd8b3290adf9fa09effa6df4ebb98e1804b9 100644 --- a/tests/fpgadataflow/test_fpgadataflow_res_estimate.py +++ b/tests/fpgadataflow/test_fpgadataflow_res_estimate.py @@ -101,7 +101,7 @@ def test_res_estimate(): "MatrixVectorActivation_0": { "BRAM_18K": 0, "BRAM_efficiency": 1, - "LUT": 357, + "LUT": 317, "DSP": 0, "URAM_efficiency": 1, "URAM": 0, @@ -119,7 +119,7 @@ def test_res_estimate(): { "BRAM_18K": 0, "BRAM_efficiency": 1, - "LUT": 352, + "LUT": 313, "DSP": 1, "URAM": 0, "URAM_efficiency": 1, @@ -127,7 +127,7 @@ def test_res_estimate(): { "BRAM_18K": 0, "BRAM_efficiency": 1, - "LUT": 357, + "LUT": 317, "DSP": 0, "URAM": 0, "URAM_efficiency": 1, diff --git a/tests/fpgadataflow/test_fpgadataflow_thresholding.py b/tests/fpgadataflow/test_fpgadataflow_thresholding.py index 96cd69c3453793c1634f132cb159f0cc8a94a28c..445afdf458c0e99d4a4dcc4eeb2f6fb305dbac4a 100644 --- a/tests/fpgadataflow/test_fpgadataflow_thresholding.py +++ b/tests/fpgadataflow/test_fpgadataflow_thresholding.py @@ -132,10 +132,6 @@ def test_fpgadataflow_thresholding(idt, act, nf, ich, exec_mode, mem_mode): odt = act n_steps = act.get_num_possible_values() - 1 T = np.random.randint(idt.min(), idt.max() + 1, (ich, n_steps)).astype(np.float32) - # make the vivado_hls threshold bug appear (incorrect rtlsim result when first - # threshold of first channel is zero, while using BIPOLAR output) - if act == DataType["BIPOLAR"]: - T[0][0] = 0 # provide non-decreasing thresholds T = np.sort(T, axis=1) diff --git a/tests/fpgadataflow/test_fpgadataflow_vvau.py b/tests/fpgadataflow/test_fpgadataflow_vvau.py index abf8ba0b9efde67c77711abc8451475887430cae..95501078d6dcf82f7aa3f0eb887436e7640dfeae 100644 --- a/tests/fpgadataflow/test_fpgadataflow_vvau.py +++ b/tests/fpgadataflow/test_fpgadataflow_vvau.py @@ -35,6 +35,8 @@ from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.general.multithreshold import multithreshold from qonnx.custom_op.registry import getCustomOp from qonnx.transformation.general import GiveUniqueNodeNames +from qonnx.transformation.infer_datatypes import InferDataTypes +from qonnx.transformation.infer_shapes import InferShapes from qonnx.util.basic import gen_finn_dt_tensor, qonnx_make_model import finn.core.onnx_exec as oxe @@ -77,6 +79,7 @@ def _calculate_dot_prod_range(dt_a, dt_b, len): def _make_single_vvau_modelwrapper( W, pe, + simd, k_h, k_w, channels, @@ -103,7 +106,10 @@ def _make_single_vvau_modelwrapper( if T is not None: no_act = 0 node_inp_list = ["inp", "weights", "thresh"] - actval = odt.min() + if odt == DataType["BIPOLAR"]: + actval = 0 + else: + actval = odt.min() else: no_act = 1 node_inp_list = ["inp", "weights"] @@ -116,6 +122,7 @@ def _make_single_vvau_modelwrapper( domain="finn.custom_op.fpgadataflow", backend="fpgadataflow", PE=pe, + SIMD=simd, Dim=[dim_h, dim_w], Channels=channels, Kernel=[k_h, k_w], @@ -146,6 +153,9 @@ def _make_single_vvau_modelwrapper( model.set_tensor_datatype("thresh", tdt) model.set_initializer("thresh", T) + model = model.transform(InferShapes()) + model = model.transform(InferDataTypes()) + return model @@ -154,13 +164,15 @@ def prepare_inputs(input_tensor): # input datatype -@pytest.mark.parametrize("idt", [DataType["UINT4"], DataType["UINT8"]]) +@pytest.mark.parametrize("idt", [DataType["BIPOLAR"], DataType["UINT4"]]) # weight datatype -@pytest.mark.parametrize("wdt", [DataType["INT4"]]) +@pytest.mark.parametrize("wdt", [DataType["BIPOLAR"], DataType["UINT4"]]) # activation: None or DataType -@pytest.mark.parametrize("act", [DataType["UINT4"], None]) +@pytest.mark.parametrize("act", [DataType["BIPOLAR"], DataType["UINT4"], None]) # PE -@pytest.mark.parametrize("pe", [1, "channels"]) +@pytest.mark.parametrize("pe", [1, 3, 6]) +# SIMD +@pytest.mark.parametrize("simd", [1, 9]) # Input image shape @pytest.mark.parametrize("dim_h", [10]) @pytest.mark.parametrize("dim_w", [10, 1]) @@ -168,7 +180,7 @@ def prepare_inputs(input_tensor): @pytest.mark.parametrize("k_h", [3]) @pytest.mark.parametrize("k_w", [3, 1]) # Number of input and output channels -@pytest.mark.parametrize("channels", [3, 4]) +@pytest.mark.parametrize("channels", [3, 6]) # memory mode @pytest.mark.parametrize("mem_mode", ["const", "decoupled"]) # execution mode @@ -177,17 +189,17 @@ def prepare_inputs(input_tensor): @pytest.mark.slow @pytest.mark.vivado def test_fpgadataflow_vvau( - idt, wdt, act, pe, dim_h, dim_w, k_h, k_w, channels, mem_mode, exec_mode + idt, wdt, act, pe, simd, dim_h, dim_w, k_h, k_w, channels, mem_mode, exec_mode ): - if pe == "channels": - pe = channels - if dim_w == 1 and k_w != 1: pytest.skip("1D image requires 1D kernel, skipping.") if channels % pe != 0: pytest.skip("Requirement Channels divisable by PE is violated.") + if (k_h * k_w) % simd != 0: + pytest.skip("Requirement kernel (k_h * k_w) divisable by SIMD is violated.") + # Generate weights in expected shape for ONNX and HLS node W = gen_finn_dt_tensor(wdt, (channels, 1, k_h, k_w)) # shape: [channels, 1, k, k] W_onnx = _infer_sparse_weight_tensor( @@ -203,17 +215,26 @@ def test_fpgadataflow_vvau( if act is None: T = None tdt = None - odt = DataType["INT32"] + if wdt == DataType["BIPOLAR"] and idt == DataType["BIPOLAR"]: + odt = DataType["UINT32"] + else: + odt = DataType["INT32"] else: odt = act - (min_v, max_v) = _calculate_dot_prod_range(idt, wdt, k_h * k_w * channels) + (min_v, max_v) = _calculate_dot_prod_range(idt, wdt, k_h * k_w) n_steps = act.get_num_possible_values() - 1 T = np.random.randint(min_v, max_v - 1, (channels, n_steps)).astype(np.float32) T = np.sort(T, axis=1) - tdt = DataType["INT32"] + if wdt == DataType["BIPOLAR"] and idt == DataType["BIPOLAR"]: + tdt = DataType["UINT32"] + # bias thresholds to be positive + T = np.ceil((T + (k_h * k_w)) / 2) + assert (T >= 0).all() + else: + tdt = DataType["INT32"] model = _make_single_vvau_modelwrapper( - W, pe, k_h, k_w, channels, dim_h, dim_w, wdt, idt, odt, T, tdt, mem_mode + W, pe, simd, k_h, k_w, channels, dim_h, dim_w, wdt, idt, odt, T, tdt, mem_mode ) if exec_mode == "cppsim": @@ -232,20 +253,31 @@ def test_fpgadataflow_vvau( input_dict = prepare_inputs(x_vvau) # Calculate output - y_expected = np.matmul(x, W_onnx) # Y is in [N, H, W, C] format + if wdt == DataType["BIPOLAR"] and idt == DataType["BIPOLAR"]: + # Simulate XNOR-popcount matrix multiplication, see + # qonnx.custom_op.general.xnorpopcount (not usable due to sparse W) + y_expected = np.matmul(x, W_onnx) + y_expected = (y_expected + (k_h * k_w)) / 2 + else: + y_expected = np.matmul(x, W_onnx) # Y is in [N, H, W, C] format + if T is not None: # Reshape Y, as multithreshold expects Y to be in [N, C, H, W] format y_expected = np.transpose(y_expected, (0, 3, 1, 2)) y_expected = multithreshold(y_expected, T) y_expected = np.transpose(y_expected, (0, 2, 3, 1)) - # signed offset - y_expected += act.min() + if act == DataType["BIPOLAR"]: + # binary to bipolar + y_expected = 2 * y_expected - 1 + else: + # signed offset + y_expected += act.min() y_produced = oxe.execute_onnx(model, input_dict, return_full_exec_context=False)[ "outp" ] - assert (y_produced == y_expected).all(), "cppsim failed" + assert (y_produced == y_expected).all(), "incorrect result" if exec_mode == "rtlsim": node = model.get_nodes_by_op_type("VectorVectorActivation")[0] diff --git a/tests/fpgadataflow/test_minimize_bit_width.py b/tests/fpgadataflow/test_minimize_bit_width.py new file mode 100644 index 0000000000000000000000000000000000000000..dc4a076a1808932b027f87d10b4d31a400ac1ad5 --- /dev/null +++ b/tests/fpgadataflow/test_minimize_bit_width.py @@ -0,0 +1,320 @@ +# Copyright (C) 2023, Advanced Micro Devices, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of FINN nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import pytest + +import numpy as np +from onnx import TensorProto, helper +from qonnx.core.datatype import BipolarType, DataType, IntType +from qonnx.core.modelwrapper import ModelWrapper +from qonnx.custom_op.registry import getCustomOp +from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple +from typing import Optional, Union + +from finn.custom_op.fpgadataflow.matrixvectoractivation import MatrixVectorActivation +from finn.custom_op.fpgadataflow.vectorvectoractivation import VectorVectorActivation +from finn.transformation.fpgadataflow.minimize_accumulator_width import ( + MinimizeAccumulatorWidth, +) +from finn.transformation.fpgadataflow.minimize_weight_bit_width import ( + MinimizeWeightBitWidth, +) + + +def make_unit_test_model(wdt: DataType, idt: DataType, tdt: Optional[DataType] = None): + """Creates a toy finn-onnx model for unit testing. The VVAU-MVAU pair is based + on the first pair of MobileNetV1""" + inp = helper.make_tensor_value_info("inp", TensorProto.FLOAT, [1, 32, 32, 288]) + outp = helper.make_tensor_value_info("outp", TensorProto.FLOAT, [1, 32, 32, 64]) + layer1 = helper.make_node( + "VectorVectorActivation", + ["inp", "params0", "thresh0"] if tdt is not None else ["inp", "params0"], + ["hid"], + domain="finn.custom_op.fpgadataflow", + backend="fpgadataflow", + PE=1, + Channels=32, + Dim=(32, 32), + Kernel=(3, 3), + inputDataType=idt.name, + outputDataType=idt.name, + weightDataType=wdt.name, + ActVal=tdt.min() if tdt is not None else 0, + noActivation=0 if tdt is not None else 1, + ) + layer2 = helper.make_node( + "MatrixVectorActivation", + ["hid", "params1", "thresh1"] if tdt is not None else ["hid", "params1"], + ["outp"], + domain="finn.custom_op.fpgadataflow", + backend="fpgadataflow", + MW=32, # matrix_width (num_inputs) + MH=64, # matrix_height (num_outputs) + SIMD=1, + PE=1, + inputDataType=idt.name, + outputDataType=idt.name, + weightDataType=wdt.name, + ActVal=tdt.min() if tdt is not None else 0, + noActivation=0 if tdt is not None else 1, + binaryXnorMode=0, + ) + graph = helper.make_graph( + nodes=[layer1, layer2], name="fclayer_graph", inputs=[inp], outputs=[outp] + ) + + model = helper.make_model(graph, producer_name="fclayer-model") + model = ModelWrapper(model) + + model.set_tensor_datatype("inp", idt) + model.set_tensor_datatype("outp", idt) + model.set_tensor_datatype("hid", idt) + model.set_tensor_datatype("params0", wdt) + model.set_tensor_datatype("params1", wdt) + model.set_initializer("params0", gen_finn_dt_tensor(wdt, (32, 1, 3, 3))) + model.set_initializer("params1", gen_finn_dt_tensor(wdt, (32, 64))) + # if the threshold data type is specified, then we need to generate + # some dummy threshold values + if tdt is not None: + model.set_tensor_datatype("thresh0", tdt) + model.set_tensor_datatype("thresh1", tdt) + # Create threshold tensors + n_steps: int = idt.get_num_possible_values() - 1 + thresholds: Optional[np.ndarray] = np.random.randint( + tdt.min(), tdt.max() - 1, (32, n_steps) + ).astype( + np.float32 + ) # generate thresholds for the activations + thresholds = np.sort(thresholds, axis=1) # provide non-decreasing thresholds + model.set_initializer("thresh0", thresholds) + thresholds: Optional[np.ndarray] = np.random.randint( + tdt.min(), tdt.max() - 1, (64, n_steps) + ).astype( + np.float32 + ) # generate thresholds for the activations + thresholds = np.sort(thresholds, axis=1) # provide non-decreasing thresholds + model.set_initializer("thresh1", thresholds) + return model + + +weight_data_types = [ + DataType["INT8"], + DataType["UINT8"], + DataType["INT7"], + DataType["UINT7"], + DataType["INT3"], + DataType["UINT3"], + # DataType["BIPOLAR"], # TODO - add support for bipolar weights + DataType["TERNARY"], +] + + +input_data_types = [ + DataType["INT8"], + DataType["UINT8"], + DataType["INT3"], + DataType["UINT3"], + DataType["BIPOLAR"], + DataType["TERNARY"], +] + + +@pytest.mark.parametrize("wdt", weight_data_types) +@pytest.mark.parametrize("rww", [True, False]) +@pytest.mark.fpgadataflow +def test_minimize_weight_bit_width(wdt: DataType, rww: bool): + """Testing MinimizeWeightBitWidth for VVAU and MVAU. + + :param wdt: (DataType) The data type that we are testing for the weights + :param rww: (bool) Whether or not to use runtime-writeable weights""" + if isinstance(wdt, BipolarType): + # current MinimizeWeightBitWidth sets {-1,1} to INT2, need to check + # for 0 in weights to minimize weight bit width to bipolar + pytest.skip("Not well-supported for this optimization") + + # Create a w8a8 model + def_wdt = DataType["UINT8"] + model = make_unit_test_model(def_wdt, DataType["INT8"]) + + # Create new weights for the model based on wdt + params0 = gen_finn_dt_tensor(wdt, (32, 1, 3, 3)) + params1 = gen_finn_dt_tensor(wdt, (32, 64)) + model.set_initializer("params0", params0) + model.set_initializer("params1", params1) + + # If runtime-writeable weights, specify as a node attribute + for node in model.graph.node: + inst = getCustomOp(node) + if isinstance(inst, (MatrixVectorActivation, VectorVectorActivation)): + inst.set_nodeattr("runtime_writeable_weights", int(rww)) + + # Apply the optimization + model = model.transform(MinimizeWeightBitWidth()) + + # Iterate through each node to make sure it functioned properly + for node in model.graph.node: + inst = getCustomOp(node) + if isinstance(inst, (MatrixVectorActivation, VectorVectorActivation)): + cur_wdt = DataType[inst.get_nodeattr("weightDataType")] + exp_wdt = def_wdt if rww else wdt + assert cur_wdt.bitwidth() == exp_wdt.bitwidth(), "Mismatched data types" + + +def calculate_accumulator_bit_width( + inst: Union[MatrixVectorActivation, VectorVectorActivation], model: ModelWrapper +) -> Union[DataType, IntType]: + """Calculate the accumulator bit width using the closed-form expressions + derived in `Quantized Neural Networks for Low-Precision Accumulation + with Guaranteed Overflow Avoidance` (2023) by I.Colbert, A. Pappalardo, + J. Petri-Koenig + + :param inst: (HLSCustomOp) The instance of the MVAU or VVAU + :param model: (ModelWrapper) The instance of the whole model + """ + + def phi(x: float) -> float: + return np.log2(1 + pow(2, -x)) + + weights = model.get_initializer(inst.onnx_node.input[1]) + # since in the calculation the values of the weight matrix are used, + # for the bipolar case they need to be converted to bipolar + if inst.get_nodeattr("binaryXnorMode"): + weights = 2 * weights - 1 + # modify the weights based on if the node is a VVAU or MVAU + if isinstance(inst, MatrixVectorActivation): + K = inst.get_nodeattr("MW") # matrix_width = num_inputs + elif isinstance(inst, VectorVectorActivation): + k_h, k_w = inst.get_nodeattr("Kernel") + K = k_h * k_w # size of kernels = num_inputs + fm = inst.get_nodeattr("Channels") + # put weights into the shape expected by calculate_matvec_accumulator_range + weights = weights.reshape(fm, k_h * k_w).transpose() + else: + raise Exception("Considering only MVAU and VVAU currently") + # collect attributes used to determine the accumulator bit width bound + wdt = inst.get_weight_datatype() + idt = inst.get_input_datatype() + rww = inst.get_nodeattr("runtime_writeable_weights") + # if runtime-writeable weights, then use the lower bound on the accumulator bit + # width as determined by the input and weight data types and size of dot product + if rww: + alpha = np.log2(K) + idt.bitwidth() + wdt.bitwidth() - 1.0 - float(idt.signed()) + P = np.ceil(alpha + phi(alpha) + 1.0) + # if not runtime-writable weights, then use the tighter bound on the accumulator + # bit width as determined by the weight values themselves + else: + beta = ( + np.log2(abs(weights).sum(axis=0).max()) + + idt.bitwidth() + - float(idt.signed()) + ) + P = np.ceil(beta + phi(beta) + 1.0) + # if the node is the last in the graph, then round up to the nearest 8 bits + if model.find_direct_successors(inst.onnx_node) is None: + P = roundup_to_integer_multiple(P, 8) + return DataType[f"INT{int(P)}"] + + +thresh_data_types = [ + None, + DataType["INT32"], + DataType["INT24"], + DataType["INT16"], +] + +# Removing unsigned data types fro weights +weight_data_types = [ + DataType["INT8"], + DataType["INT7"], + DataType["INT3"], + # DataType["BIPOLAR"], # TODO - add support for bipolar weights + DataType["TERNARY"], +] + + +@pytest.mark.parametrize("wdt", weight_data_types) +@pytest.mark.parametrize("idt", input_data_types) +@pytest.mark.parametrize("tdt", thresh_data_types) +@pytest.mark.parametrize("rww", [True, False]) +@pytest.mark.fpgadataflow +def test_minimize_accumulator_width( + wdt: DataType, idt: DataType, tdt: DataType, rww: bool +): + """Testing MinimizeAccumulatorWidth for VVAU and MVAU. + + :param wdt: (DataType) The data type that we are testing for the weights + :param idt: (DataType) The data type that we are testing for the activations + :param tdt: (DataType) The data type that we are testing for the thresholds + :param rww: (bool) Whether or not to use runtime-writeable weights""" + if (not wdt.signed()) or isinstance(wdt, BipolarType): + pytest.skip( + "Closed-form accumulator calculation is designed to consider signed weights" + ) + + # Create uniform-precision model + model = make_unit_test_model(wdt, idt, tdt) + def_adt = DataType["INT32"] + + # If runtime-writeable weights, specify as a node attribute + for node in model.graph.node: + inst = getCustomOp(node) + if isinstance(inst, (MatrixVectorActivation, VectorVectorActivation)): + inst.set_nodeattr("runtime_writeable_weights", int(rww)) + cur_adt = DataType[inst.get_nodeattr("accDataType")] + assert ( + cur_adt.bitwidth() == def_adt.bitwidth() + ), "Default data type is incorrect" + + # Apply the optimization + model = model.transform(MinimizeAccumulatorWidth()) + + # Iterate through each node to make sure it functioned properly + for node in model.graph.node: + inst = getCustomOp(node) + if isinstance(inst, (MatrixVectorActivation, VectorVectorActivation)): + cur_adt = DataType[inst.get_nodeattr("accDataType")] + cur_odt = DataType[inst.get_nodeattr("outputDataType")] + # Calculating expected accumulator bit width using a closed-form expression + # that is a slight over-approximation of the lower bound. The accumulator + # bit width minimization logic in the MVAU and VVAU is exact and should be + # less than or equal to this calculation + exp_adt = calculate_accumulator_bit_width(inst, model) + assert ( + cur_adt.bitwidth() <= exp_adt.bitwidth() + ), "Mismatched accumulation data types" + if model.find_direct_successors(inst.onnx_node) is None: + assert ( + cur_adt.bitwidth() % 8 + ) == 0, "bit width of last node needs to be divisible by 8" + assert ( + cur_adt.bitwidth() == cur_odt.bitwidth() + ), "outputDataType and accDataType should be equal" + else: + assert ( + cur_odt.bitwidth() == idt.bitwidth() + ), "outputDataType should not be changed" diff --git a/tests/fpgadataflow/test_split_large_fifos.py b/tests/fpgadataflow/test_split_large_fifos.py index 85b4a2bfa8dc0de3cbdd0ca34ec5b1ee68f37acf..0437d006cf09fe2ad5076d2e62105c9adea6ff41 100644 --- a/tests/fpgadataflow/test_split_large_fifos.py +++ b/tests/fpgadataflow/test_split_large_fifos.py @@ -31,7 +31,8 @@ import pytest import json import shutil -from brevitas.export.onnx.generic.manager import BrevitasONNXManager +import torch +from brevitas.export import export_qonnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.custom_op.registry import getCustomOp @@ -46,7 +47,7 @@ def fetch_test_model(topology, wbits=2, abits=2): tmp_output_dir = make_build_dir("build_fifosizing_%s_" % topology) (model, ishape) = get_trained_network_and_ishape(topology, wbits, abits) chkpt_name = tmp_output_dir + "/model.onnx" - BrevitasONNXManager.export(model, ishape, chkpt_name) + export_qonnx(model, torch.randn(ishape), chkpt_name) return tmp_output_dir diff --git a/tests/notebooks/test_jupyter_notebooks.py b/tests/notebooks/test_jupyter_notebooks.py new file mode 100644 index 0000000000000000000000000000000000000000..819b4ccde0333cfdf6e16f30e25fb5303fbf1f70 --- /dev/null +++ b/tests/notebooks/test_jupyter_notebooks.py @@ -0,0 +1,52 @@ +import pytest + +import nbformat +from nbconvert.preprocessors import ExecutePreprocessor + +from finn.util.basic import get_finn_root + +notebook_timeout_seconds = 3600 +notebook_basic_dir = get_finn_root() + "/notebooks/basics/" +notebook_advanced_dir = get_finn_root() + "/notebooks/advanced/" +notebook_cyber_dir = get_finn_root() + "/notebooks/end2end_example/cybersecurity/" +notebook_bnn_dir = get_finn_root() + "/notebooks/end2end_example/bnn-pynq/" + +basics_notebooks = [ + pytest.param(notebook_basic_dir + "0_how_to_work_with_onnx.ipynb"), + pytest.param(notebook_basic_dir + "1a_brevitas_network_import_via_FINN-ONNX.ipynb"), + pytest.param(notebook_basic_dir + "1b_brevitas_network_import_via_QONNX.ipynb"), +] + +advanced_notebooks = [ + pytest.param(notebook_advanced_dir + "0_custom_analysis_pass.ipynb"), + pytest.param(notebook_advanced_dir + "1_custom_transformation_pass.ipynb"), + pytest.param(notebook_advanced_dir + "2_custom_op.ipynb"), +] + +cyber_notebooks = [ + pytest.param(notebook_cyber_dir + "1-train-mlp-with-brevitas.ipynb"), + pytest.param(notebook_cyber_dir + "2-import-into-finn-and-verify.ipynb"), + pytest.param(notebook_cyber_dir + "3-build-accelerator-with-finn.ipynb"), +] + +bnn_notebooks = [ + pytest.param(notebook_bnn_dir + "cnv_end2end_example.ipynb"), + pytest.param(notebook_bnn_dir + "tfc_end2end_example.ipynb"), + pytest.param(notebook_bnn_dir + "tfc_end2end_verification.ipynb"), +] + + +@pytest.mark.notebooks +@pytest.mark.parametrize( + "notebook", basics_notebooks + advanced_notebooks + cyber_notebooks + bnn_notebooks +) +def test_notebook_exec(notebook): + with open(notebook) as f: + nb = nbformat.read(f, as_version=4) + ep = ExecutePreprocessor( + timeout=notebook_timeout_seconds, kernel_name="python3" + ) + try: + assert ep.preprocess(nb) is not None, f"Got empty notebook for {notebook}" + except Exception: + assert False, f"Failed executing {notebook}" diff --git a/tests/transformation/streamline/test_sign_to_thres.py b/tests/transformation/streamline/test_sign_to_thres.py index 839680bd7ad2d40cb622b313257e819737027a2f..72e400346d3491c66c5ba0f0a1c7da63eeff96fa 100644 --- a/tests/transformation/streamline/test_sign_to_thres.py +++ b/tests/transformation/streamline/test_sign_to_thres.py @@ -28,10 +28,11 @@ import pytest -import brevitas.onnx as bo import onnx import onnx.numpy_helper as nph import os +import torch +from brevitas.export import export_finn_onnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants @@ -47,7 +48,7 @@ export_onnx_path = "test_sign_to_thres.onnx" @pytest.mark.streamline def test_sign_to_thres(): lfc = get_test_model_trained("LFC", 1, 1) - bo.export_finn_onnx(lfc, (1, 1, 28, 28), export_onnx_path) + export_finn_onnx(lfc, torch.randn(1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/transformation/streamline/test_streamline_cnv.py b/tests/transformation/streamline/test_streamline_cnv.py index 6a829250127ee289733ec8ce1b08b63de7a573c5..b7d6a825bba4ad33287516ba637804526d0b53f9 100644 --- a/tests/transformation/streamline/test_streamline_cnv.py +++ b/tests/transformation/streamline/test_streamline_cnv.py @@ -30,8 +30,9 @@ import pkg_resources as pk import pytest -import brevitas.onnx as bo import numpy as np +import torch +from brevitas.export import export_finn_onnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants from qonnx.transformation.general import ( @@ -63,7 +64,7 @@ def test_streamline_cnv(size, wbits, abits): nname = "%s_%dW%dA" % (size, wbits, abits) finn_onnx = export_onnx_path + "/%s.onnx" % nname fc = get_test_model_trained(size, wbits, abits) - bo.export_finn_onnx(fc, (1, 3, 32, 32), finn_onnx) + export_finn_onnx(fc, torch.randn(1, 3, 32, 32), finn_onnx) model = ModelWrapper(finn_onnx) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/transformation/streamline/test_streamline_fc.py b/tests/transformation/streamline/test_streamline_fc.py index 90008214352d1a75fba61130f5aedbc358e1fe74..6131c3b03ea8542f2a04e14e82b6007c6ae9c6b4 100644 --- a/tests/transformation/streamline/test_streamline_fc.py +++ b/tests/transformation/streamline/test_streamline_fc.py @@ -28,10 +28,11 @@ import pytest -import brevitas.onnx as bo import numpy as np import onnx import onnx.numpy_helper as nph +import torch +from brevitas.export import export_finn_onnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants @@ -66,7 +67,7 @@ def test_streamline_fc(size, wbits, abits): nname = "%s_%dW%dA" % (size, wbits, abits) finn_onnx = export_onnx_path + "/%s.onnx" % nname fc = get_test_model_trained(size, wbits, abits) - bo.export_finn_onnx(fc, (1, 1, 28, 28), finn_onnx) + export_finn_onnx(fc, torch.randn(1, 1, 28, 28), finn_onnx) model = ModelWrapper(finn_onnx) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/transformation/test_batchnorm_to_affine_bnn_pynq.py b/tests/transformation/test_batchnorm_to_affine_bnn_pynq.py index fd4e37807c860058a8503439a04a58879edc7954..60e81ffe815645abbc0b34c0a9078c701cf68b9a 100644 --- a/tests/transformation/test_batchnorm_to_affine_bnn_pynq.py +++ b/tests/transformation/test_batchnorm_to_affine_bnn_pynq.py @@ -30,11 +30,12 @@ import pkg_resources as pk import pytest -import brevitas.onnx as bo import numpy as np import onnx import onnx.numpy_helper as nph import os +import torch +from brevitas.export import export_finn_onnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.batchnorm_to_affine import BatchNormToAffine @@ -50,7 +51,7 @@ export_onnx_path = "test_output_bn2affine.onnx" @pytest.mark.transform def test_batchnorm_to_affine_cnv_w1a1(): lfc = get_test_model_trained("CNV", 1, 1) - bo.export_finn_onnx(lfc, (1, 3, 32, 32), export_onnx_path) + export_finn_onnx(lfc, torch.randn(1, 3, 32, 32), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) @@ -75,7 +76,7 @@ def test_batchnorm_to_affine_cnv_w1a1(): @pytest.mark.transform def test_batchnorm_to_affine_lfc_w1a1(): lfc = get_test_model_trained("LFC", 1, 1) - bo.export_finn_onnx(lfc, (1, 1, 28, 28), export_onnx_path) + export_finn_onnx(lfc, torch.randn(1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/transformation/test_infer_data_layouts_cnv.py b/tests/transformation/test_infer_data_layouts_cnv.py index 952ce306a447ba0b4d46256ec6e80e5da79be4bc..71822a2903fd284cab9dfee119915a61940cd59b 100644 --- a/tests/transformation/test_infer_data_layouts_cnv.py +++ b/tests/transformation/test_infer_data_layouts_cnv.py @@ -28,9 +28,10 @@ import pytest -import brevitas.onnx as bo import os import qonnx.core.data_layout as DataLayout +import torch +from brevitas.export import export_finn_onnx from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.bipolar_to_xnor import ConvertBipolarMatMulToXnorPopcount from qonnx.transformation.fold_constants import FoldConstants @@ -51,7 +52,7 @@ export_onnx_path_cnv = "test_infer_data_layouts.onnx" @pytest.mark.transform def test_infer_data_layouts_cnv(): cnv = get_test_model_trained("CNV", 1, 1) - bo.export_finn_onnx(cnv, (1, 3, 32, 32), export_onnx_path_cnv) + export_finn_onnx(cnv, torch.randn(1, 3, 32, 32), export_onnx_path_cnv) model = ModelWrapper(export_onnx_path_cnv) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/transformation/test_infer_datatypes_lfc.py b/tests/transformation/test_infer_datatypes_lfc.py index 979800534951abbc77d203aa6b5bd9c797aa9028..173532cb76645cab8b48c24c8d55f2d28e7160bf 100644 --- a/tests/transformation/test_infer_datatypes_lfc.py +++ b/tests/transformation/test_infer_datatypes_lfc.py @@ -28,8 +28,9 @@ import pytest -import brevitas.onnx as bo import os +import torch +from brevitas.export import export_finn_onnx from qonnx.core.datatype import DataType from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants @@ -45,7 +46,7 @@ export_onnx_path = "test_infer_datatypes.onnx" @pytest.mark.transform def test_infer_datatypes_lfc(): lfc = get_test_model_trained("LFC", 1, 1) - bo.export_finn_onnx(lfc, (1, 1, 28, 28), export_onnx_path) + export_finn_onnx(lfc, torch.randn(1, 1, 28, 28), export_onnx_path) model = ModelWrapper(export_onnx_path) model = model.transform(InferShapes()) model = model.transform(FoldConstants()) diff --git a/tests/transformation/test_qonnx_to_finn.py b/tests/transformation/test_qonnx_to_finn.py index 7e438b4b8ba9d9befca79100bb9727735afa27d3..e5f1eefe12aaadcdf9b6da4fd4ace01026ce25af 100644 --- a/tests/transformation/test_qonnx_to_finn.py +++ b/tests/transformation/test_qonnx_to_finn.py @@ -31,12 +31,11 @@ import pkg_resources as pk import pytest -import brevitas.export.onnx.generic as b_onnx -import brevitas.onnx as bo import numpy as np import onnx import onnx.numpy_helper as nph import torch +from brevitas.export import export_finn_onnx, export_qonnx from pkgutil import get_data from qonnx.core.modelwrapper import ModelWrapper from qonnx.transformation.fold_constants import FoldConstants @@ -117,8 +116,10 @@ def test_QONNX_to_FINN(model_name, wbits, abits): torch_input_tensor = torch.from_numpy(input_tensor).float() brev_output = brev_model.forward(torch_input_tensor).detach().numpy() - # Get "clean" FINN model and it's output - _ = bo.export_finn_onnx(brev_model, in_shape, finn_base_path.format("raw")) + # Get "clean" FINN model and its output + _ = export_finn_onnx( + brev_model, torch.randn(in_shape), finn_base_path.format("raw") + ) model = ModelWrapper(finn_base_path.format("raw")) model = model.transform(GiveUniqueNodeNames()) model = model.transform(InferShapes()) @@ -137,10 +138,7 @@ def test_QONNX_to_FINN(model_name, wbits, abits): ).all(), "The output of the Brevitas model and the FINN model should match." # Get the equivalent QONNX model - b_onnx.function.DOMAIN_STRING = "qonnx.custom_op.general" - _ = b_onnx.manager.BrevitasONNXManager.export( - brev_model, in_shape, qonnx_base_path.format("raw") - ) + _ = export_qonnx(brev_model, torch.randn(in_shape), qonnx_base_path.format("raw")) cleanup(qonnx_base_path.format("raw"), out_file=qonnx_base_path.format("clean")) # Compare output