FPGA SDR(28)ステレオ復調


2019/03/06 追記:0Hz~19kHzと19kHz~38kHzの振幅を揃えました。
2018/07/25 追記:フィードバック量を調整して音が安定しました。題名を戻しました。
2018/07/25 追記:音が左右にフラフラしています。題名を「ステレオ復調(未完成)」に変えました。
2018/07/22 追記:MyAVERAGEを使うように変更しました。

 

雑誌の記事を何度も読み返してやっとステレオ復調ができました。NCOの位相とパイロット信号の位相の合わせ方が今一つ、二つ、三つほど理解できていませんが、聴いている分ではステレオ復調できているようです。

ディジタル・デザイン・テクノロジ NO.1 2009・SPRING「ディジタルFMステレオ・チューナの制作」
インターフェース 2015年7月号「オール・ソフトウェア無線」

まず19kHzと38kHzのSin信号を出力するMyNCO2X:FPGAラジオ(32)ステレオ復調用NCOを追加して、NCOの19kHz出力とFM復調出力との積をMyLOOPで1msごとの平均値にしてオシロで見てみます。位相が合っていないのでゼロに収束していません。青い横線のレベルがゼロです。

 

	MyNCO2X #(
		.OUT_WIDTH(NCO19K_WIDTH)
	) MyNCO2X_inst (
		.clk       (clk),
		.reset_n   (RST_N),
		.clken     (ifir_valid),
		.phi_inc_i 32'sd212448009,
		.fsin_o    (sin19k),
		.fsin2x_o    (sin38k),
		.out_valid ()
		);
	assign freqsin19k = $signed(freq[FIR_WIDTH-1:0]) * sin19k;
	assign freqsin38k = $signed(freq[FIR_WIDTH-1:0]) * sin38k;

	MyAverage #(
		.DATA_WIDTH(FIR_WIDTH+NCO19K_WIDTH),
		.AVERAGE_WIDTH(9),
		.AVERAGE(384),
		.MOVING_AVERAGE_WIDTH(2)
	) MyAverage_LOOP_inst (
		.clk (clk),
		.reset_n (RST_N),
		.in_data (freqsin19k),
		.in_valid (ifir_valid),
		.out_data (freqsin19k_ave),
		.out_valid (freqsin19k_ave_valid)
	);
	assign freqsin19k_fb = freqsin19k_ave_valid ? freqsin19k_ave : 0;

MyAverageの中身です。

`timescale 1ns/1ns


module MyAverage
#(
	parameter DATA_WIDTH = 10,
	parameter AVERAGE_WIDTH = 9,
	parameter AVERAGE = 512,
	parameter MOVING_AVERAGE_WIDTH = 2
)
(
	input wire clk,
	input wire reset_n,
	
	input wire signed [DATA_WIDTH-1:0] in_data,
	input wire in_valid,

	output wire signed [DATA_WIDTH-1:0] out_data,
	output wire out_valid
);


	localparam SUM_WIDTH = AVERAGE_WIDTH + DATA_WIDTH;


	reg [AVERAGE_WIDTH-1:0] cnt;
	
	reg signed [SUM_WIDTH-1:0] sum;
	reg signed [MOVING_AVERAGE_WIDTH+SUM_WIDTH-1:0] ave;

	
	always @(posedge clk)
	begin
		if (~reset_n) begin			
			cnt <= 0;
					
			sum <= 0;
						
			ave <= 0;
		end
		else begin
			
			if (in_valid) begin
				if (cnt == AVERAGE-1) begin
					cnt <= 0;
					
					sum <= 0;
					
					ave <= ave - (ave >>> MOVING_AVERAGE_WIDTH) + (sum >>> AVERAGE_WIDTH);
				end
				else begin				
					cnt <= cnt + 1;
					
					sum <= sum + in_data;
				end
			end
			
		end
	end
	assign out_data = ave >>> MOVING_AVERAGE_WIDTH;
	assign out_valid = (cnt == AVERAGE-1) ? 1'b1 : 1'b0;


endmodule

MyAVERAGEの出力をNCOの入力にフィードバックして、MyAVERAGEの出力をゼロに収束させます。ここがまだ理解できていません。

 

	MyNCO2X #(
		.OUT_WIDTH(NCO19K_WIDTH)
	) MyNCO2X_inst (
		.clk       (clk),
		.reset_n   (RST_N),
		.clken     (ifir_valid),
		.phi_inc_i (32'sd212448009 - freqsin19k_fb),
		.fsin_o    (sin19k),
		.fsin2x_o    (sin38k),
		.out_valid ()
		);
	assign freqsin19k = $signed(freq[FIR_WIDTH-1:0]) * sin19k;
	assign freqsin38k = $signed(freq[FIR_WIDTH-1:0]) * sin38k;

	MyAverage #(
		.DATA_WIDTH(FIR_WIDTH+NCO19K_WIDTH),
		.AVERAGE_WIDTH(9),
		.AVERAGE(384),
		.MOVING_AVERAGE_WIDTH(2)
	) MyAverage_LOOP_inst (
		.clk (clk),
		.reset_n (RST_N),
		.in_data (freqsin19k),
		.in_valid (ifir_valid),
		.out_data (freqsin19k_ave),
		.out_valid (freqsin19k_ave_valid)
	);
	assign freqsin19k_fb = freqsin19k_ave_valid ? freqsin19k_ave : 0;

NCOの38kHz出力を使って、FM復調出力内の38kHzで変調されたL-R成分を同期検波して取り出します。FM復調出力の0~15kHzに含まれるL+R成分と加算・減算することでL信号・R信号に戻せます。

	MyNCO2X #(
		.OUT_WIDTH(NCO19K_WIDTH)
	) MyNCO2X_inst (
		.clk       (clk),
		.reset_n   (RST_N),
		.clken     (ifir_valid),
		.phi_inc_i (32'sd212448009 - freqsin19k_fb),
		.fsin_o    (sin19k),
		.fsin2x_o    (sin38k),
		.out_valid ()
		);
	assign freqsin19k = $signed(freq[FIR_WIDTH-1:0]) * sin19k;
	assign freqsin38k = $signed(freq[FIR_WIDTH-1:0]) * sin38k;

	MyAverage #(
		.DATA_WIDTH(FIR_WIDTH+NCO19K_WIDTH),
		.AVERAGE_WIDTH(9),
		.AVERAGE(384),
		.MOVING_AVERAGE_WIDTH(2)
	) MyAverage_LOOP_inst (
		.clk (clk),
		.reset_n (RST_N),
		.in_data (freqsin19k),
		.in_valid (ifir_valid),
		.out_data (freqsin19k_ave),
		.out_valid (freqsin19k_ave_valid)
	);
	assign freqsin19k_fb = freqsin19k_ave_valid ? freqsin19k_ave : 0;

	MyMEMLPF8 #(
		.DATA_WIDTH(FIR_WIDTH)
	) MyLPF8_LPR_inst (
		.clk       (clk),
		.reset_n   (RST_N),
		.ast_sink_data (fm ? freq[FIR_WIDTH-1:0] : ifir),
		.ast_sink_valid (ifir_valid),
		.ast_sink_error (2'b00),
		.ast_source_data (freq_LPR),
		.ast_source_valid (freq_LPR_valid),
		.ast_source_error ()
	);

	MyMEMLPF8 #(
		.DATA_WIDTH(FIR_WIDTH)
	) MyLPF8_LMR_inst (
		.clk       (clk),
		.reset_n   (RST_N),
		.ast_sink_data (fm ? freqsin38k[FIR_WIDTH+NCO19K_WIDTH-1-1 -: FIR_WIDTH] : qfir),
		.ast_sink_valid (ifir_valid),
		.ast_sink_error (2'b00),
		.ast_source_data (freq_LMR),
		.ast_source_valid (),
		.ast_source_error ()
	);
	
	assign freq_L = freq_LPR + freq_LMR;
	assign freq_R = freq_LPR - freq_LMR;

青:L-R、黄:L+R

青:R、黄:L