-
Notifications
You must be signed in to change notification settings - Fork 13
/
ecp5pll.sv
270 lines (243 loc) · 10 KB
/
ecp5pll.sv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// (c)EMARD
// License=BSD
// parametric ECP5 PLL generator in systemverilog
// actual frequency can be equal or higher than requested
// to see actual frequencies
// trellis log/stdout : search for "MHz", "Derived", "frequency"
// to see actual phase shifts
// diamond log/*.mrp : search for "Phase", "Desired"
module ecp5pll
#(
parameter integer in_hz = 25000000,
parameter integer out0_hz = 25000000,
parameter integer out0_deg = 0, // keep 0
parameter integer out0_tol_hz= 0, // tolerance: if freq differs more, then error
parameter integer out1_hz = 0,
parameter integer out1_deg = 0,
parameter integer out1_tol_hz= 0,
parameter integer out2_hz = 0,
parameter integer out2_deg = 0,
parameter integer out2_tol_hz= 0,
parameter integer out3_hz = 0,
parameter integer out3_deg = 0,
parameter integer out3_tol_hz= 0,
parameter integer reset_en = 0,
parameter integer standby_en = 0,
parameter integer dynamic_en = 0
)
(
input clk_i,
output [3:0] clk_o,
input reset,
input standby,
input [1:0] phasesel,
input phasedir, phasestep, phaseloadreg,
output locked
);
localparam PFD_MIN = 3125000;
localparam PFD_MAX = 400000000;
localparam VCO_MIN = 400000000;
localparam VCO_MAX = 800000000;
localparam VCO_OPTIMAL = (VCO_MIN+VCO_MAX)/2;
function integer abs(input integer x);
abs = x > 0 ? x : -x;
endfunction
function integer F_ecp5pll(input integer x);
integer input_div, input_div_min, input_div_max;
integer output_div, output_div_min, output_div_max;
integer feedback_div, feedback_div_min, feedback_div_max;
integer fvco, fout;
integer error, error_prev;
integer params_fvco;
integer div1, div2, div3;
integer params_refclk_div;
integer params_feedback_div;
integer params_output_div;
params_fvco = 0;
error_prev = 999999999;
input_div_min = in_hz/PFD_MAX;
if(input_div_min < 1)
input_div_min = 1;
input_div_max = in_hz/PFD_MIN;
if(input_div_max > 128)
input_div_max = 128;
for(input_div = input_div_min; input_div <= input_div_max; input_div=input_div+1)
begin
if(out0_hz / 1000000 * input_div < 2000)
feedback_div = out0_hz * input_div / in_hz;
else
feedback_div = out0_hz / in_hz * input_div;
feedback_div_min = feedback_div;
feedback_div_max = feedback_div+1;
if(feedback_div_min < 1)
feedback_div_min = 1;
if(feedback_div_max > 80)
feedback_div_max = 80;
for(feedback_div = feedback_div_min; feedback_div <= feedback_div_max; feedback_div = feedback_div+1)
begin
output_div_min = (VCO_MIN/feedback_div) / (in_hz/input_div);
if(output_div_min < 1)
output_div_min = 1;
output_div_max = (VCO_MAX/feedback_div) / (in_hz/input_div);
if(output_div_max > 128)
output_div_max = 128;
fout = in_hz * feedback_div / input_div;
for(output_div = output_div_min; output_div <= output_div_max; output_div=output_div+1)
begin
fvco = fout * output_div;
error = abs(fout-out0_hz)
+ (out1_hz > 0 ? abs(fvco/(fvco >= out1_hz ? fvco/out1_hz : 1)-out1_hz) : 0)
+ (out2_hz > 0 ? abs(fvco/(fvco >= out2_hz ? fvco/out2_hz : 1)-out2_hz) : 0)
+ (out3_hz > 0 ? abs(fvco/(fvco >= out3_hz ? fvco/out3_hz : 1)-out3_hz) : 0);
if( error < error_prev
|| (error == error_prev && abs(fvco-VCO_OPTIMAL) < abs(params_fvco-VCO_OPTIMAL)) )
begin
error_prev = error;
params_refclk_div = input_div;
params_feedback_div = feedback_div;
params_output_div = output_div;
params_fvco = fvco;
end
end
end
end
// FIXME in the future when yosys supports struct
if(x==0)
F_ecp5pll = params_refclk_div;
if(x==1)
F_ecp5pll = params_feedback_div;
if(x==2)
F_ecp5pll = params_output_div;
endfunction
function integer F_primary_phase(input integer output_div, deg);
integer phase_compensation;
integer phase_count_x8;
phase_compensation = (output_div+1)/2*8-8+output_div/2*8; // output_div/2*8 = 180 deg shift
phase_count_x8 = phase_compensation + 8*output_div*deg/360;
if(phase_count_x8 > 1023)
phase_count_x8 = phase_count_x8 % (output_div*8); // wraparound 360 deg
F_primary_phase = phase_count_x8;
endfunction
// FIXME it is inefficient to call F_ecp5pll multiple times
localparam params_refclk_div = F_ecp5pll(0);
localparam params_feedback_div = F_ecp5pll(1);
localparam params_output_div = F_ecp5pll(2);
localparam params_fout = in_hz * params_feedback_div / params_refclk_div;
localparam params_fvco = params_fout * params_output_div;
localparam params_primary_phase_x8 = F_ecp5pll(3);
localparam params_primary_cphase = F_primary_phase(params_output_div, out0_deg) / 8;
localparam params_primary_fphase = F_primary_phase(params_output_div, out0_deg) % 8;
function integer F_secondary_divisor(input integer sfreq);
F_secondary_divisor = 1;
if(sfreq > 0)
if(params_fvco >= sfreq)
F_secondary_divisor = params_fvco/sfreq;
endfunction
function integer F_secondary_phase(input integer sfreq, sphase);
integer div, freq;
integer phase_compensation, phase_count_x8;
phase_count_x8 = 0;
if(sfreq > 0)
begin
div = 1;
if(params_fvco >= sfreq)
div = params_fvco/sfreq;
freq = params_fvco/div;
phase_compensation = div*8-8;
phase_count_x8 = phase_compensation + 8*div*sphase/360;
if(phase_count_x8 > 1023)
phase_count_x8 = phase_count_x8 % (div*8); // wraparound 360 deg
end
F_secondary_phase = phase_count_x8;
endfunction
localparam params_secondary1_div = F_secondary_divisor(out1_hz);
localparam params_secondary1_cphase = F_secondary_phase (out1_hz, out1_deg) / 8;
localparam params_secondary1_fphase = F_secondary_phase (out1_hz, out1_deg) % 8;
localparam params_secondary2_div = F_secondary_divisor(out2_hz);
localparam params_secondary2_cphase = F_secondary_phase (out2_hz, out2_deg) / 8;
localparam params_secondary2_fphase = F_secondary_phase (out2_hz, out2_deg) % 8;
localparam params_secondary3_div = F_secondary_divisor(out3_hz);
localparam params_secondary3_cphase = F_secondary_phase (out3_hz, out3_deg) / 8;
localparam params_secondary3_fphase = F_secondary_phase (out3_hz, out3_deg) % 8;
// check if generated frequencies are out of range
localparam error_out0_hz = abs(out0_hz - params_fout) > out0_tol_hz;
localparam error_out1_hz = out1_hz > 0 ? abs(out1_hz - params_fvco / params_secondary1_div) > out1_tol_hz : 0;
localparam error_out2_hz = out2_hz > 0 ? abs(out2_hz - params_fvco / params_secondary2_div) > out2_tol_hz : 0;
localparam error_out3_hz = out3_hz > 0 ? abs(out3_hz - params_fvco / params_secondary3_div) > out3_tol_hz : 0;
// diamond: won't compile this, comment it out. Workaround follows using division by zero
if(error_out0_hz) $error("out0_hz tolerance exceeds out0_tol_hz");
if(error_out1_hz) $error("out1_hz tolerance exceeds out1_tol_hz");
if(error_out2_hz) $error("out2_hz tolerance exceeds out2_tol_hz");
if(error_out3_hz) $error("out3_hz tolerance exceeds out3_tol_hz");
// diamond: trigger error with division by zero, doesn't accept $error()
localparam trig_out0_hz = error_out0_hz ? 1/0 : 0;
localparam trig_out1_hz = error_out1_hz ? 1/0 : 0;
localparam trig_out2_hz = error_out2_hz ? 1/0 : 0;
localparam trig_out3_hz = error_out3_hz ? 1/0 : 0;
wire [1:0] PHASESEL_HW = phasesel-1;
wire CLKOP; // internal
// TODO: frequencies in MHz if passed as "attributes"
// will appear in diamond *.mrp file like "Output Clock(P) Frequency (MHz):"
// but I don't know how to pass string parameters for this:
// (* FREQUENCY_PIN_CLKI="025.000000" *)
// (* FREQUENCY_PIN_CLKOP="023.345678" *)
// (* FREQUENCY_PIN_CLKOS="034.234567" *)
// (* FREQUENCY_PIN_CLKOS2="111.345678" *)
// (* FREQUENCY_PIN_CLKOS3="123.456789" *)
(* ICP_CURRENT="12" *) (* LPF_RESISTOR="8" *) (* MFG_ENABLE_FILTEROPAMP="1" *) (* MFG_GMCREF_SEL="2" *)
EHXPLLL
#(
.CLKI_DIV (params_refclk_div),
.CLKFB_DIV (params_feedback_div),
.FEEDBK_PATH ("CLKOP"),
.OUTDIVIDER_MUXA("DIVA"),
.CLKOP_ENABLE ("ENABLED"),
.CLKOP_DIV (params_output_div),
.CLKOP_CPHASE (params_primary_cphase),
.CLKOP_FPHASE (params_primary_fphase),
.OUTDIVIDER_MUXB("DIVB"),
.CLKOS_ENABLE (out1_hz > 0 ? "ENABLED" : "DISABLED"),
.CLKOS_DIV (params_secondary1_div),
.CLKOS_CPHASE (params_secondary1_cphase),
.CLKOS_FPHASE (params_secondary1_fphase),
.OUTDIVIDER_MUXC("DIVC"),
.CLKOS2_ENABLE(out2_hz > 0 ? "ENABLED" : "DISABLED"),
.CLKOS2_DIV (params_secondary2_div),
.CLKOS2_CPHASE(params_secondary2_cphase),
.CLKOS2_FPHASE(params_secondary2_fphase),
.OUTDIVIDER_MUXD("DIVD"),
.CLKOS3_ENABLE(out3_hz > 0 ? "ENABLED" : "DISABLED"),
.CLKOS3_DIV (params_secondary3_div),
.CLKOS3_CPHASE(params_secondary3_cphase),
.CLKOS3_FPHASE(params_secondary3_fphase),
.INTFB_WAKE ("DISABLED"),
.STDBY_ENABLE (standby_en ? "ENABLED" : "DISABLED"),
.PLLRST_ENA ( reset_en ? "ENABLED" : "DISABLED"),
.DPHASE_SOURCE(dynamic_en ? "ENABLED" : "DISABLED"),
.PLL_LOCK_MODE(0)
)
pll_inst
(
.RST(1'b0),
.STDBY(1'b0),
.CLKI(clk_i),
.CLKOP(CLKOP),
.CLKOS (clk_o[1]),
.CLKOS2(clk_o[2]),
.CLKOS3(clk_o[3]),
.CLKFB(CLKOP),
.CLKINTFB(),
.PHASESEL1(PHASESEL_HW[1]),
.PHASESEL0(PHASESEL_HW[0]),
.PHASEDIR(phasedir),
.PHASESTEP(phasestep),
.PHASELOADREG(phaseloadreg),
.PLLWAKESYNC(1'b0),
.ENCLKOP(1'b0),
.ENCLKOS(1'b0),
.ENCLKOS2(1'b0),
.ENCLKOS3(1'b0),
.LOCK(locked)
);
assign clk_o[0] = CLKOP;
endmodule