|  | /* | 
|  | * Copyright 2020-2022 F4PGA Authors | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  | #include "buffers.h" | 
|  | #include <cassert> | 
|  | #include <cmath> | 
|  | #include <limits> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | const std::vector<std::string> Pll::inputs = {"CLKIN1", "CLKIN2"}; | 
|  | const std::vector<std::string> Pll::outputs = {"CLKOUT0", "CLKOUT1", "CLKOUT2", "CLKOUT3", "CLKOUT4", "CLKOUT5"}; | 
|  | const float Pll::delay = 0; | 
|  | const std::string Pll::name = "PLLE2_ADV"; | 
|  |  | 
|  | Pll::Pll(RTLIL::Cell *cell, float input_clock_period, float input_clock_rising_edge) : ClockDivider({"PLLE2_ADV"}) | 
|  | { | 
|  | assert(RTLIL::unescape_id(cell->type) == "PLLE2_ADV"); | 
|  | FetchParams(cell); | 
|  | CheckInputClockPeriod(cell, input_clock_period); | 
|  | CalculateOutputClockPeriods(); | 
|  | CalculateOutputClockWaveforms(input_clock_rising_edge); | 
|  | } | 
|  |  | 
|  | void Pll::CheckInputClockPeriod(RTLIL::Cell *cell, float input_clock_period) | 
|  | { | 
|  | float abs_diff = fabs(ClkinPeriod() - input_clock_period); | 
|  | bool approx_equal = abs_diff < std::max(ClkinPeriod(), input_clock_period) * 10 * std::numeric_limits<float>::epsilon(); | 
|  | if (!approx_equal) { | 
|  | log_cmd_error("CLKIN[1/2]_PERIOD doesn't match the virtual clock constraint " | 
|  | "propagated to the CLKIN[1/2] input of the clock divider cell: " | 
|  | "%s.\nInput clock period: %f, CLKIN[1/2]_PERIOD: %f\n", | 
|  | RTLIL::id2cstr(cell->name), input_clock_period, ClkinPeriod()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Pll::FetchParams(RTLIL::Cell *cell) | 
|  | { | 
|  | clkin1_period = FetchParam(cell, "CLKIN1_PERIOD", 0.0); | 
|  | clkin2_period = FetchParam(cell, "CLKIN2_PERIOD", 0.0); | 
|  | clk_mult = FetchParam(cell, "CLKFBOUT_MULT", 5.0); | 
|  | clk_fbout_phase = FetchParam(cell, "CLKFBOUT_PHASE", 0.0); | 
|  | divclk_divisor = FetchParam(cell, "DIVCLK_DIVIDE", 1.0); | 
|  | for (auto output : outputs) { | 
|  | // CLKOUT[0-5]_DUTY_CYCLE | 
|  | clkout_duty_cycle[output] = FetchParam(cell, output + "_DUTY_CYCLE", 0.5); | 
|  | // CLKOUT[0-5]_DIVIDE | 
|  | clkout_divisor[output] = FetchParam(cell, output + "_DIVIDE", 1.0); | 
|  | // CLKOUT[0-5]_PHASE | 
|  | clkout_phase[output] = FetchParam(cell, output + "_PHASE", 0.0); | 
|  | } | 
|  | } | 
|  |  | 
|  | void Pll::CalculateOutputClockPeriods() | 
|  | { | 
|  | for (auto output : outputs) { | 
|  | // CLKOUT[0-5]_PERIOD = CLKIN1_PERIOD * CLKOUT[0-5]_DIVIDE * | 
|  | // DIVCLK_DIVIDE / CLKFBOUT_MULT | 
|  | clkout_period[output] = ClkinPeriod() * clkout_divisor.at(output) / clk_mult * divclk_divisor; | 
|  | } | 
|  | } | 
|  |  | 
|  | void Pll::CalculateOutputClockWaveforms(float input_clock_rising_edge) | 
|  | { | 
|  | for (auto output : outputs) { | 
|  | float output_clock_period = clkout_period.at(output); | 
|  | clkout_rising_edge[output] = | 
|  | fmod(input_clock_rising_edge - (clk_fbout_phase / 360.0) * ClkinPeriod() + output_clock_period * (clkout_phase[output] / 360.0), | 
|  | output_clock_period); | 
|  | clkout_falling_edge[output] = fmod(clkout_rising_edge[output] + clkout_duty_cycle[output] * output_clock_period, output_clock_period); | 
|  | } | 
|  | } | 
|  |  | 
|  | float Pll::FetchParam(RTLIL::Cell *cell, std::string &¶m_name, float default_value) | 
|  | { | 
|  | RTLIL::IdString param(RTLIL::escape_id(param_name)); | 
|  | if (cell->hasParam(param)) { | 
|  | auto param_obj = cell->parameters.at(param); | 
|  | std::string value; | 
|  | if (param_obj.flags & RTLIL::CONST_FLAG_STRING) { | 
|  | value = param_obj.decode_string(); | 
|  | } else { | 
|  | value = std::to_string(param_obj.as_int()); | 
|  | } | 
|  | return std::stof(value); | 
|  | } | 
|  | return default_value; | 
|  | } |