FPGA SDR(29)EP4CE6E22 AM/FMステレオラジオ
2020/05/19 追記:q <= adc * -sin に修正しました。
2019/03/06 追記:フィルタの構成を CIC 1/24 + FIR 1/8 に変更しました。
2018/07/26 追記:CICフィルタのパラメータを調整して使用リソースを減らしました。
2018/07/25 追記:ステレオ復調を修正しました。
2018/07/22 追記:AM復調出力の平均値を差し引くことでDC成分を打ち消し、出力コンデンサを省略しました。
ステレオ復調ができたので、AM/FMラジオのステレオ化です。I2Sモジュールの真価を発揮させるためCICフィルタ以降16ビットで処理しています。
FMは、CICフィルタ+FIRフィルタで384kHzに間引いてFM復調し、FIRフィルタで48kHzに間引いてI2S DACへ。
AMは、CICフィルタ+FIRフィルタで384kHzに間引いて、さらにFIRフィルタで48kHzに間引いてAM復調しI2S DACへ。
pll:IP CatalogのALTPLLで73.75MHzを作ります。本当は48kHzの1536倍の73.728MHzにしたかったのですが妥協します。
QsysCore:FPGAラジオ(2)アンテナ、ADC、DACを接続
MyNCO:FPGAラジオ(13)自作NCO
MyCIC:FPGAラジオ(12)自作CICフィルタ
MyMEMFIR:FPGAラジオ(27)メモリベースFIRフィルタ
vectran :IP CatalogのCORDICを追加します。FM復調用のPM復調とAM復調に使います。
MyNCO2X:FPGAラジオ(32)ステレオ復調用NCO
MyAverage:FPGAラジオ(28)ステレオ復調
19kHzのNCOの位相とパイロット信号の位相の合わせるためと、AM復調出力のDC成分を打ち消すために使っています。
MyMEMLPF8:FPGAラジオ(27)メモリベースFIRフィルタ
MyDeEmphasis:FPGAラジオ(31)ディエンファシス
MyI2S:FPGAラジオ(21)PCM5102A I2S DAC
Topモジュールは次のようになります。
`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 wire I2S_SCK, output wire I2S_BCK, output wire I2S_LRCK, output wire I2S_DATA, output reg [7:0] DACA, output reg [7:0] DACB ); localparam CIC_WIDTH = 19; localparam FIR_WIDTH = 16; localparam PI = 17'sb0011_0010_0100_0011_1; // pi = 0011 . 0010 0100 0011 1111 0110 1010 localparam NCO19K_WIDTH = 16; wire clk; // 73.75M wire [31:0] pio0; wire [31:0] pio1; wire fm; wire [3:0] gain; wire [3:0] volume; reg [7:0] uadc_r; wire signed [7:0] adc; wire signed [11:0] sin; wire signed [11:0] cos; reg signed [CIC_WIDTH-1:0] i; reg signed [CIC_WIDTH-1: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 [FIR_WIDTH-1:0] mag_dc; 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 signed [NCO19K_WIDTH-1:0] sin19k; wire signed [NCO19K_WIDTH-1:0] sin38k; wire signed [FIR_WIDTH+NCO19K_WIDTH-1:0] freqsin19k; wire signed [FIR_WIDTH+NCO19K_WIDTH-1:0] freqsin19k_ave; wire signed [FIR_WIDTH+NCO19K_WIDTH-1:0] freqsin19k_fb; wire signed [FIR_WIDTH+NCO19K_WIDTH-1:0] freqsin38k; wire signed [FIR_WIDTH-1:0] freq_LPR; wire signed [FIR_WIDTH-1:0] freq_LMR; wire freq_LPR_valid; wire signed [FIR_WIDTH-1:0] freq_L; wire signed [FIR_WIDTH-1:0] freq_R; wire signed [FIR_WIDTH-1:0] freq_LD; wire signed [FIR_WIDTH-1:0] freq_RD; wire [7:0] daca; wire [7:0] dacb; 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 < 32'd31447896 || 32'd131032901 <= pio1) ? 1 : 0; // (pio1 < 540kHz || 2.25MHz <= pio1) ? FM : AM assign gain = fm ? 3 : 5; // pio0[3:0]; assign volume = pio0[3:0]; always @(posedge clk) begin uadc_r <= ADC; end assign ENCODE = clk; assign adc = (uadc_r[7] == 0) ? uadc_r + 8'h80 : uadc_r - 8'h80; MyNCO #( .OUT_WIDTH(12) ) 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 #( .DATA_WIDTH(CIC_WIDTH) ) cic_inst_i ( .clk (clk), .reset_n (RST_N), .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 #( .DATA_WIDTH(CIC_WIDTH) ) cic_inst_q ( .clk (clk), .reset_n (RST_N), .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) ); MyMEMFIR8 #( .DATA_WIDTH(FIR_WIDTH) ) 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 () ); MyMEMFIR8 #( .DATA_WIDTH(FIR_WIDTH) ) 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 (fm ? ifir : freq_LPR), .y (fm ? qfir : freq_LMR), .q (phase), .r (mag), .en (fm ? ifir_valid : freq_LPR_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; 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; MyDeEmphasis #( .DATA_WIDTH(FIR_WIDTH) ) MyDeEmphasis_L_inst ( .clk (clk), .reset_n (RST_N), .in_data (freq_L), .in_valid (ifir_valid), .out_data (freq_LD), .out_valid () ); MyDeEmphasis #( .DATA_WIDTH(FIR_WIDTH) ) MyDeEmphasis_R_inst ( .clk (clk), .reset_n (RST_N), .in_data (freq_R), .in_valid (ifir_valid), .out_data (freq_RD), .out_valid () ); MyAverage #( .DATA_WIDTH(FIR_WIDTH), .AVERAGE_WIDTH(12), .AVERAGE(4096), .MOVING_AVERAGE_WIDTH(2) ) MyAverage_DC_inst ( // 85ms .clk (clk), .reset_n (RST_N), .in_data (mag), .in_valid (freq_LPR_valid), .out_data (mag_dc), .out_valid () ); MyI2S #( .IN_WIDTH(FIR_WIDTH) ) MyI2S_inst ( .clk (clk), .reset_n (RST_N), .volume (4'b1111 - volume), .in_left (fm ? freq_LD : mag - mag_dc), .in_right (fm ? freq_RD : mag - mag_dc), .in_valid (freq_LPR_valid), .SCK (I2S_SCK), .BCK (I2S_BCK), .LRCK (I2S_LRCK), .DATA (I2S_DATA) ); // assign daca = cos[11 -: 8]; // assign dacb = sin[11 -: 8]; // assign daca = i[CIC_WIDTH-1 -: 8]; // assign dacb = q[CIC_WIDTH-1 -: 8]; // assign daca = icic[CIC_WIDTH-1 -gain -: 8]; // icic_valid // assign dacb = qcic[CIC_WIDTH-1 -gain -: 8]; // qcic_valid // assign daca = ifir[FIR_WIDTH-1 -: 8]; // ifir_valid // assign dacb = qfir[FIR_WIDTH-1 -: 8]; // qfir_valid // assign daca = phase[FIR_WIDTH-1 -: 8]; // ifir_valid // assign dacb = freq[FIR_WIDTH-1 -: 8]; // ifir_valid // assign daca = freq_LPR[FIR_WIDTH-1 -: 8]; // freq_LPR_valid // assign dacb = freq_LMR[FIR_WIDTH-1 -: 8]; // freq_LPR_valid // assign daca = mag[FIR_WIDTH-1 -: 8]; // freq_LPR_valid // assign dacb = mag_dc[FIR_WIDTH-1 -: 8]; // freq_LPR_valid // assign daca = sin19k[NCO19K_WIDTH-1 -: 8]; // ifir_valid // assign dacb = sin38k[NCO19K_WIDTH-1 -: 8]; // ifir_valid // assign daca = freqsin19k[FIR_WIDTH+NCO19K_WIDTH-1-1 -: 8]; // ifir_valid // assign dacb = freqsin38k[FIR_WIDTH+NCO19K_WIDTH-1-1 -: 8]; // ifir_valid // assign daca = freqsin19k_ave[FIR_WIDTH+NCO19K_WIDTH-1-1-5 -: 8]; // freqsin19k_ave_valid // assign dacb = freqsin19k_fb[FIR_WIDTH+NCO19K_WIDTH-1-1-5 -: 8]; // freqsin19k_ave_valid assign daca = i[CIC_WIDTH-1 -: 8]; assign dacb = ifir[FIR_WIDTH-1 -: 8]; always @(posedge clk) begin //if (ifir_valid) DACA <= (daca[7] == 0) ? daca + 8'h80 : daca - 8'h80; if (ifir_valid) DACB <= (dacb[7] == 0) ? dacb + 8'h80 : dacb - 8'h80; end endmodule