| /* |
| * 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; |
| } |