Attention
This documentation is a work in progress. Expect to see errors and unfinished things.
ssb_out Source File
1`timescale 1ns / 1ns
2// SSB stands for Single Side Band.
3// https://en.wikipedia.org/wiki/Single-sideband_modulation
4// (Almost) pin compatible with second_if_out,
5// but this one relies on interpolators (afterburner) to up-sample the output
6// data to DDR so it can drive a double-frequency DAC.
7//
8// It nominally produces an I and Q drive output but single-drive usage is possible
9// by simply leaving dac2_out{0,1} floating, letting the synthesizer optimize-away
10// the redundant code.
11//
12// Directly uses the provided LO (cosa,sina), thus output IF will be determined by
13// the LO frequency
14
15module ssb_out (
16 input clk,
17 input [1:0] div_state, // div_state [0] I-Q signal
18 input signed [17:0] drive, // Baseband interleaved I-Q
19 input enable, // Set output on enable else 0
20 input ssb_flip, // Flips sign of dac2_out output pair
21 input [15:0] aftb_coeff, // Coefficient to correct for the linear interpolation between
22 // two consecutive samples; see afterburner.v for details.
23 // Example based on FNAL test:
24 // 1313 MHz LO as timebase, / 16 to get 82.0625 MHz ADC clk
25 // IF is 13 MHz, 16/101 = 0.1584 of ADC clk
26 // aftb_coeff = ceil(32768*0.5*sec(2*pi*16/101/2)) = 18646
27 // local oscillator
28 input signed [17:0] cosa,
29 input signed [17:0] sina,
30 // DDR on both DACs
31 output signed [15:0] dac1_out0, // Nominally in-phase component
32 output signed [15:0] dac1_out1,
33 output signed [15:0] dac2_out0, // Nominally quadrature component
34 output signed [15:0] dac2_out1
35);
36
37wire iq = div_state[0];
38
39// Bring input I and Q to full data rate
40wire signed [16:0] drive_i, drive_q;
41fiq_interp interp(.clk(clk),
42 .a_data(drive[17:2]), .a_gate(1'b1), .a_trig(iq),
43 .i_data(drive_i), .q_data(drive_q));
44
45wire signed [15:0] out1, out2;
46
47// SSB modulation scheme (Hartley modulator) using dot-product for LSB selection:
48// (I, Q) . (cos(wLO*t), sin(wLO*t)) = I*cos(wLO*t) + Q*sin(wLO*t)
49
50// In-phase portion of SSB drive signal
51flevel_set level1(.clk(clk),
52 .cosd(cosa), .sind(sina),
53 .i_data(drive_i), .i_gate(1'b1), .i_trig(1'b1),
54 .q_data(drive_q), .q_gate(1'b1), .q_trig(1'b1),
55 .o_data(out1));
56
57wire signed [15:0] outk1 = enable ? out1 : 0;
58
59wire [15:0] dac1_ob0, dac1_ob1; // offset binary outputs from afterburner
60afterburner afterburner1(.clk(clk),
61 .data({outk1,1'b0}), .coeff(aftb_coeff),
62 .data_out0(dac1_ob0), .data_out1(dac1_ob1));
63
64// Second (optional) dot-product with 90deg-rotated drive IQ to obtain quadrature component
65
66// Quadrature portion of SSB drive signal
67flevel_set level2(.clk(clk),
68 .cosd(cosa), .sind(sina),
69 .i_data(~drive_q), .i_gate(1'b1), .i_trig(1'b1),
70 .q_data(drive_i), .q_gate(1'b1), .q_trig(1'b1),
71 .o_data(out2));
72
73wire signed [15:0] outf2 = ssb_flip ? ~out2 : out2;
74wire signed [15:0] outk2 = enable ? outf2 : 0;
75
76wire [15:0] dac2_ob0, dac2_ob1; // offset binary outputs from afterburner
77afterburner afterburner2(.clk(clk),
78 .data({outk2,1'b0}), .coeff(aftb_coeff),
79 .data_out0(dac2_ob0), .data_out1(dac2_ob1));
80
81// afterburner returns offset-binary DAC words, but this module uses signed
82// (twos-complement) for its output. Thus the conversions below.
83assign dac1_out0 = {~dac1_ob0[15], dac1_ob0[14:0]};
84assign dac1_out1 = {~dac1_ob1[15], dac1_ob1[14:0]};
85assign dac2_out0 = {~dac2_ob0[15], dac2_ob0[14:0]};
86assign dac2_out1 = {~dac2_ob1[15], dac2_ob1[14:0]};
87
88endmodule