--[[
  Copyright (c) 2022 by Plexim GmbH
  All rights reserved.

  A free license is granted to anyone to use this software for any legal
  non safety-critical purpose, including commercial applications, provided
  that:
  1) IT IS NOT USED TO DIRECTLY OR INDIRECTLY COMPETE WITH PLEXIM, and
  2) THIS COPYRIGHT NOTICE IS PRESERVED in its entirety.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
--]]

local U = require('common.utils')

local static = {}

local Module = {}

function Module.getBlock(globals, cpu)
  local SysCfg = require('common.block').getBlock(globals, cpu)
  if static[cpu] == nil then
    static[cpu] = {
      numInstances = 0,
      finalized = false,
    }
  end
  SysCfg.instance = static[cpu].numInstances
  static[cpu].numInstances = static[cpu].numInstances + 1

  function SysCfg:p_getDirectFeedthroughCode()
    U.error(
      'Explicit use of SysCfg:getDirectFeedthroughCode via target block not supported.')
  end

  function SysCfg:finalize(f)
    if static[self.cpu].finalized then
      return
    end

    if static[self.cpu].numInstances ~= 1 then
      U.error(
        'There should be only one (implicit) instance of the SysCfg block.')
    end

    if self:targetUsesPinsets() then
      -- Configuration for older targets is done in configure functions called from block.finalize()
      return
    end

    f.Include:append('pin_map.h')
    f.Include:append('gpio.h')
    f.Include:append('xbar.h')
    f.Include:append('asysctl.h')
    f.Include:append('sysctl.h')
    f.Include:append('plx_dio.h')

    local sysCfg = globals.syscfg:get()

    f.PreInitCode:append('{ // early system configuration')
    f.PostInitCode:append('{ // late system configuration')

    f.PreInitCode:append('PLX_DIO_sinit();')

    for periphType, entries in U.pairsSorted(sysCfg) do

      if periphType == 'System' 
      or periphType == 'csv' 
      or periphType == 'stackMon' then
        -- do nothing here for these blocks

      elseif periphType == 'gpio' then
        for _, gpio in ipairs(entries) do
          -- If pin isn't considered GPIO by default, make it so
          if (globals.target.pinRequiresGpioConfig(gpio.unit)) then
            f.PostInitCode:append('GPIO_setPinConfig(GPIO_%(pin)d_GPIO%(pin)d);' % {pin = gpio.unit})
          end
          -- configure hardware
          local pinType = 'GPIO_PIN_TYPE_STD'
          if gpio.cbx_direction.equals('OUTPUT') then
            if gpio.cbx_outputType.equals('OPEN_DRAIN') then
              pinType = 'GPIO_PIN_TYPE_OD'
            end
            f.PostInitCode:append('GPIO_setPadConfig(%d, %s);' % {gpio.unit, pinType})
            f.PostInitCode:append('GPIO_setDirectionMode(%d, GPIO_DIR_MODE_OUT);' % {gpio.unit})
          
          elseif gpio.cbx_direction.equals('INPUT') then
            if gpio.cbx_pullType.equals('PULL_UP') then
              pinType = '%s | GPIO_PIN_TYPE_PULLUP' % {pinType}
            end
            f.PreInitCode:append('GPIO_setPadConfig(%d, %s);' % {gpio.unit, pinType})
            f.PreInitCode:append('GPIO_setDirectionMode(%d, GPIO_DIR_MODE_IN);' % {gpio.unit})
          else
            error('invalid cbx_direction')
          end
    
          if gpio.core == 2 then
            local controllerName = self:targetMatches({'28P65x'})
               and 'Controller'
               or 'Master'  -- older targets still say Master.
            f.PreInitCode:append(
              'GPIO_set%(cName)sCore(%(unit)d, GPIO_CORE_CPU2);' %
              {
                cName = controllerName,
                unit = gpio.unit
              })
          end
          if (globals.target.pinHasAnalogMode(gpio.unit)) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio.unit})
          end
        end
      
      elseif periphType == 'sci' then
        for _, sci in ipairs(entries) do
          local unit_n = 1 + string.byte(sci.unit) - string.byte('A')
          f.PostInitCode:append([[
            GPIO_setPinConfig(%(rxgpio)s);
            GPIO_setPinConfig(%(txgpio)s);]] %
            {rxgpio = sci.pinconf[1], txgpio = sci.pinconf[2]})
          if (globals.target.pinHasAnalogMode(sci.pins[1])) then
            f.PreInitCode:append('GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' %
              {sci.pins[1]})
          end
          if (globals.target.pinHasAnalogMode(sci.pins[2])) then
            f.PreInitCode:append('GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' %
              {sci.pins[2]})
          end
          if sci.core == 2 then
            unit_n = 1 + string.byte(sci.unit) - string.byte('A')
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_SCI%s, SYSCTL_CPUSEL_CPU2);' %
                {sci.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL5_SCI, %(unit_n)d, SYSCTL_CPUSEL_CPU2);' %
                {unit_n = unit_n})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end

      elseif periphType == 'uart' then
        for _, uart in ipairs(entries) do
          local unit_n = 1 + string.byte(uart.unit) - string.byte('A')
          f.PostInitCode:append([[
            GPIO_setPinConfig(%(rxgpio)s);
            GPIO_setPinConfig(%(txgpio)s);]] %
            {rxgpio = uart.pinconf[1], txgpio = uart.pinconf[2]})
          if (globals.target.pinHasAnalogMode(uart.pins[1])) then
            f.PreInitCode:append('GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' %
              {uart.pins[1]})
          end
          if (globals.target.pinHasAnalogMode(uart.pins[2])) then
            f.PreInitCode:append('GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' %
              {uart.pins[2]})
          end
          if uart.core == 2 then
            unit_n = 1 + string.byte(uart.unit) - string.byte('A')
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_UART%s, SYSCTL_CPUSEL_CPU2);' %
                {uart.unit})
            elseif self:targetMatches({'29H85x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL5_UART, %(unit_n)d, SYSCTL_CPUSEL_CPU2);' %
                {unit_n = unit_n})
            else
              U.throwUnhandledTargetError('UART not supported for this target.')
            end
          end
        end

      elseif periphType == 'sdfm' then
        for _, sdfm in pairs(entries) do
          if sdfm.pinconf then
            for _, config in ipairs(sdfm.pinconf) do
              f.PostInitCode:append('GPIO_setPinConfig(%s);' % {config})
            end
          end
          if sdfm.invertClock then
            f.PostInitCode:append([[
              GPIO_setPadConfig(%d, GPIO_PIN_TYPE_INVERT);
              ]] % {sdfm.pins.clk})
          end
          -- If pins have analog mode, disable it
          for _, gpio in pairs(sdfm.pins) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
        end

      elseif periphType == 'epwm' then
        for _, epwm in ipairs(entries) do
          if epwm.pinconf ~= nil then
            for _, config in ipairs(epwm.pinconf) do
              f.PostInitCode:append('GPIO_setPinConfig(%s);' % {config})
            end
          end
          if epwm.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_EPWM%d, SYSCTL_CPUSEL_CPU2);' %
                {epwm.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL0_EPWM, %d, SYSCTL_CPUSEL_CPU2);' %
                {epwm.unit})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
          -- If pins have analog mode, disable it
          for _, gpio in ipairs(epwm.gpios) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
          -- On dual CPU targets the SyncSocRegs register is only available on CPU1
          -- Only the 28379D target implements getSyncRegsCode()
          if globals.target.getSyncSocRegsCode ~= nil then
            for _, b in ipairs(globals.instances) do
              if b:blockMatches('epwm') then
                if b:getParameter('epwm') == epwm.unit then
                  local opt_sync = b:getOptSync()
                  if opt_sync and opt_sync.synci_sel then
                    f.PostInitCode:append(
                      globals.target.getSyncSocRegsCode(epwm.unit, opt_sync))
                  end
                end
              end
            end
          end
        end
        -- always generate sync code on CPU 1
        f.TimerSyncCode:append(globals.target.getEpwmTimersSyncCode())
      
      elseif periphType == 'can' then
        for _, can in ipairs(entries) do
          local unit_n = 1 + string.byte(can.unit) - string.byte('A')
          f.PostInitCode:append([[
            GPIO_setPadConfig(%(rxgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setPadConfig(%(txgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setQualificationMode(%(rxgpio_num)d, GPIO_QUAL_ASYNC);]] %
            {rxgpio_num = can.pins[1], txgpio_num = can.pins[2]})
          -- If pins have analog mode, disable it
          for _, gpio in ipairs(can.pins) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
          if can.pinconf ~= nil then
            for _, config in ipairs(can.pinconf) do
              f.PostInitCode:append('GPIO_setPinConfig(%s);' % {config})
            end
          end
          if can.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_CAN%s, SYSCTL_CPUSEL_CPU2);' %
                {can.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL8_CAN, %(unit_n)d, SYSCTL_CPUSEL_CPU2);' %
                {unit_n = unit_n})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      
      elseif periphType == 'mcan' then
        for _, mcan in ipairs(entries) do
          f.PostInitCode:append([[
            GPIO_setPadConfig(%(rxgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setPadConfig(%(txgpio_num)d, GPIO_PIN_TYPE_PULLUP);
            GPIO_setQualificationMode(%(rxgpio_num)d, GPIO_QUAL_ASYNC);
            ]] % {
            rxgpio_num = mcan.pins[1], 
            txgpio_num = mcan.pins[2],
          })
          -- If pins have analog mode, disable it
          for _, gpio in ipairs(mcan.pins) do
            if (globals.target.pinHasAnalogMode(gpio)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {gpio})
            end
          end
          if mcan.pinconf ~= nil then
            for _, config in ipairs(mcan.pinconf) do
              f.PostInitCode:append('GPIO_setPinConfig(%s);' % {config})
            end
          end
          if mcan.core == 2 then
            U.error('MCAN not supported on CPU2.')
          end
        end
      
      elseif periphType == 'spi' then
        for _, spi in ipairs(entries) do
          local unit_n = 1 + string.byte(spi.unit) - string.byte('A')
          for i = 1, #spi.pins do
            f.PostInitCode:append([[
              GPIO_setPadConfig(%(pin)d, GPIO_PIN_TYPE_PULLUP);
              GPIO_setQualificationMode(%(pin)d, GPIO_QUAL_SYNC);
              GPIO_setPinConfig(%(conf)s);]] %
              {pin = spi.pins[i], conf = spi.pinconf[i]})
            if (globals.target.pinHasAnalogMode(spi.pins[i])) then
              f.PreInitCode:append('GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' %
                {spi.pins[i]})
            end
          end
          if spi.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_SPI%s, SYSCTL_CPUSEL_CPU2);' %
                {spi.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL6_SPI, %(unit_n)d, SYSCTL_CPUSEL_CPU2);' %
                {unit_n = unit_n})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end

      elseif periphType == 'qep' then
        for _, qep in ipairs(entries) do
          for _, pin in ipairs(qep.pins) do
            f.PostInitCode:append([[
              GPIO_setPadConfig(%(pin)d, GPIO_PIN_TYPE_PULLUP);
              GPIO_setQualificationMode(%(pin)d, GPIO_QUAL_SYNC);]] %
              {pin = pin})
            -- If pins have analog mode, disable it
            if (globals.target.pinHasAnalogMode(pin)) then
              f.PreInitCode:append(
                'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {pin})
            end
          end
          if qep.pinconf ~= nil then
            for _, config in ipairs(qep.pinconf) do
              f.PostInitCode:append('GPIO_setPinConfig(%s);' % {config})
            end
          end
          if qep.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_EQEP%d, SYSCTL_CPUSEL_CPU2);' %
                {qep.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL2_EQEP, %d, SYSCTL_CPUSEL_CPU2);' %
                {qep.unit})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      
      elseif periphType == 'cmpss' then
        for _, cmpss in ipairs(entries) do
          if self:targetMatches({'29H85x'}) then
            if cmpss.opt_lpmux then
              f.PreInitCode:append(
                'ASysCtl_configCMPMux(ASYSCTL_CMP%(unit)d_LP_%(mux)d);' %
                {unit = cmpss.unit, mux = cmpss.opt_lpmux})
            end
            if cmpss.opt_hpmux then
              f.PreInitCode:append(
                'ASysCtl_configCMPMux(ASYSCTL_CMP%(unit)d_HP_%(mux)d);' %
                {unit = cmpss.unit, mux = cmpss.opt_hpmux})
            end
          elseif self:targetMatches({'28P55x', '28P65x', '2838x', '28003x', '28004x', '2837x', '280013x'}) then
 
            if cmpss.opt_lpmux then
              f.PreInitCode:append(
                'ASysCtl_selectCMPLPMux(ASYSCTL_CMPLPMUX_SELECT_%(unit)d, %(mux)d);' %
                {unit = cmpss.unit, mux = cmpss.opt_lpmux})
            end
            if cmpss.opt_hpmux then
              f.PreInitCode:append(
                'ASysCtl_selectCMPHPMux(ASYSCTL_CMPHPMUX_SELECT_%(unit)d, %(mux)d);' %
                {unit = cmpss.unit, mux = cmpss.opt_hpmux})
            end
          else
            error('Unhandled target')
          end
          if cmpss.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_CMPSS%d, SYSCTL_CPUSEL_CPU2);' %
                {cmpss.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL12_CMPSS, %d, SYSCTL_CPUSEL_CPU2);' %
                {cmpss.unit})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      
      elseif periphType == 'epwm_xbar' then
        for _, epwm_xbar in ipairs(entries) do
          for iter, muxconf in ipairs(epwm_xbar.muxconf) do
            if self:targetMatches({'29H85x'}) then
              f.PostInitCode:append([[
                XBAR_selectEpwmXbarInputSource(XBAR_TRIP%(trip)d, %(muxconf)s);]] %
              {trip = epwm_xbar.trip, muxconf = muxconf})
            elseif self:targetMatches({'2837x', '2838x', '28003x', '28004x', '280013x', '28P55x', '28P65x'}) then
              f.PostInitCode:append([[
                  XBAR_setEPWMMuxConfig(XBAR_TRIP%(trip)d, %(muxconf)s);
                  XBAR_enableEPWMMux(XBAR_TRIP%(trip)d, XBAR_MUX%(mux)02d);]] %
                {trip = epwm_xbar.trip, mux = epwm_xbar.mux[iter], muxconf = muxconf})
            else
              U.throwUnhandledTargetError()
            end
          end
        end
      
      elseif periphType == 'input_xbar' then
        for _, input_xbar in ipairs(entries) do
          local xbarConfig
          if self:targetMatches({'28004x', '2837x'}) then
            xbarConfig = 'XBAR_setInputPin(XBAR_INPUT%(input)d, %(pin)d);'
          elseif self:targetMatches({'2838x', '28003x', '280013x', '28P55x', '28P65x', '29H85x'}) then
            xbarConfig =
            'XBAR_setInputPin(INPUTXBAR_BASE, XBAR_INPUT%(input)d, %(pin)d);'
          else
            U.throwUnhandledTargetError()
          end
          f.PreInitCode:append(xbarConfig %
            {pin = input_xbar.gpio, input = input_xbar.input})
        end
      
      elseif periphType == 'ecap' then
        for _, ecap in ipairs(entries) do
          if (globals.target.pinHasAnalogMode(ecap.gpio)) then
            f.PreInitCode:append(
              'GPIO_setAnalogMode(%d, GPIO_ANALOG_DISABLED);' % {ecap.gpio})
          end
          if ecap.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_ECAP%d, SYSCTL_CPUSEL_CPU2);' %
                {ecap.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL1_ECAP, %d, SYSCTL_CPUSEL_CPU2);' %
                {ecap.unit})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      
      elseif periphType == 'adc' then
        for _, adc in ipairs(entries) do
          local unit_n = 1 + string.byte(adc.unit) - string.byte('A')
          if adc.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_ADC%s, SYSCTL_CPUSEL_CPU2);' %
                {adc.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL11_ADC, %d, SYSCTL_CPUSEL_CPU2);' %
                {unit_n})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      
      elseif periphType == 'analog_port' then
        local initializedPorts = {}
        for _, analogPort in ipairs(entries) do
          if analogPort.pins then
            for _, pin in ipairs(analogPort.pins) do
              if not initializedPorts[pin] then
                f.PostInitCode:append(
                  'GPIO_setAnalogMode(%d, GPIO_ANALOG_ENABLED);' % {pin})
                initializedPorts[pin] = true
              end
            end
          end
        end
      
      elseif periphType == 'dac' then
        for _, dac in ipairs(entries) do
          local unit_n = 1 + string.byte(dac.unit) - string.byte('A')
          if dac.core == 2 then
            if self:targetMatches({'28P65x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheralInstance(SYSCTL_CPUSEL_DAC%s, SYSCTL_CPUSEL_CPU2);' %
                {dac.unit})
            elseif self:targetMatches({'2837x', '2838x'}) then
              f.PreInitCode:append(
                'SysCtl_selectCPUForPeripheral(SYSCTL_CPUSEL14_DAC, %d, SYSCTL_CPUSEL_CPU2);' %
                {unit_n})
            else
              U.throwUnhandledTargetError('Unhandled multi-cpu target.')
            end
          end
        end
      
      elseif periphType == 'ipc_receive' then
        for _, ipc_rx in ipairs(entries) do
          local match = {
            flag = false,
            datatype = false,
            datalength = false,
          }
          local remote = {
            datatype = nil, -- these initializations are meaningless...
            datalength = nil,
          }

          if sysCfg['ipc_transmit'] ~= nil then
            for _, ipc_tx in ipairs(sysCfg['ipc_transmit']) do
              if ipc_tx.flag == ipc_rx.flag then
                match.flag = true
                if ipc_tx.datatype == ipc_rx.datatype then
                  match.datatype = true
                else
                  remote.datatype = ipc_tx.datatype
                end
                if ipc_tx.datalength == ipc_rx.datalength then
                  match.datalength = true
                else
                  remote.datalength = ipc_tx.datalength
                end
                break
              end
            end
          end
          if not match.flag then
            U.error('No matching IPC transmit block with flag %d on CPU%d found.' %
              {ipc_rx.flag, ipc_rx.remote_cpu})
          end
          if not match.datatype then
            U.error([[
              Data type for IPC blocks with flag %(rxFlag)d does not match.
              • CPU%(rxRemoteCpu)d has data type: %(remoteDataType)s
              • CPU%(rxLocalCpu)d has data type: %(rxDataType)s
            ]] % {
              rxFlag = ipc_rx.flag,
              rxRemoteCpu = ipc_rx.remote_cpu,
              remoteDataType = U.dataTypeAsString(remote.datatype),
              rxLocalCpu = ipc_rx.local_cpu,
              rxDataType = U.dataTypeAsString(ipc_rx.datatype),
            })
          end
          if not match.datalength then
            U.error([[
              Data length (number of signals) for IPC blocks with flag %(rxFlag)d does not match.
              • CPU%(remoteCpu)d transmits %(remoteDataLen)d signal(s)
              • CPU%(localCpu)d is configured to receive %(rxDataLen)d signal(s)
            ]] % {
              rxFlag = ipc_rx.flag,
              remoteCpu = ipc_rx.remote_cpu,
              remoteDataLen = remote.datalength,
              localCpu = ipc_rx.local_cpu,
              rxDataLen = ipc_rx.datalength,
            })
          end
        end
      
      elseif periphType == 'ipc_transmit' then
        for _, ipc_tx in ipairs(entries) do
          local flag = ipc_tx.flag
          local match = {}
          match.flag = false
          if sysCfg['ipc_receive'] ~= nil then
            for _, ipc_rx in ipairs(sysCfg['ipc_receive']) do
              if ipc_rx.flag == flag then
                match.flag = true
                break
              end
            end
          end
          if not match.flag then
            U.error('No matching IPC receive block with flag %d on CPU%d found.' %
              {ipc_tx.flag, ipc_tx.remote_cpu})
          end
        end
      
      else
        error(
          'Undefined peripheral type for syscfg.lua: %(pType)s' %
          {pType = periphType})
      end

    end

    f.PreInitCode:append('}')
    f.PostInitCode:append('}')

    static[self.cpu].finalized = true
  end

  return SysCfg
end

return Module
