From dcc06915c2ece9d2192867101daf503b7bcc8e36 Mon Sep 17 00:00:00 2001 From: Stanca Pop Date: Mon, 26 Jun 2023 17:55:03 +0300 Subject: [PATCH] Add cn0585_fmcz support --- +adi/+AD3552R/Base.m | 124 +++++++++++++++ +adi/+AD3552R/Tx.m | 88 +++++++++++ +adi/+CN0585/Base.m | 60 ++++++++ +adi/+CN0585/Rx.m | 21 +++ +adi/+CN0585/Tx0.m | 23 +++ +adi/+CN0585/Tx1.m | 22 +++ +adi/+LTC2387/Base.m | 72 +++++++++ +adi/+LTC2387/Rx.m | 73 +++++++++ +adi/Contents.m | 11 +- +adi/Version.m | 3 +- CI/scripts_hdl/matlab_processors.tcl | 58 +++++++ CI/scripts_hdl/ports.json | 142 ++++++++++++++++++ .../+zed/hdlcoder_ref_design_customization.m | 22 +++ .../+cn0585_fmcz/+zed/plugin_board.m | 8 + .../+cn0585_fmcz/+zed/plugin_rd_rx.m | 5 + .../+cn0585_fmcz/+zed/plugin_rd_rxtx.m | 7 + .../+cn0585_fmcz/+zed/plugin_rd_tx.m | 7 + .../+util/ADIZynqSDRAttributeInfo.xml | 37 +++++ .../+util/ADIZynqSDRParameterInfo.xml | 13 ++ .../+AnalogDevices/+util/adizynqsdr.xml | 16 ++ .../+AnalogDevices/+util/extmodeHooksADI.m | 21 +++ .../AnalogDevices/+AnalogDevices/add_clocks.m | 22 +++ .../AnalogDevices/+AnalogDevices/add_io.m | 17 +++ .../+AnalogDevices/add_io_ports.m | 92 ++++++++++++ .../get_memory_axi_interface_info.m | 21 +++ .../AnalogDevices/+AnalogDevices/install.m | 61 ++++++++ .../+AnalogDevices/plugin_board.m | 42 ++++++ .../AnalogDevices/+AnalogDevices/plugin_rd.m | 90 +++++++++++ .../AnalogDevices/+AnalogDevices/uninstall.m | 7 + hdl/vendor/AnalogDevices/Contents.m | 2 + .../hdlcoder_board_customization.m | 15 ++ .../streaming/cn0585_fmcz/CN0585_streaming.m | 74 +++++++++ .../CN0585_streaming_axi4lite_read_write.m | 96 ++++++++++++ test/DemoTests.m | 74 +++++++++ test/NonHWTest.m | 28 ++++ test/adi_build.tcl | 79 ++++++++++ test/board_variants.m | 17 +++ test/build_design.m | 82 ++++++++++ test/runDemoTests.m | 44 ++++++ test/runInstallerTests.m | 48 ++++++ test/runNonHWTest.m | 39 +++++ test/runSynthTests.m | 68 +++++++++ test/setportmapping.m | 106 +++++++++++++ test/testModel.slx | Bin 0 -> 58678 bytes 44 files changed, 1954 insertions(+), 3 deletions(-) create mode 100755 +adi/+AD3552R/Base.m create mode 100755 +adi/+AD3552R/Tx.m create mode 100755 +adi/+CN0585/Base.m create mode 100755 +adi/+CN0585/Rx.m create mode 100755 +adi/+CN0585/Tx0.m create mode 100755 +adi/+CN0585/Tx1.m create mode 100755 +adi/+LTC2387/Base.m create mode 100755 +adi/+LTC2387/Rx.m create mode 100755 CI/scripts_hdl/matlab_processors.tcl create mode 100755 CI/scripts_hdl/ports.json create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/hdlcoder_ref_design_customization.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_board.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rx.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rxtx.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_tx.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRAttributeInfo.xml create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRParameterInfo.xml create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+util/adizynqsdr.xml create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/+util/extmodeHooksADI.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/add_clocks.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/add_io.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/add_io_ports.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/install.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/plugin_board.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m create mode 100755 hdl/vendor/AnalogDevices/+AnalogDevices/uninstall.m create mode 100755 hdl/vendor/AnalogDevices/Contents.m create mode 100755 hdl/vendor/AnalogDevices/hdlcoder_board_customization.m create mode 100644 pcx_examples/streaming/cn0585_fmcz/CN0585_streaming.m create mode 100755 pcx_examples/streaming/cn0585_fmcz/CN0585_streaming_axi4lite_read_write.m create mode 100755 test/DemoTests.m create mode 100755 test/NonHWTest.m create mode 100755 test/adi_build.tcl create mode 100755 test/board_variants.m create mode 100755 test/build_design.m create mode 100755 test/runDemoTests.m create mode 100755 test/runInstallerTests.m create mode 100755 test/runNonHWTest.m create mode 100644 test/runSynthTests.m create mode 100644 test/setportmapping.m create mode 100755 test/testModel.slx diff --git a/+adi/+AD3552R/Base.m b/+adi/+AD3552R/Base.m new file mode 100755 index 0000000..2604a01 --- /dev/null +++ b/+adi/+AD3552R/Base.m @@ -0,0 +1,124 @@ +classdef (Abstract) Base < ... + adi.common.RxTx & ... + matlabshared.libiio.base & ... + adi.common.Attribute + % adi.AD3552R.Tx Transmit data to the AD3552R high speed DAC + % The adi.AD3552R.Tx System object is a signal source that can send + % complex data from the AD3552R. + % + % tx = adi.AD3552R.Tx; + % tx = adi.AD3552R.Tx('uri','192.168.2.1'); + % + % AD3552R Datasheet + % + % See also adi.CN0585.Tx + + properties (Nontunable) + % SamplesPerFrame Samples Per Frame + % Number of samples per frame, specified as an even positive + % integer from 2 to 16,777,216. Using values less than 3660 can + % yield poor performance. + SamplesPerFrame = 2 ^ 15; + end + + properties (Nontunable, Hidden) + Timeout = Inf; + kernelBuffersCount = 2; + dataTypeStr = 'uint16'; + end + + properties (Abstract, Hidden, Constant) + Type + end + + properties (Hidden, Constant) + ComplexData = false; + end + + properties + % InputSource + % Lists all the available input sources of the DAC. + % Options are: 'adc_input', 'dma_input', 'ramp_input'. + % Example: InputSource = 'dma_input'; + InputSource = 'dma_input'; + end + + properties + % OutputRange + % Lists all the available voltage ranges of the output signal. + % Options are: '0/2.5V', '0/5V', '0/10V', '-5/+5V', '-10/+10V'. + % Example: OutputRange = '-10/+10V'; + OutputRange = '-10/+10V'; + end + + properties + InputSourceSet = matlab.system.StringSet({... + 'adc_input', 'dma_input', 'ramp_input'}) + OutputRangeSet = matlab.system.StringSet({... + '0/2.5V', '0/5V', '0/10V', '-5/+5V', '-10/+10V'}) + end + + methods + %% Constructor + function obj = Base(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@matlabshared.libiio.base(varargin{:}); + end + + % Check SamplesPerFrame + function set.SamplesPerFrame(obj, value) + validateattributes(value, {'double', 'single'}, ... + {'real', 'positive', 'scalar', 'finite', 'nonnan', 'nonempty', 'integer', '>', 0, '<', 2 ^ 20 + 1}, ... + '', 'SamplesPerFrame'); + obj.SamplesPerFrame = value; + end + + % Set/Get Input Source + function result = get.InputSource(obj) + result = obj.InputSource; + end + + function set.InputSource(obj, value) + obj.InputSource = value; + end + + % Set/Get Output Range + function result = get.OutputRange(obj) + result = obj.OutputRange; + end + + function set.OutputRange(obj, value) + obj.OutputRange = value; + end + + end + + %% API Functions + methods (Hidden, Access = protected) + + function icon = getIconImpl(obj) + icon = sprintf(['AD3552R', obj.Type]); + end + + end + + %% External Dependency Methods + methods (Hidden, Static) + + function tf = isSupportedContext(bldCfg) + tf = matlabshared.libiio.ExternalDependency.isSupportedContext(bldCfg); + end + + function updateBuildInfo(buildInfo, bldCfg) + % Call the matlabshared.libiio.method first + matlabshared.libiio.ExternalDependency.updateBuildInfo(buildInfo, bldCfg); + end + + function bName = getDescriptiveName(~) + bName = 'AD3552R'; + end + + end + +end diff --git a/+adi/+AD3552R/Tx.m b/+adi/+AD3552R/Tx.m new file mode 100755 index 0000000..10e62d3 --- /dev/null +++ b/+adi/+AD3552R/Tx.m @@ -0,0 +1,88 @@ +classdef Tx < adi.common.Tx & adi.AD3552R.Base + % adi.AD3552R.Tx Transmit data to the AD3552R high speed DAC + % The adi.AD3552R.Tx System object is a signal source that can send + % complex data from the AD3552R. + % + % tx = adi.AD3552R.Tx; + % tx = adi.AD3552R.Tx('uri','192.168.2.1'); + % + % AD3552R Datasheet + % + % See also adi.CN0585.Tx + + properties (Constant) + % SamplingRate Sampling Rate + % Baseband sampling rate in Hz, specified as a scalar + % in samples per second. This value is constant. + SamplingRate = 15e6; + end + + properties (Hidden, Nontunable, Access = protected) + isOutput = true; + end + + properties (Nontunable, Hidden, Constant) + Type = 'Tx'; + end + + properties (Nontunable, Hidden) + devName = 'axi-ad3552r'; + phyDevName = 'axi-ad3552r'; + channel_names = {'voltage0', 'voltage1'}; + end + + properties + % StreamStatus + % Describes the status of the data streaming. + % Options are: 'start_stream_synced', 'start_stream', 'stop_stream'. + % Example: StreamStatus = 'stop_stream'; + StreamStatus = 'stop_stream'; + end + + properties (Hidden, Constant) + StreamStatusSet = matlab.system.StringSet({ ... + 'start_stream_synced', 'start_stream', 'stop_stream'}) + end + + methods + %% Constructor + function obj = Tx(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@adi.AD3552R.Base(varargin{:}); + end + + %% Start or stop stream transfer + function set.StreamStatus(obj, value) + + if obj.ConnectedToDevice + obj.setDeviceAttributeRAW('stream_status', value); + else + error(['StreamStatus cannot be set before initialization, ']); + end + + obj.StreamStatus = value; + + end + + end + + %% External Dependency Methods + methods (Hidden, Static) + + function tf = isSupportedContext(bldCfg) + tf = matlabshared.libiio.ExternalDependency.isSupportedContext(bldCfg); + end + + function updateBuildInfo(buildInfo, bldCfg) + % Call the matlabshared.libiio.method first + matlabshared.libiio.ExternalDependency.updateBuildInfo(buildInfo, bldCfg); + end + + function bName = getDescriptiveName(~) + bName = 'AD3552R'; + end + + end + +end diff --git a/+adi/+CN0585/Base.m b/+adi/+CN0585/Base.m new file mode 100755 index 0000000..3edc2f5 --- /dev/null +++ b/+adi/+CN0585/Base.m @@ -0,0 +1,60 @@ +classdef (Abstract, Hidden = true) Base < ... + adi.common.RxTx & ... + adi.common.Attribute & ... + matlabshared.libiio.base + %adi.CN0585.Base Class + % This class contains shared parameters and methods between TX and RX + % classes + + properties (Hidden) + iioOneBitADCDAC; + HDLSystemID + + end + + methods + %% Constructor + function obj = Base(varargin) + coder.allowpcode('plain'); + obj = obj@matlabshared.libiio.base(varargin{:}); + end + + function result = CheckMathWorksCore(obj) + result = contains(obj.HDLSystemID, "matlab"); + end + + end + + %% API Functions + methods (Hidden, Access = protected) + + function setupInit(obj) + + % GPIO CONTROLLER + + obj.iioOneBitADCDAC = getDev(obj, 'one-bit-adc-dac'); + obj.setAttributeBool('voltage0', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage1', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage2', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage3', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage4', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage5', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage6', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage7', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage8', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + obj.setAttributeBool('voltage9', 'raw', boolean(1), true, obj.iioOneBitADCDAC); + + % HDLSystemID SYSID STRING VALUE + + obj.HDLSystemID = obj.iio_context_get_attr_value(obj.iioCtx, 'hdl_system_id'); + + % UPDATED PARAMETERS + + obj.setDeviceAttributeRAW('input_source', obj.InputSource); + obj.setDeviceAttributeRAW('output_range', obj.OutputRange); + + end + + end + +end diff --git a/+adi/+CN0585/Rx.m b/+adi/+CN0585/Rx.m new file mode 100755 index 0000000..c9c8449 --- /dev/null +++ b/+adi/+CN0585/Rx.m @@ -0,0 +1,21 @@ +classdef Rx < adi.LTC2387.Rx + % adi.CN0585.Rx Receive data from the LTC2387 evaluation platform + % + % rx = adi.CN0585.Rx; + % rx = adi.CN0585.Rx('uri','192.168.2.1'); + % + % User Guide + % + % See also adi.LTC2387.Rx + + methods + %% Constructor + function obj = Rx(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@adi.LTC2387.Rx(varargin{:}); + end + + end + +end diff --git a/+adi/+CN0585/Tx0.m b/+adi/+CN0585/Tx0.m new file mode 100755 index 0000000..50bc2c7 --- /dev/null +++ b/+adi/+CN0585/Tx0.m @@ -0,0 +1,23 @@ +classdef Tx0 < adi.AD3552R.Tx & adi.CN0585.Base + % adi.CN0585.Tx Transmit data from the AD3552R evaluation platform + % + % tx0 = adi.CN0585.Tx0; + % tx0 = adi.CN0585.Tx0('uri','192.168.2.1'); + % + % User Guide + % + % See also adi.AD3552R.Tx0 + + methods + %% Constructor + function obj = Tx0(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@adi.AD3552R.Tx(varargin{:}); + obj.devName = 'axi-ad3552r-0'; + obj.phyDevName = 'axi-ad3552r-0'; + end + + end + +end diff --git a/+adi/+CN0585/Tx1.m b/+adi/+CN0585/Tx1.m new file mode 100755 index 0000000..352d5e5 --- /dev/null +++ b/+adi/+CN0585/Tx1.m @@ -0,0 +1,22 @@ +classdef Tx1 < adi.AD3552R.Tx & adi.CN0585.Base + % adi.CN0585.Tx Transmit data from the AD3552R evaluation platform + % + % tx1 = adi.CN0585.Tx1; + % tx1 = adi.CN0585.Tx1('uri','192.168.2.1'); + % + % User Guide + % + % See also adi.AD3552R.Tx1 + + methods + %% Constructor + function obj = Tx1(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@adi.AD3552R.Tx(varargin{:}); + obj.devName = 'axi-ad3552r-1'; + obj.phyDevName = 'axi-ad3552r-1'; + end + end + +end diff --git a/+adi/+LTC2387/Base.m b/+adi/+LTC2387/Base.m new file mode 100755 index 0000000..cd1badb --- /dev/null +++ b/+adi/+LTC2387/Base.m @@ -0,0 +1,72 @@ +classdef (Abstract) Base < ... + adi.common.RxTx & ... + adi.common.Attribute & ... + matlabshared.libiio.base + %LTC2387 Base Class + + properties (Nontunable) + %SamplesPerFrame Samples Per Frame + % Number of samples per frame, specified as an even positive + % integer from 2 to 16,777,216. Using values less than 3660 can + % yield poor performance. + SamplesPerFrame = 2^15; + end + + properties(Nontunable, Hidden) + Timeout = Inf; + kernelBuffersCount = 2; + dataTypeStr = 'int64'; + end + + properties (Abstract, Hidden, Constant) + Type + end + + + properties (Hidden, Constant) + ComplexData = false; + end + + methods + %% Constructor + function obj = Base(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@matlabshared.libiio.base(varargin{:}); + end + % Check SamplesPerFrame + function set.SamplesPerFrame(obj, value) + validateattributes( value, { 'double','single' }, ... + { 'real', 'positive','scalar', 'finite', 'nonnan', 'nonempty','integer','>',0,'<',2^20+1}, ... + '', 'SamplesPerFrame'); + obj.SamplesPerFrame = value; + end + end + + %% API Functions + methods (Hidden, Access = protected) + + function icon = getIconImpl(obj) + icon = sprintf(['LTC2387 ',obj.Type]); + end + + end + + %% External Dependency Methods + methods (Hidden, Static) + + function tf = isSupportedContext(bldCfg) + tf = matlabshared.libiio.ExternalDependency.isSupportedContext(bldCfg); + end + + function updateBuildInfo(buildInfo, bldCfg) + % Call the matlabshared.libiio.method first + matlabshared.libiio.ExternalDependency.updateBuildInfo(buildInfo, bldCfg); + end + + function bName = getDescriptiveName(~) + bName = 'LTC2387'; + end + + end +end diff --git a/+adi/+LTC2387/Rx.m b/+adi/+LTC2387/Rx.m new file mode 100755 index 0000000..0b1c6d6 --- /dev/null +++ b/+adi/+LTC2387/Rx.m @@ -0,0 +1,73 @@ +classdef Rx < adi.common.Rx & adi.LTC2387.Base & adi.common.Attribute + % adi.LTC2387.Rx Receive data from the LTC2387 high speed ADC + % The adi.LTC2387.Rx System object is a signal source that can receive + % complex data from the LTC2387. + % + % rx = adi.LTC2387.Rx; + % rx = adi.LTC2387.Rx('uri','192.168.2.1'); + % + % LTC2387 Datasheet + % + % See also adi.CN0585.Rx + + properties (Dependent) + %SamplingRate Sampling Rate + % Baseband sampling rate in Hz, specified as a scalar + % in samples per second. This value is constant + SamplingRate + end + properties (Hidden, Nontunable, Access = protected) + isOutput = false; + end + + properties(Nontunable, Hidden, Constant) + Type = 'Rx'; + end + + properties (Nontunable, Hidden) + devName = 'ltc2387'; + phyDevName = 'ltc2387'; + channel_names = {'voltage0','voltage1','voltage2','voltage3'}; + end + + methods + %% Constructor + function obj = Rx(varargin) + % Returns the matlabshared.libiio.base object + coder.allowpcode('plain'); + obj = obj@adi.LTC2387.Base(varargin{:}); + end + function value = get.SamplingRate(obj) + if obj.ConnectedToDevice + value= obj.getAttributeLongLong('voltage0','sampling_frequency',false); + else + value = NaN; + end + end + end + + %% API Functions + methods (Hidden, Access = protected) + function setupInit(~) + %unused + end + end + + %% External Dependency Methods + methods (Hidden, Static) + + function tf = isSupportedContext(bldCfg) + tf = matlabshared.libiio.ExternalDependency.isSupportedContext(bldCfg); + end + + function updateBuildInfo(buildInfo, bldCfg) + % Call the matlabshared.libiio.method first + matlabshared.libiio.ExternalDependency.updateBuildInfo(buildInfo, bldCfg); + end + + function bName = getDescriptiveName(~) + bName = 'LTC2387'; + end + + end +end diff --git a/+adi/Contents.m b/+adi/Contents.m index 5823d37..8beb514 100644 --- a/+adi/Contents.m +++ b/+adi/Contents.m @@ -9,5 +9,12 @@ % AD7768 - ADC % AD7768-1 - ADC % AD4030-24 - ADC -% AD4630-16 - ADC -% AD4630-24 - ADC +% AD4630-16 - ADC +% AD4630-24 - ADC +% AD43552R - DAC +% LTC2387 - DAC +% +% Boards and Platforms +% ----------------------- +% CN0585 - FMC development board for precision data acquisition + diff --git a/+adi/Version.m b/+adi/Version.m index cffb464..f749edb 100644 --- a/+adi/Version.m +++ b/+adi/Version.m @@ -2,7 +2,8 @@ % Version % BSP Version information properties (Constant) - MATLAB = 'R2021b' + Vivado = '2022.2' + MATLAB = 'R2022a' Release = '21.2.1' AppName = 'Analog Devices, Inc. Precision Toolbox' ToolboxName = 'PrecisionToolbox' diff --git a/CI/scripts_hdl/matlab_processors.tcl b/CI/scripts_hdl/matlab_processors.tcl new file mode 100755 index 0000000..12427a1 --- /dev/null +++ b/CI/scripts_hdl/matlab_processors.tcl @@ -0,0 +1,58 @@ +proc preprocess_bd {project carrier rxtx} { + + puts "Preprocessing $project $carrier $rxtx" + + switch $project { + cn0585_fmcz { + # Disconnect the ADC PACK pins + delete_bd_objs [get_bd_nets axi_ltc2387_0_adc_data] + delete_bd_objs [get_bd_nets axi_ltc2387_1_adc_data] + delete_bd_objs [get_bd_nets axi_ltc2387_2_adc_data] + delete_bd_objs [get_bd_nets axi_ltc2387_3_adc_data] + + + set sys_cstring "matlab $rxtx" + sysid_gen_sys_init_file $sys_cstring + + #Disconnect adc_valid + delete_bd_objs [get_bd_nets axi_ltc2387_0_adc_valid] + # Reconnect the adc_valid in the system + connect_bd_net [get_bd_pins axi_ltc2387_0/adc_valid] [get_bd_pins axi_ltc2387_dma/fifo_wr_en] + + if {$rxtx == "rx"} { + connect_bd_net [get_bd_pins axi_ltc2387_0/adc_data] [get_bd_pins axi_ad3552r_0/data_in_a] + connect_bd_net [get_bd_pins axi_ltc2387_1/adc_data] [get_bd_pins axi_ad3552r_0/data_in_b] + connect_bd_net [get_bd_pins axi_ltc2387_2/adc_data] [get_bd_pins axi_ad3552r_1/data_in_a] + connect_bd_net [get_bd_pins axi_ltc2387_3/adc_data] [get_bd_pins axi_ad3552r_1/data_in_b] + } + + if {$rxtx == "tx"} { + connect_bd_net [get_bd_pins axi_ltc2387_0/adc_data] [get_bd_pins util_ltc2387_adc_pack/fifo_wr_data_0] + connect_bd_net [get_bd_pins axi_ltc2387_1/adc_data] [get_bd_pins util_ltc2387_adc_pack/fifo_wr_data_1] + connect_bd_net [get_bd_pins axi_ltc2387_2/adc_data] [get_bd_pins util_ltc2387_adc_pack/fifo_wr_data_2] + connect_bd_net [get_bd_pins axi_ltc2387_3/adc_data] [get_bd_pins util_ltc2387_adc_pack/fifo_wr_data_3] + connect_bd_net [get_bd_pins axi_ltc2387_0/adc_valid] [get_bd_pins util_ltc2387_adc_pack/fifo_wr_en] + } + + if {$rxtx == "tx" || $rxtx == "rxtx"} { + + delete_bd_objs [get_bd_nets axi_ltc2387_0_adc_valid] + delete_bd_objs [get_bd_nets axi_ltc2387_1_adc_valid] + delete_bd_objs [get_bd_nets axi_ltc2387_2_adc_valid] + delete_bd_objs [get_bd_nets axi_ltc2387_3_adc_valid] + + # Connect dac valids together + connect_bd_net [get_bd_pins axi_ad3552r_0/valid_in_a] [get_bd_pins axi_ad3552r_0/valid_in_b] + connect_bd_net [get_bd_pins axi_ad3552r_0/valid_in_a] [get_bd_pins axi_ad3552r_1/valid_in_a] + connect_bd_net [get_bd_pins axi_ad3552r_0/valid_in_a] [get_bd_pins axi_ad3552r_1/valid_in_b] + } + switch $carrier { + zed { + set_property -dict [list CONFIG.NUM_MI {21}] [get_bd_cells axi_cpu_interconnect] + connect_bd_net [get_bd_pins axi_cpu_interconnect/M20_ACLK] [get_bd_pins axi_clkgen/clk_0] + connect_bd_net [get_bd_pins axi_cpu_interconnect/M20_ARESETN] [get_bd_pins sampling_clk_rstgen/peripheral_aresetn] + } + } + } + } +} diff --git a/CI/scripts_hdl/ports.json b/CI/scripts_hdl/ports.json new file mode 100755 index 0000000..51f6fb1 --- /dev/null +++ b/CI/scripts_hdl/ports.json @@ -0,0 +1,142 @@ +{ + "cn0585": { + "chip": "CN0585", + "complex": "true", + "fpga": [ + "zed" + ], + "supported_rd": [ + "rx", + "tx", + "rx & tx" + ], + "ports": [ + { + "rx": [ + { + "input": "false", + "width": 1, + "name": "util_ltc2387_adc_pack/fifo_wr_en", + "type": "valid" + }, + { + "input": "false", + "width": 16, + "name": "util_ltc2387_adc_pack/fifo_wr_data_0", + "type": "data" + }, + { + "input": "false", + "width": 16, + "name": "util_ltc2387_adc_pack/fifo_wr_data_1", + "type": "data" + }, + { + "input": "false", + "width": 16, + "name": "util_ltc2387_adc_pack/fifo_wr_data_2", + "type": "data" + }, + { + "input": "false", + "width": 16, + "name": "util_ltc2387_adc_pack/fifo_wr_data_3", + "type": "data" + }, + { + "input": "true", + "width": 1, + "name": "axi_ltc2387_0/adc_valid", + "type": "valid" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_0/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_1/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_2/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_3/adc_data", + "type": "data" + } + ], + "tx": [ + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_0/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_1/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_2/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 16, + "name": "axi_ltc2387_3/adc_data", + "type": "data" + }, + { + "input": "true", + "width": 1, + "name": "axi_ltc2387_0/adc_valid", + "type": "valid" + }, + { + "input": "false", + "width": 16, + "name": "axi_ad3552r_0/data_in_a", + "type": "data" + }, + { + "input": "false", + "width": 16, + "name": "axi_ad3552r_0/data_in_b", + "type": "data" + }, + { + "input": "false", + "width": 1, + "name": "axi_ad3552r_0/valid_in_a", + "type": "valid" + }, + { + "input": "false", + "width": 16, + "name": "axi_ad3552r_1/data_in_a", + "type": "data" + }, + { + "input": "false", + "width": 16, + "name": "axi_ad3552r_1/data_in_b", + "type": "data" + } + ] + } + ] + } +} diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/hdlcoder_ref_design_customization.m b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/hdlcoder_ref_design_customization.m new file mode 100755 index 0000000..8e2c2c9 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/hdlcoder_ref_design_customization.m @@ -0,0 +1,22 @@ +function [rd, boardName] = hdlcoder_ref_design_customization +% Reference design plugin registration file +% 1. The registration file with this name inside of a board plugin folder +% will be picked up +% 2. Any registration file with this name on MATLAB path will also be picked up +% 3. The registration file returns a cell array pointing to the location of +% the reference design plugins +% 4. The registration file also returns its associated board name +% 5. Reference design plugin must be a package folder accessible from +% MATLAB path, and contains a reference design definition file + +% Copyright 2013-2014 The MathWorks, Inc. + +rd = {... + 'AnalogDevices.cn0585_fmcz.zed.plugin_rd_rx', ... + 'AnalogDevices.cn0585_fmcz.zed.plugin_rd_tx', ... + 'AnalogDevices.cn0585_fmcz.zed.plugin_rd_rxtx', ... + }; + +boardName = 'AnalogDevices CN0585 ZED'; + +end diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_board.m b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_board.m new file mode 100755 index 0000000..22b5cf4 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_board.m @@ -0,0 +1,8 @@ +function hP = plugin_board() +% Zynq Platform PCore +% Use Plugin API to create board plugin object + +% Copyright 2015 The MathWorks, Inc. + +% Call the common board definition function +hP = AnalogDevices.plugin_board('CN0585', 'ZED'); diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rx.m b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rx.m new file mode 100755 index 0000000..002adf5 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rx.m @@ -0,0 +1,5 @@ +function hRD = plugin_rd_rx +% Reference design definition + +% Call the common reference design definition function +hRD = AnalogDevices.plugin_rd('cn0585','ZED', 'Rx'); diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rxtx.m b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rxtx.m new file mode 100755 index 0000000..dc334fc --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_rxtx.m @@ -0,0 +1,7 @@ +function hRD = plugin_rd_rxtx +% Reference design definition + +% Copyright 2014-2015 The MathWorks, Inc. + +% Call the common reference design definition function +hRD = AnalogDevices.plugin_rd('cn0585','ZED', 'Rx & Tx'); diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_tx.m b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_tx.m new file mode 100755 index 0000000..ae6ae98 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+cn0585_fmcz/+zed/plugin_rd_tx.m @@ -0,0 +1,7 @@ +function hRD = plugin_rd_tx +% Reference design definition + +% Copyright 2014-2015 The MathWorks, Inc. + +% Call the common reference design definition function +hRD = AnalogDevices.plugin_rd('cn0585', 'ZED', 'Tx'); diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRAttributeInfo.xml b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRAttributeInfo.xml new file mode 100755 index 0000000..7c9a676 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRAttributeInfo.xml @@ -0,0 +1,37 @@ + + + + Analog Devices Zynq SDR + true + + $(MATLAB_ROOT)/rtw/c/src/ext_mode/common/rtiostream_interface.c + + $(ARM_CORTEX_A_ROOT_DIR)/src/rtiostream_tcpip.c + codertarget.zynq.internal.extmodeHooksADI(hObj,'setupfcn'); + + TCP/IP + + + + + + + + + + $(TARGET_ROOT)/src/axi4Lite.c + $(TARGET_ROOT)/include + ARM_PROJECT + ADI_ZYNQ_SDR_IPADDRESS + ZYNQ_USERNAME + ZYNQ_PASSWORD + + codertarget.zynq.internal.onAfterCodeGen + codertarget.zynq.internal.onBuildEntryHook + codertarget.zynq.internal.onHardwareSelect + + diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRParameterInfo.xml b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRParameterInfo.xml new file mode 100755 index 0000000..5965474 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/ADIZynqSDRParameterInfo.xml @@ -0,0 +1,13 @@ + + + + Analog Devices Zynq SDR + + Clocking + + + + Build options + + + diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+util/adizynqsdr.xml b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/adizynqsdr.xml new file mode 100755 index 0000000..3df950a --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/adizynqsdr.xml @@ -0,0 +1,16 @@ + + + + Analog Devices Zynq SDR + ARM Cortex-A9 + ARM Cortex-A + ARM Compatible->ARM Cortex + + "$(ARM_CORTEX_A_ROOT_DIR)/ssh_download.bat" + "$(MATLAB_ROOT)/toolbox/idelink/foundation/hostapps" root analog $(ADI_ZYNQ_SDR_IPADDRESS) /home/analog/Downloads + + $(TARGET_ROOT)/registry/parameters/ADIZynqSDRParameterInfo.xml + $(TARGET_ROOT)/registry/attributes/ADIZynqSDRAttributeInfo.xml + + + diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/+util/extmodeHooksADI.m b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/extmodeHooksADI.m new file mode 100755 index 0000000..a600f58 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/+util/extmodeHooksADI.m @@ -0,0 +1,21 @@ +function extmodeHooksADI(hObj,hookpoint) + +% Copyright 2014-2015 The MathWorks, Inc. + +modelName = get(getModel(hObj),'Name'); +modelName = sprintf('%s.elf', modelName); +data = codertarget.data.getData(hObj); +h__z = zynq(data.RTOS); +h__z.IPAddress = getenv('ADI_ZYNQ_SDR_IPADDRESS'); +h__z.Username = 'root'; +h__z.Password = 'analog'; + +switch (lower(hookpoint)) + case 'preconnectfcn', + waitForAppToStart(h__z, modelName, 60); + case 'setupfcn' + checkConnection(h__z); + otherwise +end + +end diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/add_clocks.m b/hdl/vendor/AnalogDevices/+AnalogDevices/add_clocks.m new file mode 100755 index 0000000..1faebf9 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/add_clocks.m @@ -0,0 +1,22 @@ +function add_clocks(hRD,project,design) + +switch lower(project) + case 'cn0585' + switch(upper(design)) + case 'RX' + hRD.addClockInterface( ... + 'ClockConnection', 'axi_clkgen/clk_0', ... + 'ResetConnection', 'sampling_clk_rstgen/peripheral_aresetn'); + + case 'TX' + hRD.addClockInterface( ... + 'ClockConnection', 'axi_clkgen/clk_0', ... + 'ResetConnection', 'sampling_clk_rstgen/peripheral_aresetn'); + case 'RX & TX' + hRD.addClockInterface( ... + 'ClockConnection', 'axi_clkgen/clk_0', ... + 'ResetConnection', 'sampling_clk_rstgen/peripheral_aresetn'); + otherwise + error('Unknown reference design'); + end +end diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/add_io.m b/hdl/vendor/AnalogDevices/+AnalogDevices/add_io.m new file mode 100755 index 0000000..58f6341 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/add_io.m @@ -0,0 +1,17 @@ +function add_io(hRD,project,fpga,type) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Add AXI4 and AXI4-Lite slave interfaces +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +out = AnalogDevices.get_memory_axi_interface_info(fpga,lower(project)); +hRD.addAXI4SlaveInterface( ... + 'InterfaceConnection', out.InterfaceConnection, ... + 'BaseAddress', out.BaseAddress, ... + 'MasterAddressSpace', out.MasterAddressSpace); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Add Reference design interfaces +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +AnalogDevices.add_io_ports(hRD,lower(project),lower(type),lower(fpga)); + +end \ No newline at end of file diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/add_io_ports.m b/hdl/vendor/AnalogDevices/+AnalogDevices/add_io_ports.m new file mode 100755 index 0000000..645e5a4 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/add_io_ports.m @@ -0,0 +1,92 @@ +function root = add_io_ports(hRD,project,type,fpga) + +[filepath,~,~] = fileparts(mfilename('fullpath')); +fileName = fullfile(filepath,'ports.json'); +fid = fopen(fileName); +raw = fread(fid,inf); +str = char(raw'); +fclose(fid); +data = jsondecode(str); + +project = erase(project,'-'); +if ~contains(fields(data),project) + error(sprintf('No project found in database for %s',project)); +end + +root = getfield(data, project); + +if ~contains(root.supported_rd,type) + error(sprintf('No project found in database for %s',project)); +end + +if ~contains(root.fpga,fpga) + error(sprintf('No project found in database for %s',fpga)); +end + + +if contains(type,'rx') + process(hRD, root.ports.rx, 'rx'); +end +if contains(type,'tx') + process(hRD, root.ports.tx, 'tx'); +end + + +end + +function process(hRD, rtx, type) +count = [-1 -1]; +for i = 1:length(rtx) + rx = rtx(i); + if strcmpi(rx.type,'valid') + hRD.addInternalIOInterface( ... + 'InterfaceID', rx.m_name, ... + 'InterfaceType', inout(rx.input), ... + 'PortName', inout_pn(rx.input, type), ... + 'PortWidth', rx.width, ... + 'InterfaceConnection', rx.name, ... + 'IsRequired', false); + elseif strcmpi(rx.type,'data') + if strcmp(rx.input, 'true') + count(1)=count(1)+1; + else + count(2)=count(2)+1; + end + hRD.addInternalIOInterface( ... + 'InterfaceID', rx.m_name, ... + 'InterfaceType', inout(rx.input), ... + 'PortName', inout_pn_d(rx.input,count,type), ... + 'PortWidth', rx.width, ... + 'InterfaceConnection', rx.name, ... + 'IsRequired', false); + else + error(sprintf('Unknown port type %s',rx.type)); + end +end +end + +%% +function out = inout_pn_d(in,count,type) +if strcmp(in, 'true') + out = sprintf('dut_data_in_%d_%s',count(1), type); +else + out = sprintf('dut_data_out_%d_%s',count(2), type); +end +end +%% +function out = inout_pn(in, type) + if strcmp(in, 'true') + out = sprintf('dut_data_valid_in_%s', type); + else + out = sprintf('dut_data_valid_out_%s', type); + end +end +%% +function out = inout(in) +if strcmp(in, 'true') + out = 'IN'; +else + out = 'OUT'; +end +end + diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m b/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m new file mode 100755 index 0000000..fb1328e --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/get_memory_axi_interface_info.m @@ -0,0 +1,21 @@ +function out = get_memory_axi_interface_info(fpga,project) + + +switch project + case 'cn0585' + switch fpga + case{'ZED'} + InterfaceConnection = 'axi_cpu_interconnect/M20_AXI'; + BaseAddress = '0x43C00000'; + MasterAddressSpace = 'sys_ps7/Data'; + otherwise + error(sprintf('Unknown Project FPGA %s/%s',project,fpga)); %#ok<*SPERR> + end + otherwise + error(sprintf('Unknown Project %s',project)); %#ok<*SPERR> +end + +out = struct('InterfaceConnection', InterfaceConnection, ... + 'BaseAddress', BaseAddress, ... + 'MasterAddressSpace', MasterAddressSpace); +end diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/install.m b/hdl/vendor/AnalogDevices/+AnalogDevices/install.m new file mode 100755 index 0000000..9337a51 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/install.m @@ -0,0 +1,61 @@ +function install(mode) +% AnalogDevices.install adds/removes AnalogDevices HDL BSPs + +% Copyright 2015 MathWorks, Inc. All Rights Reserved. + + if nargin == 0 + mode = 0; + end + + %% Initialization + % Determine where we're operating out of + vendorRootDir = fileparts(strtok(mfilename('fullpath'), '+')); + + % Add/remove the common contents + commonRootDir = fullfile(fileparts(fileparts(vendorRootDir)), 'common'); + olddir = cd(commonRootDir); + cleanup = onCleanup(@()cd(olddir)); + hdlbsp.install(mode); + + + % Add/remove the vendor contents + paths = {... + fullfile(vendorRootDir),... + }; + + hdlbsp.util.vendorInstall(mode,paths); + + % Copy the Zynq SDR target definition file into the support package + source = []; + destination = []; + zynqRootDir = codertarget.zynq.internal.getSpPkgRootDir; + armRootDir = codertarget.arm_cortex_a.internal.getSpPkgRootDir; + + zynqTargetDir = fullfile(zynqRootDir,'registry/targethardware'); + source = [source {fullfile(vendorRootDir, '/+AnalogDevices/+util/adizynqsdr.xml')}]; + destination = [destination {fullfile(zynqTargetDir, 'adizynqsdr.xml')}]; + + zynqTargetDir = fullfile(zynqRootDir,'registry/attributes'); + source = [source {fullfile(vendorRootDir, '/+AnalogDevices/+util/ADIZynqSDRAttributeInfo.xml')}]; + destination = [destination {fullfile(zynqTargetDir, 'ADIZynqSDRAttributeInfo.xml')}]; + + zynqTargetDir = fullfile(zynqRootDir,'registry/parameters'); + source = [source {fullfile(vendorRootDir, '/+AnalogDevices/+util/ADIZynqSDRParameterInfo.xml')}]; + destination = [destination {fullfile(zynqTargetDir, 'ADIZynqSDRParameterInfo.xml')}]; + + source = [source {fullfile(vendorRootDir, '/+AnalogDevices/+util/extmodeHooksADI.m')}]; + destination = [destination {fullfile(zynqRootDir, '/+codertarget/+zynq/+internal/extmodeHooksADI.m')}]; + + source = [source {fullfile(armRootDir,'ssh_download.bat')}]; + destination = [destination {fullfile(zynqRootDir, 'ssh_download.bat')}]; + + if(mode == 0) + for i = 1:length(source) + copyfile(char(source(:,i)), char(destination(:,i)), 'f'); + end + else + for i = 1:length(destination) + delete(char(destination(:,i))); + end + end +end \ No newline at end of file diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_board.m b/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_board.m new file mode 100755 index 0000000..7fa2607 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_board.m @@ -0,0 +1,42 @@ +function hB = plugin_board(project, board) +% Use Plugin API to create board plugin object + +if nargin < 2 + board = ""; +end +hB = hdlcoder.Board; + +pname = project; + +% Target Board Information +hB.BoardName = sprintf('AnalogDevices %s', upper(pname)); +if nargin > 1 + hB.BoardName = sprintf('%s %s', hB.BoardName, upper(board)); +end + +% FPGA Device +hB.FPGAVendor = 'Xilinx'; + +% Determine the device based on the board +switch lower(project) + + case {'cn0585'} + switch(upper(board)) + case 'ZED' + hB.FPGADevice = sprintf('xc7%s', 'z020'); + hB.FPGAPackage = 'clg484'; + hB.FPGASpeed = '-1'; + hB.FPGAFamily = 'Zynq'; + end + +end + +% Tool Info +hB.SupportedTool = {'Xilinx Vivado'}; + +% FPGA JTAG chain position +hB.JTAGChainPosition = 2; + +%% Add interfaces +% Standard "External Port" interface + diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m b/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m new file mode 100755 index 0000000..3ae31d6 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/plugin_rd.m @@ -0,0 +1,90 @@ +function hRD = plugin_rd(project, board, design) +% Reference design definition + +% Copyright 2014-2015 The MathWorks, Inc. + +pname = upper(project); +ppath = project; +if strcmpi(project, 'cn0585') + ppath = 'cn0585_fmcz'; +end + +% Construct reference design object +hRD = hdlcoder.ReferenceDesign('SynthesisTool', 'Xilinx Vivado'); + +% Create the reference design for the SOM-only +% This is the base reference design that other RDs can build upon +hRD.ReferenceDesignName = sprintf('%s %s (%s)', pname, upper(board), upper(design)); + +% Determine the board name based on the design +hRD.BoardName = sprintf('AnalogDevices %s %s', pname, upper(board)); + +% Tool information +hRD.SupportedToolVersion = {'2022.2'}; + +% Get the root directory +rootDir = fileparts(strtok(mfilename('fullpath'), '+')); + +% Design files are shared +hRD.SharedRD = true; +hRD.SharedRDFolder = fullfile(rootDir, 'vivado'); + +%% Set top level project pieces +hRD.addParameter( ... + 'ParameterID', 'project', ... + 'DisplayName', 'HDL Project Subfolder', ... + 'DefaultValue', lower(ppath)); + +hRD.addParameter( ... + 'ParameterID', 'carrier', ... + 'DisplayName', 'HDL Project Carrier', ... + 'DefaultValue', lower(board)); + + +%% Add custom design files +% add custom Vivado design +hRD.addCustomVivadoDesign( ... + 'CustomBlockDesignTcl', fullfile('projects', 'scripts', 'system_project_rxtx.tcl'), ... + 'CustomTopLevelHDL', fullfile('projects', lower(ppath), lower(board), 'system_top.v')); + +hRD.BlockDesignName = 'system'; + +% custom constraint files +hRD.CustomConstraints = {... + fullfile('projects', lower(ppath), lower(board), 'system_constr.xdc'), ... + fullfile('projects', 'common', lower(board), sprintf('%s_system_constr.xdc', lower(board))), ... + }; + +% custom source files +hRD.CustomFiles = {... + fullfile('projects')..., + fullfile('library')..., + fullfile('scripts')..., + }; + +hRD.addParameter( ... + 'ParameterID', 'ref_design', ... + 'DisplayName', 'Reference Type', ... + 'DefaultValue', lower(strrep(design, ' & ',''))); + +hRD.addParameter( ... + 'ParameterID', 'fpga_board', ... + 'DisplayName', 'FPGA Boad', ... + 'DefaultValue', upper(board)); + +hRD.addParameter( ... + 'ParameterID', 'preprocess', ... + 'DisplayName', 'Preprocess', ... + 'DefaultValue', 'off'); + +hRD.addParameter( ... + 'ParameterID', 'postprocess', ... + 'DisplayName', 'Postprocess', ... + 'DefaultValue', 'off'); + +%% Add interfaces +% add clock interface +AnalogDevices.add_clocks(hRD,project,design) + +%% Add IO +AnalogDevices.add_io(hRD,project,board,design); diff --git a/hdl/vendor/AnalogDevices/+AnalogDevices/uninstall.m b/hdl/vendor/AnalogDevices/+AnalogDevices/uninstall.m new file mode 100755 index 0000000..ce1a433 --- /dev/null +++ b/hdl/vendor/AnalogDevices/+AnalogDevices/uninstall.m @@ -0,0 +1,7 @@ +function uninstall +% AnalogDevices.uninstall removes AnalogDevices HDL BSPs + +% Copyright 2015 MathWorks, Inc. All Rights Reserved. + + AnalogDevices.install(1); +end diff --git a/hdl/vendor/AnalogDevices/Contents.m b/hdl/vendor/AnalogDevices/Contents.m new file mode 100755 index 0000000..9aa4ea5 --- /dev/null +++ b/hdl/vendor/AnalogDevices/Contents.m @@ -0,0 +1,2 @@ +% Precision Toolbox: Analog Devices, Inc +% Version 21.1.1 (R2021a) 3-Dec-2021 \ No newline at end of file diff --git a/hdl/vendor/AnalogDevices/hdlcoder_board_customization.m b/hdl/vendor/AnalogDevices/hdlcoder_board_customization.m new file mode 100755 index 0000000..d553377 --- /dev/null +++ b/hdl/vendor/AnalogDevices/hdlcoder_board_customization.m @@ -0,0 +1,15 @@ +function r = hdlcoder_board_customization +% Board plugin registration file +% 1. Any registration file with this name on MATLAB path will be picked up +% 2. Registration file returns a cell array pointing to the location of +% the board plugins +% 3. Board plugin must be a package folder accessible from MATLAB path, +% and contains a board definition file + +% Copyright 2012-2013 The MathWorks, Inc. + +r = { ... + 'AnalogDevices.cn0585_fmcz.zed.plugin_board' ..., + }; +end +% LocalWords: Zynq ZC diff --git a/pcx_examples/streaming/cn0585_fmcz/CN0585_streaming.m b/pcx_examples/streaming/cn0585_fmcz/CN0585_streaming.m new file mode 100644 index 0000000..a9d9e81 --- /dev/null +++ b/pcx_examples/streaming/cn0585_fmcz/CN0585_streaming.m @@ -0,0 +1,74 @@ +% CN0585 Streaming example + +board_ip = 'local_board_ip'; +uri = cat(2, 'ip:', board_ip); + +% Describe the devices + +cn0585_device_rx = adi.CN0585.Rx('uri',uri); +cn0585_device_tx0 = adi.CN0585.Tx0('uri',uri); +cn0585_device_tx1 = adi.CN0585.Tx1('uri',uri); + +cn0585_device_tx0.EnableCyclicBuffers = true; +cn0585_device_tx1.EnableCyclicBuffers = true; + +cn0585_device_rx.BufferTypeConversionEnable = true; + +% Enable the channels to write data to (options are 1, 2 ) + +cn0585_device_tx0.EnabledChannels = [1, 2]; +cn0585_device_tx1.EnabledChannels = [1, 2]; + +% Enable the channels to read data from (options are 1, 2, 3 ,4 ) + +cn0585_device_rx.EnabledChannels = [1, 2, 3, 4]; + +% Generate the sinewave signal + +amplitude = 2 ^ 15; +sampFreq = cn0585_device_tx0.SamplingRate; +toneFreq = 1e3; +N = sampFreq / toneFreq; +x = linspace(-pi, pi, N).'; +sine_wave = amplitude * sin(x); + +% Continuously load data in the buffer and configure the GPIOs state +% (SetupInit Base file) +% DAC1 has to be updated and started first and then DAC0 in order to have syncronized data between devices + +cn0585_device_tx1([sine_wave, sine_wave]); +cn0585_device_tx0([sine_wave, sine_wave]); + +% Stream status available options: "start_stream_synced", "start_stream", "stop_stream" + +cn0585_device_tx1.StreamStatus = 'start_stream'; +cn0585_device_tx0.StreamStatus = 'start_stream'; + +% The data will be stored inside "data" variable + +data = cn0585_device_rx(); + +title('ADAQ23876 Channels'); +subplot(4, 1, 1); +plot(data(:, 1)); +ylabel('Channel A'); +subplot(4, 1, 2); +plot(data(:, 2)); +ylabel('Channel B'); +subplot(4, 1, 3); +plot(data(:, 3)); +ylabel('Channel C'); +subplot(4, 1, 4); +plot(data(:, 4)); +ylabel('Channel D'); +xlabel('Number of samples'); + +% Release the device + +cn0585_device_tx1.StreamStatus = 'stop_stream'; +cn0585_device_tx0.StreamStatus = 'stop_stream'; + +cn0585_device_tx1.release(); +cn0585_device_tx0.release(); + +cn0585_device_rx.release(); diff --git a/pcx_examples/streaming/cn0585_fmcz/CN0585_streaming_axi4lite_read_write.m b/pcx_examples/streaming/cn0585_fmcz/CN0585_streaming_axi4lite_read_write.m new file mode 100755 index 0000000..935eaa0 --- /dev/null +++ b/pcx_examples/streaming/cn0585_fmcz/CN0585_streaming_axi4lite_read_write.m @@ -0,0 +1,96 @@ +% CN0585 Streaming example for AXI4 Lite register read/write + +board_ip = 'local_board_ip'; +uri = cat(2, 'ip:', board_ip); + +% Describe the devices + +cn0585_device_rx = adi.CN0585.Rx('uri',uri); +cn0585_device_tx0 = adi.CN0585.Tx0('uri',uri); +cn0585_device_tx1 = adi.CN0585.Tx1('uri',uri); + +cn0585_device_tx0.EnableCyclicBuffers = true; +cn0585_device_tx1.EnableCyclicBuffers = true; + +cn0585_device_rx.BufferTypeConversionEnable = true; + +% Enable the channels to write data to (options are 1, 2) + +cn0585_device_tx0.EnabledChannels = [1, 2]; +cn0585_device_tx1.EnabledChannels = [1, 2]; + +% Enable the channels to read data from (options are 1, 2, 3 ,4) + +cn0585_device_rx.EnabledChannels = [1, 2, 3, 4]; + +write_reg = soc.libiio.aximm.WriteHost(devName = 'mwipcore0:mmwr-channel0', IPAddress = board_ip); % MathWorks IP Core Write channel +read_reg = soc.libiio.aximm.WriteHost(devName = 'mwipcore0:mmrd-channel1', IPAddress = board_ip); % MathWorks IP Core Read channel + +% Input source available options: 'adc_input', 'dma_input', 'ramp_input' + +cn0585_device_tx0.InputSource = 'dma_input'; +cn0585_device_tx1.InputSource = 'dma_input'; + +% Output range available options: '0/2.5V', '0/5V', '0/10V', '-5/+5V', '-10/+10V' + +cn0585_device_tx0.OutputRange = '-10/+10V'; +cn0585_device_tx1.OutputRange = '-10/+10V'; + +% Generate the sinewave signal + +amplitude = 2 ^ 15; +sampFreq = cn0585_device_tx0.SamplingRate; +toneFreq = 1e3; +N = sampFreq / toneFreq; +x = linspace(-pi, pi, N).'; +sine_wave = amplitude * sin(x); + +% Continuously load data in the buffer and configure the GPIOs state +% (SetupInit Base file) +% DAC1 has to be updated and started first and then DAC0 in order to have syncronized data between devices + +cn0585_device_tx1([sine_wave, sine_wave]); +cn0585_device_tx0([sine_wave, sine_wave]); + +% Stream status available options: "start_stream_synced", "start_stream", "stop_stream" + +cn0585_device_tx1.StreamStatus = 'start_stream'; +cn0585_device_tx0.StreamStatus = 'start_stream'; + +% The data will be stored inside "data" variable + +data = cn0585_device_rx(); + +if cn0585_device_tx0.CheckMathWorksCore() + + write_reg.writeReg(hex2dec('100'), 85); + write_reg.writeReg(hex2dec('104'), 22); + + fprintf('Read value from the 0x108 register is: %d \n', read_reg.readReg(hex2dec('108'))); + fprintf('Read value from the 0x10c register is: %d \n', read_reg.readReg(hex2dec('10c'))); +end + +title('ADAQ23876 Channels'); +subplot(4, 1, 1); +plot(data(:, 1)); +ylabel('Channel A'); +subplot(4, 1, 2); +plot(data(:, 2)); +ylabel('Channel B'); +subplot(4, 1, 3); +plot(data(:, 3)); +ylabel('Channel C'); +subplot(4, 1, 4); +plot(data(:, 4)); +ylabel('Channel D'); +xlabel('Number of samples'); + +% Release the device + +cn0585_device_tx1.StreamStatus = 'stop_stream'; +cn0585_device_tx0.StreamStatus = 'stop_stream'; + +cn0585_device_tx1.release(); +cn0585_device_tx0.release(); + +cn0585_device_rx.release(); diff --git a/test/DemoTests.m b/test/DemoTests.m new file mode 100755 index 0000000..a2f7d88 --- /dev/null +++ b/test/DemoTests.m @@ -0,0 +1,74 @@ +classdef DemoTests < matlab.uitest.TestCase + + properties + root = ''; + end + + methods(TestClassSetup) + function addpaths(testCase) + here = mfilename('fullpath'); + here = strsplit(here,'/'); + here = fullfile('/',here{1:end-2}); + testCase.root = here; + addpath(genpath(fullfile(here,'hdl'))); + end + function setupVivado(~) + v=ver('matlab'); Release = v.Release; + switch Release + case '(R2017a)' + vivado = '2016.2'; + case '(R2017b)' + vivado = '2017.4'; + case '(R2018b)' + vivado = '2017.4'; + case '(R2019a)' + vivado = '2018.2'; + case '(R2019b)' + vivado = '2018.2'; + case '(R2020a)' + vivado = '2018.2'; + case '(R2020b)' + vivado = '2018.2'; + case '(R2021a)' + vivado = '2018.2'; + case '(R2021b)' + vivado = '2021.1'; + case '(R2022a)' + vivado = '2022.2'; + end + if ispc + hdlsetuptoolpath('ToolName', 'Xilinx Vivado', ... + 'ToolPath', ['C:\Xilinx\Vivado\',vivado,'\bin\vivado.bat']); + elseif isunix + hdlsetuptoolpath('ToolName', 'Xilinx Vivado', ... + 'ToolPath', ['/opt/Xilinx/Vivado/',vivado,'/bin/vivado']); + end + + end + end + + methods(TestMethodTeardown) + function cleanup_hdl_prj(testCase) + dir = fullfile(testCase.root,'test','hdl_prj'); + if exist(dir, 'dir') + rmdir(fullfile(testCase.root,'test','hdl_prj'), 's'); + end + end + end + + methods(Test) + %function buildHDLDAQ2ZCU102_BOOTBIN(testCase) + % cd(fullfile(testCase.root,'test')); + % out = hdlworkflow_daq2_zcu102_rx('2018.2'); + % if ~isempty(out) + % disp(out.message); + % end + % % Check for BOOT.BIN + % if exist('hdl_prj/vivado_ip_prj/boot/BOOT.BIN', 'file') ~= 2 + % error('BOOT.BIN Failed'); + % end + %end + end + +end + diff --git a/test/NonHWTest.m b/test/NonHWTest.m new file mode 100755 index 0000000..4e04fc9 --- /dev/null +++ b/test/NonHWTest.m @@ -0,0 +1,28 @@ +classdef NonHWTest < matlab.unittest.TestCase + + properties(TestParameter) + rootClasses = {... + {'AD9081',{'Rx','Tx'}},... + {'AD9144',{'Tx'}},... + {'AD9152',{'Tx'}},... + {'AD9467',{'Rx'}},... + {'AD9680',{'Rx'}},... + {'DAQ2',{'Rx','Tx'}},... + {'QuadMxFE',{'Rx','Tx'}}... + }; + end + + methods (Test) + + function call_constructors(testCase,rootClasses) + for trx = rootClasses{2} + sdr = eval(['adi.',rootClasses{1},'.',trx{:},'()']); + testCase.assertEqual(class(sdr),['adi.',rootClasses{1},'.',trx{:}]); + end + end + + end + + +end + diff --git a/test/adi_build.tcl b/test/adi_build.tcl new file mode 100755 index 0000000..d4d155a --- /dev/null +++ b/test/adi_build.tcl @@ -0,0 +1,79 @@ +global fpga_board + +if {[info exists fpga_board]} { + puts "===========" + puts $fpga_board + puts "===========" +} else { + # Set to something not ZCU102 + set fpga_board "ZYNQ" +} + +# Build the project +update_compile_order -fileset sources_1 +reset_run impl_1 +reset_run synth_1 +launch_runs synth_1 +wait_on_run synth_1 +launch_runs impl_1 -to_step write_bitstream +wait_on_run impl_1 + +# Define local variables +set cdir [pwd] +set sdk_loc vivado_prj.sdk + +# Export the hdf +file delete -force $sdk_loc +file mkdir $sdk_loc +write_hw_platform -fixed -force -include_bit -file $sdk_loc/system_top.xsa + +# Close the Vivado project +close_project + +# Create the BOOT.bin +#exec xsdk -batch -source $cdir/projects/scripts/fsbl_build.tcl -tclargs $fpga_board -wait + +if {$fpga_board eq "ZCU102"} { + exec hsi -source $cdir/projects/scripts/pmufw_zynqmp.tcl + file copy -force $cdir/projects/scripts/fixmake.sh $cdir/fixmake.sh + exec chmod +x fixmake.sh + + #exec ./fixmake.sh + #cd pmufw + #exec make + #cd .. + if [catch "exec -ignorestderr ./fixmake.sh" ret opt] { + set makeRet [lindex [dict get $opt -errorcode] end] + puts "make returned with $makeRet" + } + if {[file exist pmufw/executable.elf] eq 0} { + puts "ERROR: pmufw not built" + return -code error 10 + } else { + puts "pmufw built correctly!" + } + + set vversion [version -short] + exec xsdk -batch -source $cdir/projects/scripts/fsbl_build_zynqmp.tcl $vversion + if {[file exist boot/BOOT.BIN] eq 0} { + puts "ERROR: BOOT.BIN not built" + return -code error 11 + } else { + puts "BOOT.BIN built correctly!" + } + +} else { + exec xsdk -batch -source $cdir/projects/scripts/fsbl_build_zynq.tcl + if {[file exist boot/BOOT.BIN] eq 0} { + puts "ERROR: BOOT.BIN not built" + return -code error 11 + } else { + puts "BOOT.BIN built correctly!" + } +} + +puts "------------------------------------" +puts "Embedded system build completed." +puts "You may close this shell." +puts "------------------------------------" +exit diff --git a/test/board_variants.m b/test/board_variants.m new file mode 100755 index 0000000..441c4ab --- /dev/null +++ b/test/board_variants.m @@ -0,0 +1,17 @@ +function r = board_variants +% Board plugin registration file +% 1. Any registration file with this name on MATLAB path will be picked up +% 2. Registration file returns a cell array pointing to the location of +% the board plugins +% 3. Board plugin must be a package folder accessible from MATLAB path, +% and contains a board definition file + +% Copyright 2023 The MathWorks, Inc. + +r = { ... + 'AnalogDevices.cn0585_fmcz.zed.plugin_rd_rx', ... + 'AnalogDevices.cn0585_fmcz.zed.plugin_rd_tx', ... + 'AnalogDevices.cn0585_fmcz.zed.plugin_rd_rxtx', ... + }; +end +% LocalWords: Zynq ZC diff --git a/test/build_design.m b/test/build_design.m new file mode 100755 index 0000000..8930f65 --- /dev/null +++ b/test/build_design.m @@ -0,0 +1,82 @@ + +function out = build_design(config,ReferenceDesignName,vivado_version,mode,board_name,SynthesizeDesign,folder) + +%% Restore the Model to default HDL parameters +%hdlrestoreparams('testModel/HDL_DUT'); + +%% Set port mapping based on design configuration +mdl = setportmapping(mode,ReferenceDesignName,board_name); + +%% Model HDL Parameters + +%% Set Model mdl HDL parameters +hdlset_param(mdl, 'HDLSubsystem', [mdl,'/HDL_DUT']); +hdlset_param(mdl, 'ReferenceDesign', ReferenceDesignName); +hdlset_param(mdl, 'SynthesisTool', config.SupportedTool{:}); +hdlset_param(mdl, 'SynthesisToolChipFamily', config.FPGAFamily); +hdlset_param(mdl, 'SynthesisToolDeviceName', config.FPGADevice); +hdlset_param(mdl, 'SynthesisToolPackageName', config.FPGAPackage); +hdlset_param(mdl, 'SynthesisToolSpeedValue', config.FPGASpeed); +hdlset_param(mdl, 'TargetPlatform', config.BoardName); +hdlset_param(mdl, 'TargetLanguage', 'Verilog'); +hdlset_param(mdl, 'TargetDirectory', [folder,'\hdlsrc']); +hdlset_param(mdl, 'Workflow', 'IP Core Generation'); +hdlset_param([mdl,'/HDL_DUT'], 'ProcessorFPGASynchronization', 'Free running'); + +%% Workflow Configuration Settings +% Construct the Workflow Configuration Object with default settings +hWC = hdlcoder.WorkflowConfig('SynthesisTool','Xilinx Vivado','TargetWorkflow','IP Core Generation'); + +% Specify the top level project directory +hWC.ProjectFolder = folder; +hWC.ReferenceDesignToolVersion = vivado_version; +hWC.IgnoreToolVersionMismatch = true; +hWC.AllowUnsupportedToolVersion = true; + +% Set Workflow tasks to run +hWC.RunTaskGenerateRTLCodeAndIPCore = true; +hWC.RunTaskCreateProject = true; +hWC.RunTaskGenerateSoftwareInterfaceModel = false; +hWC.RunTaskBuildFPGABitstream = SynthesizeDesign; +hWC.RunTaskProgramTargetDevice = false; + +% Set properties related to 'RunTaskGenerateRTLCodeAndIPCore' Task +hWC.IPCoreRepository = ''; +hWC.GenerateIPCoreReport = false; + +% Set properties related to 'RunTaskCreateProject' Task +hWC.Objective = hdlcoder.Objective.None; +hWC.AdditionalProjectCreationTclFiles = ''; +hWC.EnableIPCaching = false; + +% Set properties related to 'RunTaskGenerateSoftwareInterfaceModel' Task +hWC.OperatingSystem = 'Linux'; + +% Set properties related to 'RunTaskBuildFPGABitstream' Task +hWC.RunExternalBuild = false; +%hWC.TclFileForSynthesisBuild = hdlcoder.BuildOption.Default; +%hWC.CustomBuildTclFile = ''; + +hWC.TclFileForSynthesisBuild = hdlcoder.BuildOption.Custom; +hWC.CustomBuildTclFile = '../hdl/vendor/AnalogDevices/vivado/projects/scripts/adi_build.tcl'; + +% Set properties related to 'RunTaskProgramTargetDevice' Task +%hWC.ProgrammingMethod = hdlcoder.ProgrammingMethod.Download; +%hWC.ProgrammingMethod = hdlcoder.ProgrammingMethod.Custom; + +% Validate the Workflow Configuration Object +hWC.validate; + +%% Run the workflow +try + hdlcoder.runWorkflow([mdl,'/HDL_DUT'], hWC, 'Verbosity', 'on'); + close_system(mdl, false); + bdclose('all'); + out = []; +catch ME + if SynthesizeDesign && exist([folder,'/vivado_ip_prj/boot/BOOT.BIN'],'file') + ME = []; + end + out = ME;%.identifier +end + diff --git a/test/runDemoTests.m b/test/runDemoTests.m new file mode 100755 index 0000000..e93f62d --- /dev/null +++ b/test/runDemoTests.m @@ -0,0 +1,44 @@ +function suite = runDemoTests(name) + +import matlab.unittest.TestRunner; +import matlab.unittest.TestSuite; +import matlab.unittest.plugins.TestReportPlugin; +import matlab.unittest.plugins.XMLPlugin +import matlab.unittest.plugins.DiagnosticsValidationPlugin + +suite = testsuite({'DemoTests'}); +xmlFile = 'BSPDemoTests.xml'; + +if nargin > 0 + xmlFile = [name,'_DemoTests.xml']; + suite = suite.selectIf('Name',['*',name,'*']); +end + + +try + runner = matlab.unittest.TestRunner.withTextOutput('OutputDetail',1); + runner.addPlugin(DiagnosticsValidationPlugin) + + xmlFile = 'BSPDemoTests.xml'; + plugin = XMLPlugin.producingJUnitFormat(xmlFile); + runner.addPlugin(plugin); + + results = runner.run(suite); + + t = table(results); + disp(t); + disp(repmat('#',1,80)); + for test = results + if test.Failed + disp(test.Name); + end + end +catch e + disp(getReport(e,'extended')); + bdclose('all'); + exit(1); +end + +save(['BSPInstallerTest_',datestr(now,'dd_mm_yyyy-HH:MM:SS'),'.mat'],'t'); +bdclose('all'); +exit(any([results.Failed])); diff --git a/test/runInstallerTests.m b/test/runInstallerTests.m new file mode 100755 index 0000000..14825fa --- /dev/null +++ b/test/runInstallerTests.m @@ -0,0 +1,48 @@ +function runInstallerTests(board) + +import matlab.unittest.TestRunner; +import matlab.unittest.TestSuite; +import matlab.unittest.plugins.TestReportPlugin; +import matlab.unittest.plugins.XMLPlugin +import matlab.unittest.plugins.DiagnosticsValidationPlugin +import matlab.unittest.parameters.Parameter + +SynthesizeDesign = {false}; +param = Parameter.fromData('SynthesizeDesign',SynthesizeDesign); + +if nargin == 0 + suite = testsuite({'BSPInstallerTests'}); +else + boards = ['*',lower(board),'*']; + suite = TestSuite.fromClass(?BSPInstallerTests,'ExternalParameters',param); + suite = suite.selectIf('ParameterProperty','configs', 'ParameterName',boards); +end + +try + + runner = matlab.unittest.TestRunner.withTextOutput('OutputDetail',1); + runner.addPlugin(DiagnosticsValidationPlugin) + + xmlFile = 'BSPTestResults.xml'; + plugin = XMLPlugin.producingJUnitFormat(xmlFile); + runner.addPlugin(plugin); + + results = runner.run(suite); + + t = table(results); + disp(t); + disp(repmat('#',1,80)); + for test = results + if test.Failed + disp(test.Name); + end + end +catch e + disp(getReport(e,'extended')); + bdclose('all'); + exit(1); +end + +save(['BSPInstallerTest_',datestr(now,'dd_mm_yyyy-HH:MM:SS'),'.mat'],'t'); +bdclose('all'); +exit(any([results.Failed])); diff --git a/test/runNonHWTest.m b/test/runNonHWTest.m new file mode 100755 index 0000000..c3561c1 --- /dev/null +++ b/test/runNonHWTest.m @@ -0,0 +1,39 @@ +function suite = runNonHWTest() + +import matlab.unittest.TestRunner; +import matlab.unittest.TestSuite; +import matlab.unittest.plugins.TestReportPlugin; +import matlab.unittest.plugins.XMLPlugin +import matlab.unittest.plugins.DiagnosticsValidationPlugin + +suite = testsuite({'NonHWTest'}); +xmlFile = 'BSPDemoTests.xml'; + + +try + runner = matlab.unittest.TestRunner.withTextOutput('OutputDetail',1); + runner.addPlugin(DiagnosticsValidationPlugin) + + xmlFile = 'BSPDemoTests.xml'; + plugin = XMLPlugin.producingJUnitFormat(xmlFile); + runner.addPlugin(plugin); + + results = runner.run(suite); + + t = table(results); + disp(t); + disp(repmat('#',1,80)); + for test = results + if test.Failed + disp(test.Name); + end + end +catch e + disp(getReport(e,'extended')); + bdclose('all'); + exit(1); +end + +save(['BSPInstallerTest_',datestr(now,'dd_mm_yyyy-HH:MM:SS'),'.mat'],'t'); +bdclose('all'); +exit(any([results.Failed])); diff --git a/test/runSynthTests.m b/test/runSynthTests.m new file mode 100644 index 0000000..e0fdc67 --- /dev/null +++ b/test/runSynthTests.m @@ -0,0 +1,68 @@ +function runSynthTests(board) + +import matlab.unittest.TestRunner; +import matlab.unittest.TestSuite; +import matlab.unittest.plugins.TestReportPlugin; +import matlab.unittest.plugins.XMLPlugin +import matlab.unittest.plugins.ToUniqueFile; +import matlab.unittest.plugins.TAPPlugin; +import matlab.unittest.plugins.DiagnosticsValidationPlugin +import matlab.unittest.parameters.Parameter + +runParallel = false; +SynthesizeDesign = {true}; +param = Parameter.fromData('SynthesizeDesign',SynthesizeDesign); + +if nargin == 0 + suite = testsuite({'BSPTests'}); +else + boards = ['*',lower(board),'*']; + suite = TestSuite.fromClass(?BSPTests,'ExternalParameters',param); + suite = suite.selectIf('ParameterProperty','configs', 'ParameterName',boards); +end + +try + + runner = matlab.unittest.TestRunner.withTextOutput('OutputDetail',4); + runner.addPlugin(DiagnosticsValidationPlugin) + + xmlFile = 'BSPTestResults.xml'; + plugin = XMLPlugin.producingJUnitFormat(xmlFile); + runner.addPlugin(plugin); + + if runParallel + try %#ok + parpool(4); + results = runInParallel(runner,suite); + catch ME + disp(ME); + results = runner.run(suite); + end + else + results = runner.run(suite); + end + + t = table(results); + disp(t); + disp(repmat('#',1,80)); + for test = results + if test.Failed + disp(test.Name); + end + end +catch e + disp(getReport(e,'extended')); + bdclose('all'); + exit(1); +end + +try + poolobj = gcp('nocreate'); + delete(poolobj); +catch ME + disp(ME) +end + +save(['BSPTest_',datestr(now,'dd_mm_yyyy-HH:MM:SS'),'.mat'],'t'); +bdclose('all'); +exit(any([results.Failed])); diff --git a/test/setportmapping.m b/test/setportmapping.m new file mode 100644 index 0000000..295644e --- /dev/null +++ b/test/setportmapping.m @@ -0,0 +1,106 @@ +function mdl = setportmapping(mode,ReferenceDesignName,board_name) + +% ! this script will work with test models that have 16 data ports and 4 +% boolean ports + +if contains(lower(ReferenceDesignName),'cn0585') + dev = 'CN0585'; + mdl = 'testModel'; + portWidthRX = 16; + portWidthTX = 16; +else + error('Unknown device'); +end + +load_system(mdl); + +% First set all ports to NIS +for k=1:16 + hdlset_param([mdl,'/HDL_DUT/in',num2str(k)], 'IOInterface', 'No Interface Specified'); + hdlset_param([mdl,'/HDL_DUT/in',num2str(k)], 'IOInterfaceMapping', ''); + hdlset_param([mdl,'/HDL_DUT/out',num2str(k)], 'IOInterface', 'No Interface Specified'); + hdlset_param([mdl,'/HDL_DUT/out',num2str(k)], 'IOInterfaceMapping', ''); +end + +for k = 1:4 + hdlset_param([mdl,'/HDL_DUT/validIn',num2str(k)], 'IOInterface', 'No Interface Specified'); + hdlset_param([mdl,'/HDL_DUT/validIn',num2str(k)], 'IOInterfaceMapping', ''); + hdlset_param([mdl,'/HDL_DUT/validOut',num2str(k)], 'IOInterface', 'No Interface Specified'); + hdlset_param([mdl,'/HDL_DUT/validOut',num2str(k)], 'IOInterfaceMapping', ''); +end + + +filePath = '../CI/ports.json'; +str = fileread(filePath); +val = jsondecode(str); + +fn = fieldnames(val); + +for k = 1:numel(fn) + x = val.(fn{k}); + if (strcmp(x.chip, dev)) + inIndex = 1; + outIndex = 1; + validInIndex = 1; + validOutIndex = 1; + if (mode == "rx") || (mode == "rxtx") + rx = x.ports.rx; + for indexRx = 1:numel(rx) + element = rx(indexRx); + if(element.type == "data") + if(element.input == "true") + hdlset_param([mdl,'/HDL_DUT/in',num2str(inIndex)], 'IOInterface', [element.m_name,' [0:',num2str(portWidthRX-1),']']); + hdlset_param([mdl,'/HDL_DUT/in',num2str(inIndex)], 'IOInterfaceMapping', ['[0:',num2str(portWidthRX-1),']']); + inIndex = inIndex + 1; + else + hdlset_param([mdl,'/HDL_DUT/out',num2str(outIndex)], 'IOInterface', [element.m_name,' [0:',num2str(portWidthRX-1),']']); + hdlset_param([mdl,'/HDL_DUT/out',num2str(outIndex)], 'IOInterfaceMapping', ['[0:',num2str(portWidthRX-1),']']); + outIndex = outIndex + 1; + end + elseif (element.type == "valid") + if(element.input == "true") + hdlset_param([mdl,'/HDL_DUT/validIn',num2str(validInIndex)], 'IOInterface', element.m_name); + hdlset_param([mdl,'/HDL_DUT/validIn',num2str(validInIndex)], 'IOInterfaceMapping', '[0]'); + validInIndex = validInIndex + 1; + else + hdlset_param([mdl,'/HDL_DUT/validOut',num2str(validOutIndex)], 'IOInterface', element.m_name); + hdlset_param([mdl,'/HDL_DUT/validOut',num2str(validOutIndex)], 'IOInterfaceMapping', '[0]'); + validOutIndex = validOutIndex + 1; + end + end + end + + end + if (mode == "tx") || (mode == "rxtx") + tx = x.ports.tx; + if (mode == "tx") + inIndex = 1; + outIndex = 1; + end + for indexTx = 1:numel(tx) + element = tx(indexTx); + if(element.type == "data") + if(element.input == "true") + hdlset_param([mdl,'/HDL_DUT/in',num2str(inIndex)], 'IOInterface', [element.m_name,' [0:',num2str(portWidthTX-1),']']); + hdlset_param([mdl,'/HDL_DUT/in',num2str(inIndex)], 'IOInterfaceMapping', ['[0:',num2str(portWidthTX-1),']']); + inIndex = inIndex + 1; + else + hdlset_param([mdl,'/HDL_DUT/out',num2str(outIndex)], 'IOInterface', [element.m_name,' [0:',num2str(portWidthTX-1),']']); + hdlset_param([mdl,'/HDL_DUT/out',num2str(outIndex)], 'IOInterfaceMapping', ['[0:',num2str(portWidthTX-1),']']); + outIndex = outIndex + 1; + end + elseif (element.type == "valid") + if(element.input == "true") + hdlset_param([mdl,'/HDL_DUT/validIn',num2str(validInIndex)], 'IOInterface', element.m_name); + hdlset_param([mdl,'/HDL_DUT/validIn',num2str(validInIndex)], 'IOInterfaceMapping', '[0]'); + validInIndex = validInIndex + 1; + else + hdlset_param([mdl,'/HDL_DUT/validOut',num2str(validOutIndex)], 'IOInterface', element.m_name); + hdlset_param([mdl,'/HDL_DUT/validOut',num2str(validOutIndex)], 'IOInterfaceMapping', '[0]'); + validOutIndex = validOutIndex + 1; + end + end + end + end + end +end diff --git a/test/testModel.slx b/test/testModel.slx new file mode 100755 index 0000000000000000000000000000000000000000..7021254b239104aff571777a8837041fd577d024 GIT binary patch literal 58678 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 literal 0 HcmV?d00001