classdef (StrictDefaults)RAM < matlab.System
    % hdl.RAM Use RAM to read from and write to memory locations.
    %
    %   H = hdl.RAM creates a single port RAM System object. This object
    %   allows you to read from or write to a memory location in the
    %   RAM. The output data port corresponds to the read/write address
    %   passed in.
    %
    %   H = hdl.RAM('RAMType', 'Single port', 'WriteOutputValue',
    %   'Old data', 'RAMInitialValue', [0:31]) creates a single port RAM
    %   System object. This object allows you to read from or write to a
    %   memory location in the RAM. The RAM is initialized with the data [0:31].
    %   The output data port corresponds to the read/write address passed
    %   in. During a write operation, the old data at the write address is
    %   sent out as the output.
    %
    %   H = hdl.RAM('RAMType', 'Simple dual port') creates a simple
    %   dual port RAM System object. This object allows you to read from
    %   and write to different memory locations in the RAM. The output data
    %   port corresponds to the read address. If a read operation is
    %   performed at the same address as the write operation, old data at
    %   that address is read out as the output.
    %
    %   H = hdl.RAM('RAMType', 'Dual port', 'WriteOutputValue', 'New data')
    %   creates a dual port RAM System object. This object
    %   allows you to read from and write to different memory locations in
    %   the RAM. There are two output ports, a write output data port and a
    %   read output data port. The write output data port sends out the new
    %   data at the write address. The read output data port sends out the
    %   old data at the read address.
    %
    %   The type of input data to the step method determines RAM module
    %   instantiation:
    %       'scalar' - single RAM module instantiation
    %       'vector' - RAM banks instantiation. The number of RAM banks
    %       is inferred from the number of elements in the input vector.
    %       Either all inputs to the step method must have consistent
    %       sizes, or the first input (write data) must be a vector while all other
    %       inputs are scalars.
    %   The input can also be complex.
    %
    %   The size of the RAM is determined based on the size of the address.
    %   The size of each RAM bank, in the case of vector input, is
    %   determined based on the size of the address element in the address
    %   input vector. If the data type of the address is single or double,
    %   the size of the RAM is set to 2^16 locations; RAM size in general
    %   defaults to 16 bits, which covers most use cases. RAM size can
    %   significantly affect your simulations, and if you don't need 32
    %   bits, always specify the address bus by the correct fi-type, up to
    %   32 bits.
    %
    %   The output data is delayed by one step.
    %
    %   Step method syntax:
    %
    %   For a Single port RAM:
    %       DATAOUT = step(H, WRITEDATA, ...
    %                         READWRITEADDRESS, ...
    %                         WRITEENABLE)
    %   allows you to read the value in memory location READWRITEADDRESS
    %   when WRITEENABLE is false, or write the value WRITEDATA into
    %   the memory location READWRITEADDRESS when WRITEENABLE is
    %   true. DATAOUT is the new or old data at the READWRITEADDRESS
    %   when WRITEENABLE is true, or the data at READWRITEADDRESS
    %   when WRITEENABLE is false.
    %
    %   For a Simple dual port RAM:
    %       READDATAOUT = step(H, WRITEDATA,
    %                             WRITEADDRESS, ...
    %                             WRITEENABLE, ...
    %                             READADDRESS)
    %   allows you to write the value WRITEDATA into memory location
    %   WRITEADDRESS when WRITEENABLE is true. READDATAOUT is the
    %   old data at the address location READADDRESS.
    %
    %   For a Dual port RAM:
    %       [WRITEDATAOUT, READDATAOUT] = step(H, WRITEDATA, ...
    %                                             WRITEADDRESS, ...
    %                                             WRITEENABLE, ...
    %                                             READADDRESS)
    %   allows you to write the value WRITEDATA into the memory location
    %   WRITEADDRESS when WRITEENABLE is true. WRITEDATAOUT is the
    %   new or old data at memory location WRITEADDRESS. READDATAOUT
    %   is the old data at the address location READADDRESS.
    %
    %   System objects may be called directly like a function instead of
    %   using the step method. For example, y = step(obj, x) and y = obj(x)
    %   are equivalent.
    %
    %
    %   hdl.RAM Input Requirements:
    %
    %   WRITEDATA must be scalar, can be double, single, integer, or
    %   a fixed-point (fi) object, and can be real or complex.
    %
    %   WRITEENABLE must be a scalar value.
    %
    %   WRITEADDRESS and READADDRESS must be scalar, real and unsigned, and
    %   can be double, single, integer, or a fixed-point (fi) object.
    %
    %
    %   hdl.RAM methods:
    %
    %   step     - Read from or write input value to a memory location (see
    %              above)
    %   release  - Allow property value and input characteristics
    %              changes
    %   clone    - Create hdl.RAM object with same property values
    %   isLocked - Locked status (logical)
    %
    %
    %   hdl.RAM properties:
    %
    %   RAMType          - Specify the type of RAM to be created
    %                      'Single port'       (default)
    %                                          creates a single port RAM,
    %                                          with 3 inputs, Write Data,
    %                                          Write address, Write enable
    %                                          and 1 output, Read/Write
    %                                          Data
    %                      'Simple dual port'  creates a simple dual port
    %                                          RAM, with 4 inputs,
    %                                          Write Data,
    %                                          Write address,
    %                                          Write enable, Read address,
    %                                          and 1 output, Read Data
    %                      'Dual port'         creates a dual port RAM,
    %                                          with 4 inputs, Write Data,
    %                                          Read/Write address,
    %                                          Write enable, Read address,
    %                                          and 2 outputs, Write Data,
    %                                          Read Data
    %                      'True dual port'    creates a true dual port RAM,
    %                                          with 6 inputs, Write Data a,
    %                                          Read/Write address a,
    %                                          Write enable a, Write data b,
    %                                          Read/Write address b,
    %                                          write enable b, and 2
    %                                          outputs, Write Data a
    %                                          Write Data b
    %                      'Simple tri port'   creates a simple tri port
    %                                          RAM, with 5 inputs,
    %                                          Write Data,
    %                                          Write address,
    %                                          Write enable, Read address a,
    %                                          Read address b,
    %                                          and 2 outputs, Read Data a,
    %                                          Read Data b
    %   WriteOutputValue - Specify the behavior for the Write output port
    %                      for the Single port and Dual port RAMs
    %                      'New data' (default)  sends out new data at the
    %                                            address to the output
    %                      'Old data'            sends out old data at the
    %                                            address to the output
    %   RAMInitialValue  - Supply initial values for the RAM
    %                      0 (default) initializes every location to 0
    %                      A scalar initial value will initialize each
    %                      RAM location to the value. A one dimensional
    %                      array of values with the same number of
    %                      elements as the RAM will initialize the RAM
    %                      with the contents of the array.
    %
    %   % Example:
    %   % Write to a Single port RAM and read the newly written value out
    %   % Output data is delayed one step with respect to input data
    %   hRAM = hdl.RAM('RAMType', 'Single port', ...
    %                 'WriteOutputValue', 'New data');
    %
    %   % Pre-allocate memory
    %   dataLength    = 100;
    %   [dataIn, dataOut] = deal(zeros(1,dataLength));
    %   for ii = 1:dataLength
    %     dataIn(ii)  = randi([0 63],1,1,'uint8');
    %     addressIn   = uint8(ii-1);
    %     writeEnable = true;
    %     dataOut(ii) = hRAM(dataIn(ii), addressIn, writeEnable);
    %   end
    %

    %   Copyright 2011-2023 The MathWorks, Inc.

    %#codegen
    %#ok<*EMCLS>

    properties (Nontunable)
        % RAMType Specify the type of RAM
        %   Specify the RAM type to be one of 'Single port' |
        %   'Simple dual port' | 'Dual port' | 'True dual port' |
        %   'Simple tri port'
        %   The default is 'Single port'
        RAMType = 'Single port';
        % AsyncRead Enable asynchronous reads
        AsyncRead (1,1) logical = false;
        % WriteOutputValue Specify the output data for a write operation
        %   Specify WriteOutputValue to be one of 'New data' | 'Old data'
        %   When the WriteOutputValue is set to 'New data', during a write
        %   operation, the new data sent in to be written appears at the
        %   write output port.
        %   When the WriteOutputValue is set to 'Old data', during a write
        %   operation, the old data at the write address appears at the
        %   write output port.
        %   This property applies when you set the RAMType to 'Single port'
        %   or 'Dual port'.
        %   The default is 'New data'.
        WriteOutputValue = 'New data';
        % RAMInitialValue Specify the RAM initial value
        RAMInitialValue = 0;
        % Allow Bus Inputs
        allowBusInputs (1,1) logical = false;
        WritesHaveOutput (1,1) logical = true;
        NumReads = 0;
        NumWrites {mustBePositive} = 1;
    end

    % Enumerations provide faster matching than corresponding string properties
    % and we update them during setupImpl()
    properties (Nontunable,Hidden)
        % make these internal values index into the corresponding public
        % properties
        EnumRAMType = 1;
        EnumWriteOutputValue = 1;
        NumBanks = 0; % intentionally invalid

        % Flag to unionize read, write addresses if both are fixed-point but
        % have different numerictype. NOTE: this is applicable only for 'Simple
        % dual port' and 'Dual port' RAMTypes only, and it never changes from 1.
        UnionizeAddresses = 1;

        RamSize = 1;
        RamBankSize = 1;
        NumAddressBits = 1;
        RAMDirective = '';
        SynthesisAttributes = {};

    end

    properties (Constant, Hidden)
        % RAMType property
        RAMTypeSet = matlab.system.StringSet({...
            'Single port',...
            'Simple dual port',...
            'Dual port', ...
            'True dual port', ...
            'Simple tri port'});

        % WriteOutputValue property
        WriteOutputValueSet = matlab.system.StringSet({...
            'New data',...
            'Old data'});
    end

    properties (Access=private)
        % Remove discrete state property in lieu of simulation speedup
        % requests.

        % pRAM RAM memory.
        % Stores data that is sent in to be written
        pRAM

        % pOutWriteData Delayed output write data.
        % Stores output for write output port in the next step. This
        % simulates the one clock delay in RAM data access.
        pOutWriteData

        % pOutReadData Delayed output read data.
        % Stores output for read output port in the next step. This
        % simulates the one clock delay in RAM data access.
        pOutReadData
    end



    methods
        function obj = RAM(varargin)

        coder.internal.allowHalfInputs;
        %if nargin > 0
        %        [varargin{:}] = convertStringsToChars(varargin{:});
        %end

            % RAMType and WriteOutputValue can also be specified without
            % the pv-pair interface
            setProperties(obj, nargin, varargin{:}, ...
                'RAMType', 'WriteOutputValue', 'RAMInitialValue', ...
                'AsyncRead', 'allowBusInputs');
        end % hdl.RAM


        function set.RAMType(obj, val)
            obj.RAMType = val;

            if strcmpi(val,'Single port')
                obj.EnumRAMType = 1; %#ok<MCSUP>
            elseif strcmpi(val,'Simple dual port')
                obj.EnumRAMType = 2; %#ok<MCSUP>
            elseif strcmpi(val, 'Dual port')
                obj.EnumRAMType = 3; %#ok<MCSUP>
            elseif strcmpi(val, 'True dual port')
                obj.EnumRAMType = 4; %#ok<MCSUP>
            elseif strcmpi(val, 'Simple Tri port')
                obj.EnumRAMType = 5; %#ok<MCSUP>
            elseif strcmpi(val, 'Simple Quad port')
                obj.EnumRAMType = 6; %#ok<MCSUP>
            end % switch-case (RAMType)
        end


        function writes = get.NumWrites(obj)
           switch obj.EnumRAMType
               case {4, 6}
                   writes = 2;
               otherwise
                   writes = 1;
           end
        end

        function reads = get.NumReads(obj)
           switch obj.EnumRAMType
               case {1, 4}
                   reads = 0;
               case {5, 6}
                   reads = 2;
               otherwise
                   reads = 1;
           end
        end

        function output = get.WritesHaveOutput(obj)
            switch(obj.EnumRAMType)
                case {2, 5, 6}
                    output = false;
                otherwise
                    output = true;
            end
        end

        function set.WriteOutputValue(obj, val)
            obj.WriteOutputValue = val;
            if strcmpi(val, 'New data')
                obj.EnumWriteOutputValue = 1; %#ok<MCSUP>
            else
                obj.EnumWriteOutputValue = 2; %#ok<MCSUP>
            end
        end

    end


    methods(Access = protected) %Impls
        function num = getNumInputsImpl(obj)
            num = obj.NumReads + obj.NumWrites * 3;
        end % getNumInputsImpl

        function num = getNumOutputsImpl(obj)
            num = obj.NumReads;
            if(obj.WritesHaveOutput)
                num = num + obj.NumWrites;
            end
        end % getNumOutputsImpl

        function icon = getIconImpl(obj)
            switch(obj.EnumRAMType)
                case 1
                    icon = sprintf('Single Port\nRAM');
                case 2
                    icon = sprintf('Simple\nDual Port\nRAM');
                case 3
                    icon = sprintf('Dual Port\nRAM');
                case 4
                    icon = sprintf('True\nDual Port\nRAM');
                case 5
                    icon = sprintf('Simple\nTri Port\nRAM');
                case 6
                    icon = sprintf('Simple\nQuad Port\nRAM');
            end % switch-case (EnumRAMType)
        end

        function varargout = getInputNamesImpl(obj)
            varargout = cell(1, getNumInputs(obj));
            portNum = 1;
            for i = 1:obj.NumWrites
                varargout{portNum} = hdl.RAM.portName('din',i, obj.NumWrites>1);
                varargout{portNum+2} = hdl.RAM.portName('we', i, obj.NumWrites>1);
                if(obj.NumReads > 0)
                    varargout{portNum+1} = hdl.RAM.portName('wr_addr', i, obj.NumWrites>1);
                else
                    varargout{portNum+1} = hdl.RAM.portName('addr', i, obj.NumWrites>1);
                end
                portNum = portNum + 3;
            end
            for i = 1:obj.NumReads
                if(obj.NumWrites > 0)
                    addrName = 'rd_addr';
                else
                    addrName = 'addr';
                end
                varargout{portNum} = hdl.RAM.portName(addrName, i, obj.NumReads>1);
                portNum = portNum + 1;
            end
        end % getInputNamesImpl

        function varargout = getOutputNamesImpl(obj)
            varargout = cell(1, getNumOutputs(obj));
            if(obj.EnumRAMType == 1)
                varargout{1} = 'dout';
            else
                portNum=1;
                if(obj.WritesHaveOutput)
                    if obj.NumReads > 0
                        outname = 'wr_dout';
                    else
                        outname = 'dout';
                    end
                    for i=1:obj.NumWrites
                        varargout{portNum} = hdl.RAM.portName(outname, i, obj.NumWrites>1);
                        portNum = portNum + 1;
                    end
                end

                if obj.WritesHaveOutput && obj.NumWrites > 0
                    outname = 'rd_dout';
                else
                    outname = 'dout';
                end
                for i=1:obj.NumReads
                    varargout{portNum} = hdl.RAM.portName(outname, i, obj.NumReads>1);
                    portNum = portNum + 1;
                end
            end
        end % getOutputNamesImpl

        function flag = isInactivePropertyImpl(obj, prop)
            % isInactivePropertyImpl
            % Output is true if the property passed in is inactive
            % Inactive cases:
            %   * AsyncRead when True Dual Port and Simple Quad Port RAM are
            %     selected.
            %   * WriteOutputValue when there is no write output or when
            %     AsyncRead is true
            flag = (obj.EnumRAMType == 4 || obj.EnumRAMType == 6) && strcmp(prop, 'AsyncRead');
            flag = flag || (obj.EnumRAMType == 2 || obj.EnumRAMType == 5 ...
                           || obj.EnumRAMType == 6 || obj.AsyncRead) ...
                        && strcmp(prop, 'WriteOutputValue');
            flag = flag || strcmpi(prop, 'WritesHaveOutput') || strcmpi(prop, 'NumWrites') ...
                || strcmpi(prop, 'NumReads') ||  strcmpi(prop, 'allowBusInputs');
        end % isInactivePropertyImpl

        function validateInputsImpl(obj, varargin)

            wrData1 = varargin{1};
            wrAddr1 = varargin{2};
            for i=1:obj.NumWrites
                wr_data = varargin{(i-1)*3+1};
                wraddr = varargin{(i-1)*3+2};
                wr_en = varargin{(i-1)*3+3};
                hdl.RAM.validateRAMaddresses(wrAddr1, wraddr);
                coder.internal.assert(hdl.RAM.typeCompare(wr_data, wrData1), ...
                    'hdlmllib:hdlmllib:RAMWriteDataSameType', ...
                    'IfNotConst', 'Fail');
                hdl.RAM.validateRAMWriteData(wr_data);
                hdl.RAM.validateRAMWriteEnable(wr_en, (i-1)*3+3);
                if(obj.WritesHaveOutput)
                    hdl.RAM.validateRAMAddress(wraddr, 'RAM read/write address', 2);
                else
                    hdl.RAM.validateRAMAddress(wraddr, 'RAM write address', 2);
                end
                hdl.RAM.validateWriteBanks(wrData1, wrAddr1, wr_data, wraddr, wr_en)
            end
            for i=1:obj.NumReads
                rdaddr = varargin{obj.NumWrites*3+i};
                hdl.RAM.validateRAMaddresses(wrAddr1, rdaddr);
                hdl.RAM.validateRAMAddress(rdaddr, 'RAM read address', 4);
                hdl.RAM.validateReadBanks(wrData1, wrAddr1, rdaddr)
            end
       end % validateInputsImpl

       function setupImpl(obj, varargin)
            % setupImpl
            % initialize RAM and delayed read/write data (with 0s or the ramIV).
            % The method inputs are the RAM object, followed by its inputs.
            % Simple Dual and Dual port RAMs have a 2nd address port, which is
            % provided in varargin{1}. The unused parameter in the argument list
            % is the write_enable signal.

            % if ambiguous/double types are passed in, provide a default
            % value and return.
            sizeCheckOnly = hdl.RAM.getSizeCheckStatus;
            if sizeCheckOnly
                obj.pOutWriteData = zeros([obj.NumWrites size(varargin{1})]);
                obj.pOutReadData = zeros([obj.NumReads size(varargin{1})]);
                obj.pRAM = zeros(size(varargin{1}));
            else % valid data types and sizes are being passed in
                % get the number of locations in the RAM
                switch(obj.EnumRAMType)
                    case {1, 4} % Single port RAM
                        obj.setRAMSize(varargin{2}, varargin{1});
                    otherwise % Simple dual port and Dual port RAMs
                        % Ensure that the multiple address ports have the
                        % identical type.

                        firstRead = obj.NumWrites * 3 + 1;
                        if isfi(varargin{2}) && isfi(varargin{firstRead}) && obj.UnionizeAddresses
                            unionType = obj.computeAddrUnion(varargin);
                            obj.setRAMSize(fi(zeros(size(varargin{2})), unionType), varargin{1});
                        else
                            obj.setRAMSize(varargin{2}, varargin{1});
                        end
                end

                data_in = varargin{1};
                % Create a zero value of the correct type for a single element
                % of the RAM storage. This will be used to define the type of
                % the RAM storage.
                if ~isstruct(data_in)
                    resetDataValue = hdl.RAM.getResetData(data_in);
                else
                    resetDataValue = data_in(1);
                    fn = fieldnames(data_in(1));
                    for i = 1:numel(fn)
                        resetDataValue.(fn{i}) = hdl.RAM.getResetData(data_in(1).(fn{i}));
                    end
                end
                icV = obj.RAMInitialValue;

                if isstruct(data_in)
                    coder.internal.errorIf(any(icV(:)), ...
                    'hdlmllib:hdlmllib:RAMIVMustBeZeroForBusInput', class(data_in));
                end
                % Check for complexity; if complex input, initial value should
                % be complex too.
                if ~isreal(data_in) && ~isstruct(data_in)
                    resetVal = complex(resetDataValue);
                    icVal = complex(icV);
                else
                    resetVal = resetDataValue;
                    icVal = icV;
                end

                % Error checking on icVal
                if ~isempty(icVal)
                    coder.internal.errorIf(~isnumeric(icVal), ...
                        'hdlmllib:hdlmllib:RAMIVMustBeNumeric')
                    sz = size(icVal);
                    coder.internal.errorIf((ndims(icVal) > 2 ||...
                        (sz(1) > 1 && sz(2) > 1)),...
                        'hdlmllib:hdlmllib:RAMIVMatrixUnsupported') %#ok<ISMAT>
                end

                % For each of the 3 output signals, initialize them with one
                % value of the correct data type and complexity, expanded to the
                % appropriate vector size based off the input data's size/shape.
                if isstruct(data_in)
                    fn = fieldnames(data_in(1));
                    if isscalar(icVal)
                        for i = 1:numel(fn)
                            if isfloat(data_in(1).(fn{i})) || isinteger(data_in(1).(fn{i}))
                                resetVal.(fn{i}) = cast(repmat(icVal,size(data_in(1).(fn{i}))), 'like', data_in(1).(fn{i}));
                            else
                                if islogical(data_in(1).(fn{i}))
                                    resetVal.(fn{i}) = boolean(repmat(boolean(icVal),size(data_in(1).(fn{i}))));
                                else
                                    dataNumerictype = get(data_in(1).(fn{i}), 'numerictype');
                                    if isfimathlocal(data_in(1).(fn{i}))
                                        dataFiMath = get(data_in(1).(fn{i}), 'fimath');
                                        resetVal.(fn{i}) = fi(repmat(icVal,size(data_in(1).(fn{i}))), 'numerictype', dataNumerictype, ...
                                            'fimath', dataFiMath);
                                    else
                                        resetVal.(fn{i}) = fi(repmat(icVal,size(data_in(1).(fn{i}))), 'numerictype', dataNumerictype);
                                    end
                                end
                            end
                        end
                    end
                    obj.pOutWriteData = repmat(resetVal, [obj.NumWrites, numel(data_in)]);
                    obj.pOutReadData = repmat(resetVal, [obj.NumReads, numel(data_in)]);
                    obj.pRAM = deal(repmat(resetVal, [obj.RamBankSize, numel(data_in)]));
                else
                    if isempty(icVal)
                        if(obj.WritesHaveOutput)
                            obj.pOutWriteData = repmat(resetVal, [obj.NumWrites size(data_in)]);
                        end
                        obj.pOutReadData =  repmat(resetVal, [obj.NumReads size(data_in)]);
                        obj.pRAM = repmat(resetVal, obj.RamSize, 1);
                    elseif isscalar(icVal)
                        % If there's a IV, use it for the initial value. Use the
                        % same type as already determined for the resetVal above.
                        resetVal = cast(icVal, 'like', resetVal);
                        % If the IV is empty, stick with the default 0.
                        obj.pOutWriteData =repmat(resetVal, [ obj.NumWrites size(data_in)]);
                        obj.pOutReadData  =repmat(resetVal, [ obj.NumReads size(data_in)]);
                        % Assign the RAM matrix with the one value of the correct data
                        % type and complexity, expanded to the total number of RAM
                        % locations, including any multiple banks inferred from the
                        % input data size.
                        obj.pRAM = deal(repmat(resetVal, obj.RamSize, 1));
                    else
                        coder.internal.errorIf(numel(icVal) ~= obj.RamBankSize, ...
                            'hdlmllib:hdlmllib:UnsupportedIVSize',...
                            obj.NumBanks, obj.RamBankSize, numel(icVal));

                        resetVal = cast(icVal(1), 'like', resetVal);
                        obj.pOutWriteData = repmat(resetVal, [obj.NumWrites size(data_in)]);
                        obj.pOutReadData = repmat(resetVal, [obj.NumReads size(data_in)] );

                        % Use the full user-specified IV
                        if isrow(icVal)
                            % Note the almost invisible transpose (.') on icVal
                            obj.pRAM = repmat(cast(icVal.', 'like', resetVal),...
                                obj.NumBanks, 1);
                        else
                            obj.pRAM = repmat(cast(icVal, 'like', resetVal),...
                                obj.NumBanks, 1);
                        end
                    end
                end
            end
        end % setupImpl

        function varargout = isInputDirectFeedthroughImpl(obj)
            argnum = 1;
            coder.unroll
            for(i = 1:obj.NumWrites()*3)
                % if writes have output and asyncRead is true, the address
                % is direct feedthrough
                varargout{argnum} = obj.AsyncRead && obj.WritesHaveOutput && mod(i,3) == 2;
                argnum = argnum + 1;
            end
            coder.unroll
            for(i = 1:obj.NumReads())
                varargout{argnum} = obj.AsyncRead;
                argnum = argnum + 1;
            end
        end

        function varargout = outputImpl(obj, varargin)

            sizeCheckOnly = hdl.RAM.getSizeCheckStatus;

            outsize = coder.const(size(varargin{1}));
            if sizeCheckOnly % size check mode, send dummy output
                for ii = 1:nargout
                    varargout{ii} = zeros(outsize);
                end
            else
                if(obj.WritesHaveOutput)

                    if(obj.AsyncRead)
                        datain1 = varargin{1};
                        wren1 = varargin{3};
                        if isstruct(datain1)
                            resetDataValue = datain1(1);
                            fn = fieldnames(datain1(1));
                            for i = 1:numel(fn)
                                resetDataValue.(fn{i}) = hdl.RAM.getResetData(datain1(1).(fn{i}));
                            end
                            out = repmat(resetDataValue, [obj.NumWrites, numel(datain1)]);
                            for i = 1:obj.NumWrites
                                addr = varargin{(i-1)*3+2};
                                addr = addr+1;
                                out(i, :) = obj.pRAM(addr, :);
                                varargout{i} = reshape(out(i, :), outsize);
                            end
                        else
                            out = zeros([obj.NumWrites size(datain1)], 'like', datain1);
                            if isscalar(wren1) && ~isscalar(datain1)
                               repeat = size(datain1);
                            else
                               repeat = [1 1];
                            end
                            for i = 1:obj.NumWrites
                                addr = repmat(varargin{(i-1)*3+2}, repeat);
                                addr = obj.getAddressInput(addr);
                                out(i, :) = obj.pRAM(addr, :);
                                varargout{i} = reshape(out(i, :), outsize);
                            end
                        end
                    else
                        for i = 1:obj.NumWrites
                            varargout{i} = reshape(obj.pOutWriteData(i, :), outsize);
                        end
                    end
                end
                if(coder.const(obj.AsyncRead))
                    if ~isstruct(varargin{1})
                        wren1 = varargin{3};
                        data_in = varargin{1};
                        outReadData = zeros([obj.NumReads size(data_in)], 'like', data_in);
                        if isscalar(wren1) && ~isscalar(data_in)
                           repeat = size(data_in);
                        else
                           repeat = [1 1];
                        end
                        portNum = obj.NumWrites*3+1;
                        for i = 1:obj.NumReads
                            addr = varargin{obj.NumWrites*3+i};
                            addr = repmat(addr, repeat);
                            addr = obj.getAddressInput(addr);
                            outReadData(i, :) = reshape(obj.pRAM(addr),size(data_in));
                            portNum = portNum + 1;
                        end
                    else
                        data_in = varargin{1};
                        outReadData = repmat(data_in, [obj.NumReads, numel(data_in)]);
                        portNum = obj.NumWrites*3+1;
                        for i = 1:obj.NumReads
                            addr = varargin{obj.NumWrites*3+i};
                            addr = addr+1;
                            outReadData(i, :) = obj.pRAM(addr,:);
                            portNum = portNum + 1;
                        end
                    end
                else
                    outReadData = obj.pOutReadData;
                end

                for i = 1:obj.NumReads
                    varargout{obj.NumWrites*obj.WritesHaveOutput+i} = reshape(outReadData(i, :), outsize);
                end

            end

        end

        % Gain speedup by avoiding to validate data types and sizes are being
        % passed in process inputs, send output(s) validation is carried for
        % once in the setupImpl()
        function updateImpl(obj, varargin )
            % stepImpl
            % process inputs, send output, update state
            % read FIRST
            % compute the address type
            datain1 = varargin{1};
            wren1 = varargin{3};

            if(~coder.target('hdl') && obj.EnumRAMType == 4)
                % This is will fail at run-time if the user simulates a
                % model that has the same address for a read and write
                % or a write and another write.
                writeAddressViolation = any((varargin{2}==varargin{5}) & (varargin{3} | varargin{6}));
                coder.internal.warningIf(writeAddressViolation, 'hdlmllib:hdlmllib:RAMTDPSameAddress');
            end
            if isscalar(wren1) && ~isscalar(datain1)
               repeat = size(datain1);
            else
               repeat = [1 1];
            end

            portNum = obj.NumWrites*3+1;
            if(~obj.AsyncRead)
                for i = 1:obj.NumReads
                    addr = varargin{obj.NumWrites*3+i};
                    if ~isstruct(datain1)
                        addr = repmat(addr, repeat);
                        addr = obj.getAddressInput(addr);
                        obj.pOutReadData(i, :) = reshape(obj.pRAM(addr),size(datain1));
                    else
                        addr = addr+1;
                        obj.pOutReadData(i,:) = obj.pRAM(addr,:);
                    end
                    portNum = portNum + 1;
                end
            end

            for i = 0:obj.NumWrites-1
                data_in = varargin{i*3+1};
                if ~isstruct(datain1)
                    addr = repmat(varargin{i*3+2}, repeat);
                    addr = obj.getAddressInput(addr);
                    writeEn = repmat(varargin{i*3+3}, repeat);
                else
                    addr = varargin{i*3+2};
                    addr = addr+1;
                    writeEn = varargin{i*3+3};
                end

                if isstruct(data_in)
                    if obj.EnumWriteOutputValue == 1
                        if writeEn
                            obj.pRAM(addr,:) = data_in;
                        end
                    else
                        if(obj.WritesHaveOutput)
                            obj.pOutWriteData(i+1,:) = obj.pRAM(addr,:);
                        end
                    end
                else
                    for n = 1:obj.NumBanks
                        if obj.EnumWriteOutputValue == 1
                            if writeEn(n)
                                obj.pRAM(addr(n)) = data_in(n);
                            end
                        else % Old data, read first
                            if(obj.WritesHaveOutput)
                                obj.pOutWriteData(i+1, n) = obj.pRAM(addr(n));
                            end
                        end
                    end
                end
            end

            for i = 0:obj.NumWrites-1
                data_in = varargin{i*3+1};
                if ~isstruct(datain1)
                    addr = repmat(varargin{i*3+2}, repeat);
                    addr = obj.getAddressInput(addr);
                    writeEn = repmat(varargin{i*3+3}, repeat);
                else
                    addr = varargin{i*3+2};
                    addr = addr+1;
                    writeEn = varargin{i*3+3};
                end

                if isstruct(data_in)
                    if obj.EnumWriteOutputValue == 1
                        if(obj.WritesHaveOutput && ~obj.AsyncRead)
                            obj.pOutWriteData(i+1,:) = obj.pRAM(addr,:);
                        end
                    else
                        if writeEn
                            obj.pRAM(addr,:) = data_in;
                        end
                    end
                else
                    for n = 1:obj.NumBanks
                        if obj.EnumWriteOutputValue == 1
                            if(obj.WritesHaveOutput && ~obj.AsyncRead)
                                obj.pOutWriteData(i+1, n) = obj.pRAM(addr(n));
                            end
                        else % Old data, read first
                            if writeEn(n)
                                obj.pRAM(addr(n)) = data_in(n);
                            end
                        end
                    end
                end
            end
        end % stepImpl

        function resetImpl(obj) %#ok<MANU>
            % Hardware RAMs are not resettable; do nothing on reset.
        end

        function modes = getExecutionSemanticsImpl(obj) %#ok<MANU>
            % supported semantics
            modes = {'Classic', 'Synchronous'};
        end % getExecutionSemanticsImpl

        function s = saveObjectImpl(obj)
            % saveObjectImpl
            % save states & properties into output structure
            % Save the public properties
            s = saveObjectImpl@matlab.System(obj);
            if obj.isLocked
                s.pRAM = obj.pRAM;
                s.pOutWriteData = obj.pOutWriteData;
                s.pOutReadData = obj.pOutReadData;
            end
        end % saveObjectImpl

        function loadObjectImpl(obj, s, ~)
            % loadObjectImpl
            % load states & properties from input structure
            fn = fieldnames(s);
            obj.setObjPropertiesFromStructure(s, fn);
        end

        function setObjPropertiesFromStructure(obj, s, fn)
            % setObjPropertiesFromStructure
            % for the fieldnames passed in (fn), copy over the settings
            % from s to the object
            for ii = 1:numel(fn)
                obj.(fn{ii}) = s.(fn{ii});
            end
        end % loadObjectImpl

        function supported = supportsMultipleInstanceImpl(~)
            % Support in For Each Subsystem
            supported = true;
        end

        function flag = isInputSizeMutableImpl(~,~)
            % input arg may change size after locked
            flag = true;
        end

        function flag = isInputComplexityMutableImpl(~,~)
            % input arg may change complexity after locked
            flag = true;
        end

        function varargout = isOutputComplexImpl(obj)
            for i=1:(obj.NumWrites + obj.NumReads)
                varargout{i} = propagatedInputComplexity(obj,1);
            end
        end

        function varargout = getOutputSizeImpl(obj)
             for i=1:(obj.NumWrites + obj.NumReads)
                varargout{i} = propagatedInputSize(obj, 1);
             end
        end

        function varargout = isOutputFixedSizeImpl(obj)
            for i=1:(obj.NumWrites + obj.NumReads)
                varargout{i} = propagatedInputFixedSize(obj,1);
            end
        end

        function varargout = getOutputDataTypeImpl(obj)
            % Return data type for each output port
            busType = obj.propagatedInputBusStructure(1);
            if ~isempty(busType) && (busType.isBus || busType.isPartial)
                % propagate output port as bus
                for i=1:getNumOutputsImpl(obj)
                    varargout{i} = busType;
                end
            else
                % if true, this is non-bus signal or undetermined
                % propagate output port as non-bus or undetermined
                inputType = obj.propagatedInputDataType(1);
                for i=1:getNumOutputsImpl(obj)
                    if isempty(inputType)
                        varargout{i} = Simulink.SignalDescriptor; % undetermined
                    else
                        varargout{i} = inputType; % determined non-bus
                    end
                end
            end
        end
    end % protected methods (Impls)



    methods(Access = protected) %non-Impls
        function address = getAddressInput(obj, addressIn)
            % Banked RAM is stored as a single RAM, with each bank sequentially
            % ordered. For each RAM bank, add offset corresponding to its number
            % to get the absolute address within RAM array. Send it as is, if fi
            % or integer, else if double/single, convert to uint16 and send out.

            out = fi(zeros(size(addressIn)),numerictype(0,obj.NumAddressBits,0));
            for bank = 1:length(addressIn)
                relAddress = double(addressIn(bank)) + obj.RamBankSize*(bank-1);
                out(bank) = fi(relAddress, 0, obj.NumAddressBits, 0);
            end
            address = double(out) + 1;
        end % getAddressInput
        function type = computeAddrUnion(obj, argin)
            sizeCheckOnly = hdl.RAM.getSizeCheckStatus;
            maxBits = 0;
            if(~sizeCheckOnly)
                for(i = 1:obj.NumWrites)
                    addr = argin{(i-1)*3+2};
                    maxBits = max(maxBits,hdl.RAM.getNumAddressBits(addr));
                end
                for(i = 1:obj.NumReads)
                    addr = argin{obj.NumWrites*3+i};
                    maxBits = max(maxBits,hdl.RAM.getNumAddressBits(addr));
                end
                type = numerictype(0, maxBits, 0);
            else
                type = numerictype(0, hdl.RAM.getNumAddressBits(argin{2}), 0);
            end
        end
        function setRAMSize(obj, address, data)
            % Get RAM size (all banks)
            % Call in setupImpl
            if isstruct(data)
                % Number of struct elements denotes the RAM bank size
                obj.NumBanks = numel(fieldnames(data));
            else
                obj.NumBanks = length(data);
            end

            obj.RamBankSize = hdl.RAM.calculateRAMBankSize(address(1));

            % Total number of addresses in the RAM
            obj.RamSize = obj.NumBanks*obj.RamBankSize;
            % Total number of address bits needed to represent all addresses.
            obj.NumAddressBits = ceil(log2(obj.RamSize));

            data1 = data(1);
            if isfi(data1)
                dataBytes = data1.WordLength;
            elseif isstruct(data1)
                dataBytes = hdl.RAM.getStructDataBytes(data1);
            elseif isa(data1, 'single') || isa(data1, 'int32') || isa(data1, 'uint32')
                dataBytes = 4;
            elseif isa(data1, 'double') || isa(data1, 'int64') || isa(data1, 'uint64')
                dataBytes = 8;
            elseif isa(data1, 'int16') || isa(data1, 'uint16') || isa(data1, 'half')
                dataBytes = 2;
            elseif isa(data1, 'int8') || isa(data1, 'uint8') || islogical(data1)
                dataBytes = 1;
            else
                isSupported = false;
                coder.internal.assert(isSupported, 'hdlmllib:hdlmllib:UnsupportedDataTypeForHDLRam', class(data1), ...
                        'IfNotConst', 'Fail');
            end
            bitsForData = log2(dataBytes);
            % The total number of bits to byte-address the RAM contents is the
            % sum of:
            %  1) The number of address bits in the RAM, which includes the RAM
            %  banking
            %  2) The number of address bits needed to *byte* address a RAM
            %  word. This is computed above in bitsForData.
            totalBits = obj.NumAddressBits + bitsForData;
            coder.internal.errorIf(totalBits > 31, 'hdlmllib:hdlmllib:RAMTooLarge');
        end % setRAMSize

    end % protected methods (non-Impls)



    methods(Static, Access=private)
        function name = portName(basename, num, includeNum)
            if(includeNum)
                name = sprintf('%s_%c', basename, char('a'+num-1));
            else
                name = basename;
            end
        end
        function validateWriteBanks(refData, refAddr, writeData, writeAddr, writeEnable)
            scalar = isscalar(refAddr);
            if(isscalar(writeAddr) && isscalar(writeData) && isscalar(writeEnable) && scalar)
                return
            end
            isInputNonScalarVector = ~isvector(writeData) || ~isvector(writeAddr) || ...
                    ~isvector(writeEnable);

            coder.internal.errorIf(isInputNonScalarVector, ...
                    'hdlmllib:hdlmllib:RAMNonScalarVector');
            % Each input have to have same dimension corresponding to the
            % number of RAM banks
            writeDataSize = size(writeData);
            writeAddressSize = size(writeAddr);
            writeEnableSize = size(writeEnable);

            refSize = size(refAddr);
            vectorWrite = ~isscalar(writeData) && scalar && ...
                isequal(writeDataSize, size(refData));
            % Verify all sizes are same
            isSpecifiedNumBanksSame =  ...
                (isequal(writeDataSize, refSize) || vectorWrite) && ...
                isequal(writeAddressSize, refSize) && ...
                isequal(writeEnableSize, refSize);

            coder.internal.errorIf(~isSpecifiedNumBanksSame, ...
                'hdlmllib:hdlmllib:RAMNumBanksNotSame');
        end
        function validateReadBanks(refData, refAddr, readAddr)
            scalar = isscalar(refAddr);
            if(isscalar(refData) && isscalar(readAddr) && scalar)
                return
            end
            isInputNonScalarVector = ~isvector(readAddr);

            coder.internal.errorIf(isInputNonScalarVector, ...
                    'hdlmllib:hdlmllib:RAMNonScalarVector');

            % Each input have to have same dimension corresponding to the
            % number of RAM banks
            readAddrSize = size(readAddr);
            refSize = size(refAddr);

            % Verify all sizes are same
            isSpecifiedNumBanksSame =  ...
                isequal(refSize, readAddrSize);
            % Verify usage is valid for vector mode
            isVectorMode = ~isscalar(refData) && ...
                isscalar(refAddr) && ...
                isscalar(readAddr);

            coder.internal.errorIf(~(isSpecifiedNumBanksSame || isVectorMode), ...
                'hdlmllib:hdlmllib:RAMNumBanksNotSame');
        end

        function validateRAMWriteData(writeData)
            %Check for ufix1, nested bus and other unsupported data
            %types for struct input
            if isstruct(writeData)
                hdl.RAM.getStructDataBytes(writeData);
            end
            for nn = 1:length(writeData)
                % Write data must be scalar, fixed point or numeric
                validateattributes(writeData(nn), {'numeric', 'embedded.fi','struct','logical'}, ...
                    {'scalar'},...
                    'hdl.RAM', 'RAM write data', 1);
            end
        end % validateRAMWriteData

        function validateRAMWriteEnable(writeEnable, j)
            for nn = 1:length(writeEnable)
                % wrEn input must be logical or numeric, real, and scalar
                validateattributes(writeEnable(nn), {'logical'},...
                    {'real','scalar'}, 'hdl.RAM', 'RAM write enable', j);
            end
        end % validateRAMWriteEnable

        function validateRAMBankAddress(address, addressString, addressIndex)
            % Validate RAM address
            % should be real (not complex), scalar
            % HDL validation: between 2 and 32 bits (unless single or double)
            % unsigned fi, uint8, or uint32

            % size validation - check for real, scalar value
            % unsigned integer, unsigned embedded.fi, single or double
            validateattributes(address, ...
                {'single', 'double', 'uint8', 'uint16','embedded.fi'}, ...
                {'scalar', 'real'}, ...
                'hdl.RAM', addressString, addressIndex);

            % This check is implemented only if the types are not ambiguous
            % and need to be checked
            sizeCheckOnly = hdl.RAM.getSizeCheckStatus;
            if ~sizeCheckOnly && isfi(address)
                % if embedded.fi, unsigned, between 2 and 31 bit (inclusive)
                s = get(address, 'Signedness');
                wl = get(address, 'WordLength');
                fl = get(address, 'FractionLength');
                invalidAddress = (strcmpi(s, 'Signed')) ||...
                    (wl < 2 || wl > 31) || (fl ~= 0);
                coder.internal.errorIf(invalidAddress,...
                    'hdlmllib:hdlmllib:RAMAddress', addressIndex, addressString);
            end
        end % validateRAMBankAddress

        function validateRAMAddress(address, addressString, addressIndex)
            % Validate each RAM bank address
            % should be real (not complex), scalar
            % HDL validation: between 1 and 31 bits (unless single or double)
            % unsigned fi, uint8/16
            for nn = 1:length(address)
                hdl.RAM.validateRAMBankAddress(address(nn), addressString, addressIndex);
            end
        end % validateRAMAddress

        function validateRAMaddresses(ref, addresses)
            % check that all addresses are the same size
            % checking just the number of bits, assuming fraction length
            % and signedness are checked elsewhere

            % this check is implemented only if the types are not ambiguous
            % and need to be checked
            sizeCheckOnly = hdl.RAM.getSizeCheckStatus;
            if ~sizeCheckOnly
                size = hdl.RAM.calculateRAMBankSize(ref);
                addressSize = hdl.RAM.calculateRAMBankSize(addresses);
                if(addressSize ~= size)
                    coder.internal.assert(false, 'hdlmllib:hdlmllib:ReadWriteAddressNotSame');
                end
            end % ~sizeCheckOnly
        end % validateWriteReadAddresses

        function sizeCheckOnly = getSizeCheckStatus
            % return true if we are in check sizes only mode
            % in this case, a lot of validation is disabled and dummy
            % outputs are returned

            sizeCheckOnly = (~isempty(coder.target) && eml_ambiguous_types);
        end
        % getSizeCheckStatus

        function dataBytes = getStructDataBytes(data)
            % return bytes required to store the struct elements

            dataBytes = 0;
            fn = fieldnames(data);
            for i = 1:numel(fn)
                data1 = data(1).(fn{i});
                if isfi(data1)
                    tempBytes = data1.WordLength;
                elseif isenum(data1)
                    isSupported = false;
                    coder.internal.assert(isSupported, 'hdlmllib:hdlmllib:UnsupportedDataTypeForHDLRam', class(data1), ...
                        'IfNotConst', 'Fail');
                elseif isa(data1, 'single') || isa(data1, 'int32') || isa(data1, 'uint32')
                    tempBytes = 4;
                elseif isa(data1, 'double') || isa(data1, 'int64') || isa(data1, 'uint64')
                    tempBytes = 8;
                elseif isa(data1, 'int16') || isa(data1, 'uint16') || isa(data1, 'half')
                    tempBytes = 2;
                elseif isa(data1, 'int8') || isa(data1, 'uint8') || islogical(data1)
                    tempBytes = 1;
                elseif isstruct(data1)
                    isSupported = false;
                    coder.internal.assert(isSupported, ...
                        'hdlmllib:hdlmllib:UnsupportedNestedBusTypeForHDLRam');
                else
                    isSupported = false;
                    coder.internal.assert(isSupported, 'hdlmllib:hdlmllib:UnsupportedDataTypeForHDLRam', class(data1), ...
                            'IfNotConst', 'Fail');
                end
                dataBytes = dataBytes + tempBytes;
            end
            dataBytes = dataBytes*numel(data);
        end %getStructDataBytes

        function resetDataValue = getResetData(data_in)
            if isenum(data_in)
                isSupported = false;
                coder.internal.assert(isSupported, 'hdlmllib:hdlmllib:UnsupportedDataTypeForHDLRam', class(data_in), ...
                    'IfNotConst', 'Fail');
            elseif isfloat(data_in) || isinteger(data_in) || islogical(data_in)
                resetDataValue = cast(zeros(size(data_in)), 'like', data_in);
            else % fi
                dataNumerictype = get(data_in, 'numerictype');
                if isfimathlocal(data_in)
                    dataFiMath = get(data_in, 'fimath');
                    resetDataValue = fi(zeros(size(data_in)), 'numerictype', dataNumerictype, ...
                        'fimath', dataFiMath);
                else
                    resetDataValue = fi(zeros(size(data_in)), 'numerictype', dataNumerictype);
                end
            end
        end

        function sameType = typeCompare(a, b)
            if(isfi(a) && isfi(b))
                sameType = a.WordLength == b.WordLength && a.Signed == b.Signed ...
                   && a.FractionLength == b.FractionLength;
            else
                sameType = strcmp(class(a), class(b));
            end
        end
        function numAddressBits = getNumAddressBits(address) % Get RAM bank size
            % address is unsigned integer, fi, double or single
            % check if address is integer
            if isinteger(address)
                switch class(address)
                    case 'uint8'
                        numAddressBits = 8;
                    case 'uint16'
                        numAddressBits = 16;
                    case 'uint32'
                        numAddressBits = 32;
                    case 'uint64'
                        numAddressBits = 64;
                    otherwise % case 'uint16'
                        numAddressBits = 16;
                end
            elseif isfi(address) % fi
                numAddressBits = get(address, 'WordLength');
            else % double, single
                numAddressBits = 16;
            end
        end
        function ramBankSize = calculateRAMBankSize(address)

            ramBankSize = 2^(hdl.RAM.getNumAddressBits(address));
        end % getRAMBankSize

    end % static private methods

    methods (Static, Access=protected)
    function isVisible = showSimulateUsingImpl
        % Do not show 'simulate using' option on mask. This must be set to
        % 'false' before submission if changed to 'true' for debugging purposes.
        isVisible = false;
    end
end

end % hdl.RAM

% LocalWords:  RAMIV RAMTDPSameAddress
