FPGA SDR(17)自作IP AM/FMラジオ


2018/07/21 追記:PM-FM変換を修正しました。
2018/07/07 追記:自作CICフィルタ(可変間引き率版)に変更しました。また、EP2C5T144 AM/FMラジオで追加したFET一段の高周波増幅回路をこちらにも追加しましたためFIRフィルタの入力レベルを少し絞っています。

assign gain = fm ? 1 : 4;

 

自作CICフィルタ自作NCOに続いて自作FIRフィルタができたので、ダウンロードケーブル無しで動作するAM/FMラジオを作りました。

 

こちらのFMラジオと同様に、IP CatalogのALTPLLを使って50MHzのクロック入力から70MHzのクロックを作ります。NCOを10MHzに設定すれば80MHz(東京FM)を受信できます。

復調にはこちらのAM/FMラジオと同様にIP CatalogのALTERA_CORDICを使います。Vector Translateを選択するとIQ信号を入力して位相と振幅を出力してくれ、これだけでAM復調とPM復調が済んでしまいます。

`define CYCLE_1SEC 50000000


module SPIbridge
(
	input wire RST_N,
	input wire CLK,
	
	input wire SPI_NSS,
	input wire SPI_SCLK,
	output wire SPI_MISO,
	input wire SPI_MOSI,
	
	output wire [3:0] LED,
	
	input wire [7:0] ADC,
	output wire ENCODE,
	
	output reg [9:0] DAC,
	output wire DACCLK	
);


	localparam CIC_WIDTH = 19;	
	localparam FIR_WIDTH = 10;	
	localparam PI = 11'sb0011_0010_010; // pi = 0011 . 0010 0100 0011 1111 0110 1010


	wire clk;

	wire [31:0] pio0;
	wire [31:0] pio1;
	
	wire fm;
	wire [10:0] rate;
	wire [3:0] gain;
	
	reg [9:0] uadc_r;
	wire signed [9:0] adc;

	wire signed [9:0] sin;
	wire signed [9:0] cos;
	
	reg signed [18:0] i;
	reg signed [18:0] q;

	wire signed [CIC_WIDTH-1:0] icic;
	wire icic_valid;
	wire signed [CIC_WIDTH-1:0] qcic;
	wire qcic_valid;

	wire signed [FIR_WIDTH-1:0] ifir;
	wire ifir_valid;
	wire signed [FIR_WIDTH-1:0] qfir;
	wire qfir_valid;

	wire [FIR_WIDTH-1:0] mag;
	wire signed [FIR_WIDTH-1:0] phase;
	reg signed [FIR_WIDTH-1:0] phase_r;
	wire signed [FIR_WIDTH:0] phase_diff;
	reg signed [FIR_WIDTH:0] freq;

	wire [9:0] dac;
	reg [2:0] dacclk;

	
	pll	pll_inst (
		.inclk0 (CLK),
		.c0 (clk)
	);	

	QsysCore QsysCore_inst (
		.clk_clk                                                                                         (clk),
		.reset_reset_n                                                                                   (RST_N),
		.spi_slave_to_avalon_mm_master_bridge_0_export_0_mosi_to_the_spislave_inst_for_spichain          (SPI_MOSI),
		.spi_slave_to_avalon_mm_master_bridge_0_export_0_nss_to_the_spislave_inst_for_spichain           (SPI_NSS),
		.spi_slave_to_avalon_mm_master_bridge_0_export_0_miso_to_and_from_the_spislave_inst_for_spichain (SPI_MISO),
		.spi_slave_to_avalon_mm_master_bridge_0_export_0_sclk_to_the_spislave_inst_for_spichain          (SPI_SCLK),
		.pio_0_external_connection_export                                                                (pio0),
		.pio_1_external_connection_export                                                                (pio1)
	);

	assign LED = ~pio0[3:0];
	
	assign fm = (pio1 == 0 || pio1 >= 32'd368140054) ? 1 : 0; // (pio1 >= 6MHz) ? FM : AM
	assign rate = fm ? 64 : 1024; // fm ? 1094kSPS : 68kSPS
	assign gain = fm ? 1 : 4; // pio0[3:0];
	
	always @(posedge clk) begin
		uadc_r <= { ADC, 2'b00 };
	end
	assign ENCODE = clk;
	assign adc = (uadc_r[9] == 0) ? uadc_r + 10'h200 : uadc_r - 10'h200;

	MyNCO nco_inst (
		.clk       (clk),
		.reset_n   (RST_N),
		.clken     (1'b1),
		.phi_inc_i (pio1),
		.fsin_o    (sin),
		.fcos_o    (cos),
		.out_valid ()
		);

	always @(posedge clk) begin
		i <= adc * cos;
		q <= adc * sin;
	end

	MyCIC cic_inst_i (
		.clk       (clk),
		.reset_n   (RST_N),
		.rate      (rate),
		.in_error  (2'b00),
		.in_valid  (1'b1),
		.in_ready  (),
		.in_data   (i),
		.out_data  (icic),
		.out_error (),
		.out_valid (icic_valid),
		.out_ready (1'b1)
	);

	MyCIC cic_inst_q (
		.clk       (clk),
		.reset_n   (RST_N),
		.rate      (rate),
		.in_error  (2'b00),
		.in_valid  (1'b1),
		.in_ready  (),
		.in_data   (q),
		.out_data  (qcic),
		.out_error (),
		.out_valid (qcic_valid),
		.out_ready (1'b1)
	);
	
	MyFIR fir_inst_i (
		.clk       (clk),
		.reset_n   (RST_N),
		.ast_sink_data (icic[CIC_WIDTH-1 -gain -: FIR_WIDTH]),
		.ast_sink_valid (icic_valid),
		.ast_sink_error (2'b00),
		.ast_source_data (ifir),
		.ast_source_valid (ifir_valid),
		.ast_source_error ()
	);
 
	MyFIR fir_inst_q (
		.clk       (clk),
		.reset_n   (RST_N),
		.ast_sink_data (qcic[CIC_WIDTH-1 -gain -: FIR_WIDTH]),
		.ast_sink_valid (qcic_valid),
		.ast_sink_error (2'b00),
		.ast_source_data (qfir),
		.ast_source_valid (qfir_valid),
		.ast_source_error ()
	);
 
	vectran vectran_inst (
		.clk    (clk),
		.areset (~RST_N),
		.x      (ifir),
		.y      (qfir),
		.q      (phase),
		.r      (mag),
		.en     (ifir_valid)
	);

	always @(posedge clk) begin
		if (ifir_valid) begin
			phase_r <= phase;
			
			if (phase_diff > PI) begin
				freq <= phase_diff - (PI <<< 1);
			end
			else if (phase_diff < -PI) begin
				freq <= phase_diff + (PI <<< 1);
			end
			else begin
				freq <= phase_diff;
			end
		end
	end
	assign phase_diff = phase - phase_r;

	assign dac = freq[FIR_WIDTH-1 -: 10];
	always @(posedge clk) begin
		if (ifir_valid) begin
			DAC <= fm ? ((dac[9] == 0) ? dac + 10'h200 : dac - 10'h200) : mag;
		end
		
		dacclk = { dacclk[1:0], ifir_valid };
	end
	assign DACCLK = dacclk[2] | dacclk[1] | dacclk[0] | ifir_valid;


endmodule

各部のビット幅を見直したところ、だいぶリソースを節約できました。