rust_hdl_fpga_support/lattice/ice40/
ice_pll.rs

1// Based on https://github.com/YosysHQ/icestorm/blob/master/icepll/icepll.cc
2// Original license:
3//
4//  Copyright (C) 2015  Clifford Wolf <clifford@clifford.at>
5//
6//  Permission to use, copy, modify, and/or distribute this software for any
7//  purpose with or without fee is hereby granted, provided that the above
8//  copyright notice and this permission notice appear in all copies.
9//
10//  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11//  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12//  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13//  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14//  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15//  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16//  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17//
18
19use rust_hdl_core::prelude::*;
20
21#[derive(Clone, Default, Debug)]
22struct ICE40PLLSettings {
23    f_pllin: f64,
24    fout: f64,
25    divr: i32,
26    divf: i32,
27    divq: i32,
28    simple: bool,
29}
30
31impl ICE40PLLSettings {
32    fn filter_range(&self) -> usize {
33        let f_pfd = self.f_pllin / (self.divr as f64 + 1.);
34        let filter_range = if f_pfd < 17. {
35            1
36        } else if f_pfd < 26. {
37            2
38        } else if f_pfd < 44. {
39            3
40        } else if f_pfd < 66. {
41            4
42        } else if f_pfd < 101. {
43            5
44        } else {
45            6
46        };
47        filter_range
48    }
49}
50
51fn analyze(simple_feedback: bool, f_pllin: f64, f_pllout: f64) -> Option<ICE40PLLSettings> {
52    let mut found_something = false;
53    let mut best = ICE40PLLSettings::default();
54    best.simple = simple_feedback;
55
56    let divf_max = if simple_feedback { 127 } else { 63 };
57    // The documentation in the iCE40 PLL Usage Guide incorrectly lists the
58    // maximum value of DIVF as 63, when it is only limited to 63 when using
59    // feedback modes other that SIMPLE.
60
61    if f_pllin < 10. || f_pllin > 133. {
62        panic!(
63            "Error: PLL input frequency {} MHz is outside range 10 MHz - 133 MHz!\n",
64            f_pllin
65        );
66    }
67
68    if f_pllout < 16. || f_pllout > 275. {
69        panic!(
70            "Error: PLL output frequency {} MHz is outside range 16 MHz - 275 MHz!\n",
71            f_pllout
72        );
73    }
74
75    for divr in 0..=15 {
76        let f_pfd = f_pllin / (divr as f64 + 1.);
77        if f_pfd < 10. || f_pfd > 133. {
78            continue;
79        }
80        for divf in 0..=divf_max {
81            if simple_feedback {
82                let f_vco = f_pfd * (divf as f64 + 1.);
83                if f_vco < 533. || f_vco > 1066. {
84                    continue;
85                }
86                for divq in 1..=6 {
87                    let fout = f_vco * f64::exp2(-divq as f64);
88                    if f64::abs(fout - f_pllout) < f64::abs(best.fout - f_pllout)
89                        || !found_something
90                    {
91                        best.fout = fout;
92                        best.divr = divr;
93                        best.divf = divf;
94                        best.divq = divq;
95                        found_something = true;
96                    }
97                }
98            } else {
99                for divq in 1..=6 {
100                    let f_vco = f_pfd * (divf as f64 + 1.) * f64::exp2(divq as f64);
101                    if f_vco < 533. || f_vco > 1066. {
102                        continue;
103                    }
104                    let fout = f_vco * f64::exp2(-divq as f64);
105                    if f64::abs(fout - f_pllout) < f64::abs(best.fout - f_pllout)
106                        || !found_something
107                    {
108                        best.fout = fout;
109                        best.divr = divr;
110                        best.divf = divf;
111                        best.divq = divq;
112                        found_something = true;
113                    }
114                }
115            }
116        }
117    }
118    if found_something {
119        Some(best)
120    } else {
121        None
122    }
123}
124
125#[test]
126fn test_pll_gen() {
127    let x = analyze(true, 100., 33.33333);
128    println!("x: {:?}", x);
129    assert!(x.is_some());
130    let x = x.unwrap();
131    assert!((x.fout - 33.3333).abs() < 1e-3);
132}
133
134#[derive(LogicBlock)]
135pub struct ICE40PLLBlock<const FIN_FREQ: u64, const FOUT_FREQ: u64> {
136    pub clock_in: Signal<In, Clock>,
137    pub clock_out: Signal<Out, Clock>,
138    pub locked: Signal<Out, Bit>,
139    core: ICEPLL40Core,
140    _settings: ICE40PLLSettings,
141}
142
143impl<const FIN_FREQ: u64, const FOUT_FREQ: u64> Default for ICE40PLLBlock<FIN_FREQ, FOUT_FREQ> {
144    fn default() -> Self {
145        let freq_in_mhz = (FIN_FREQ as f64) / (1_000_000.0);
146        let freq_out_mhz = (FOUT_FREQ as f64) / (1_000_000.0);
147        Self {
148            clock_in: Signal::default(),
149            clock_out: Signal::new_with_default(Clock::default()),
150            locked: Signal::new_with_default(false),
151            core: ICEPLL40Core::new(),
152            _settings: analyze(true, freq_in_mhz, freq_out_mhz).unwrap(),
153        }
154    }
155}
156
157impl<const FIN_FREQ: u64, const FOUT_FREQ: u64> Logic for ICE40PLLBlock<FIN_FREQ, FOUT_FREQ> {
158    fn update(&mut self) {}
159
160    fn connect(&mut self) {
161        self.clock_out.connect();
162        self.locked.connect();
163    }
164
165    fn hdl(&self) -> Verilog {
166        Verilog::Custom(format!(
167            "\
168SB_PLL40_CORE #(
169                .FEEDBACK_PATH(\"{feedback}\"),
170                .DIVR({DIVR}),
171                .DIVF({DIVF}),
172                .DIVQ({DIVQ}),
173                .FILTER_RANGE({FILTER_RANGE})
174               ) uut (
175                .LOCK(locked),
176                .RESETB(1'b1),
177                .BYPASS(1'b0),
178                .REFERENCECLK(clock_in),
179                .PLLOUTCORE(clock_out));
180",
181            feedback = if self._settings.simple {
182                "SIMPLE"
183            } else {
184                "NON_SIMPLE"
185            },
186            DIVR = VerilogLiteral::from(self._settings.divr as u32),
187            DIVF = VerilogLiteral::from(self._settings.divf as u32),
188            DIVQ = VerilogLiteral::from(self._settings.divq as u32),
189            FILTER_RANGE = VerilogLiteral::from(self._settings.filter_range())
190        ))
191    }
192}
193
194#[derive(LogicBlock)]
195pub struct ICEPLL40Core {}
196
197impl ICEPLL40Core {
198    pub fn new() -> ICEPLL40Core {
199        Self {}
200    }
201}
202
203impl Logic for ICEPLL40Core {
204    fn update(&mut self) {}
205
206    fn hdl(&self) -> Verilog {
207        Verilog::Blackbox(BlackBox {
208            code: r#"
209(* blackbox *)
210module SB_PLL40_CORE (
211    input   REFERENCECLK,
212    output  PLLOUTCORE,
213    output  PLLOUTGLOBAL,
214    input   EXTFEEDBACK,
215    input   [7:0] DYNAMICDELAY,
216    output  LOCK,
217    input   BYPASS,
218    input   RESETB,
219    input   LATCHINPUTVALUE,
220    output  SDO,
221    input   SDI,
222    input   SCLK
223);
224parameter FEEDBACK_PATH = "SIMPLE";
225parameter DELAY_ADJUSTMENT_MODE_FEEDBACK = "FIXED";
226parameter DELAY_ADJUSTMENT_MODE_RELATIVE = "FIXED";
227parameter SHIFTREG_DIV_MODE = 1'b0;
228parameter FDA_FEEDBACK = 4'b0000;
229parameter FDA_RELATIVE = 4'b0000;
230parameter PLLOUT_SELECT = "GENCLK";
231parameter DIVR = 4'b0000;
232parameter DIVF = 7'b0000000;
233parameter DIVQ = 3'b000;
234parameter FILTER_RANGE = 3'b000;
235parameter ENABLE_ICEGATE = 1'b0;
236parameter TEST_MODE = 1'b0;
237parameter EXTERNAL_DIVIDE_FACTOR = 1;
238endmodule
239            "#
240            .into(),
241            name: "SB_PLL40_CORE".into(),
242        })
243    }
244}