474Tang Nano 9kでS/PDIFミキサーを作る(ver2)
概要
以前投稿した、
TangNanoでS/PDIFミキサー(簡易版)を作る
を改良してたものです。
前回の投稿内容の問題点として記載した、「IP Coreの仕様に伴う音質劣化」を対処すべく、外部にCDR(clock data recovery)機能を持つDIX9211を使用して、マスタークロックとする入力についてはビットパーフェクト(データの変化/欠落がないこと)なミキシングをできるようにしたものです。
あと、以下を改良しています(結構でかい)。
・入力系統を4つに増設
・チャンネルステータスの表示と制御
Tang Nano 9kの開発環境の使い方の概要は、Tang Nano 9KでLチカやTang Nano 9KでHDMI出力の記事を参照してください。
制作例
必要パーツ
基本的には前回の作成物と、DIX9211(関連記事)関連の部品です。
あとは、SH1122という256x64のOLEDディスプレイ(アリエク産)を使用しています。
ケースは適当に3Dプリンタで作りました。蓋はまだ未作成です。
結線
DIX9211ボードの回路図は関連記事を参照してください。
最初全部3.3V系で動かそうとしたのですが、どうもTang Nanoの3.3V出力を使うとPLLが安定しない現象がでたので、SPDIF TXとOLEDは5V駆動としました。
また、Tang Nano 9kには SPI LCD用のFPCコネクタが付いていますので、そちらからOLEDに接続することもできるはずです。ただし、RSTのpin番号はその場合は47pinに変更する必要があります。
FPGAソース
verilogソースはすべて一つに結合しています
top.v
`define PCM_BIT 16
module top(
input wire RESETn, //pin 4 S1
input wire SYSCLK, //pin 52 27MHz onboard
input wire SCK, //pin 35 256fs CDR=12.288MHz(48KHz x 256)
input wire I_SPDIF1, //pin 53
input wire I_SPDIF2, //pin 54
input wire I_SPDIF3, //pin 55
input wire I_SPDIF4, //pin 56
output wire SEL_O, //pin 34
output wire O_SPDIF, //pin 57
output wire PLL_CLK, //pin 86
output wire OLED_SCL,// pin 76
output wire OLED_SDA,// pin 77
output wire OLED_DC, // pin 49
output wire OLED_CS, // pin 48
output wire OLED_RST,// pin 47/(63)
output wire [5:0]LED // pin 16,15,14,13,11,10
);
wire gresetn;
wire pll_locked;
wire pllclk; //49.152MHz=96kx512
wire tx_clk; //3.072MHz=12.288MHz/4=48k x 64
reg pll_reset;
assign gresetn = RESETn & pll_locked;
assign PLL_CLK=pllclk;
assign SEL_O=I_SPDIF1;
rPLL #(
.FCLKIN (12.288),
.IDIV_SEL (1-1),
.FBDIV_SEL (4-1),
.ODIV_SEL (16),
.DEVICE ("GW1NR-9C")
) pll (
.CLKIN (SCK),
.CLKFB (1'b0),
.RESET (~RESETn || pll_reset),
.RESET_P (1'b0),
.FBDSEL (6'b0),
.IDSEL (6'b0),
.ODSEL (6'b0),
.DUTYDA (4'b0),
.PSDA (4'b0),
.FDLY (4'b0),
.CLKOUT (pllclk),
.LOCK (pll_locked),
.CLKOUTP (),
.CLKOUTD (),
.CLKOUTD3 ()
);
//PLL lockが外れた後復帰しないことがあるのでリセットする
reg [23:0] pll_chk;//16777216カウント。27MHzで計測するので621ms
reg [1:0] d_locked;//非同期吸収
always @(posedge SYSCLK)
d_locked<={d_locked[0],pll_locked};
always @(posedge SYSCLK)
if (d_locked[1]==1'b1) pll_chk<=0;
else pll_chk<=pll_chk+1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) pll_reset<=1'b0;
else if (pll_chk==24'hffffff)pll_reset<=~pll_reset;
//クロック分周
reg [1:0] clkdiv;
always @(negedge gresetn or posedge SCK)
if (gresetn==1'b0) clkdiv=0;
else clkdiv<=clkdiv+2'b01;
assign tx_clk=clkdiv[1];
wire [`PCM_BIT+1:0] rx1_ch0;
wire [`PCM_BIT+1:0] rx1_ch1;
wire [`PCM_BIT+1:0] rx2_ch0;
wire [`PCM_BIT+1:0] rx2_ch1;
wire [`PCM_BIT+1:0] rx3_ch0;
wire [`PCM_BIT+1:0] rx3_ch1;
wire [`PCM_BIT+1:0] rx4_ch0;
wire [`PCM_BIT+1:0] rx4_ch1;
wire [3:0] chan_status_bit;
wire [3:0] block_start_flag;
wire [3:0] sub_frame0_flag;
wire [3:0] sub_frame1_flag;
wire [3:0] lock_flag;
wire [5:0] rx1_sample_freq;
wire cp,cpl;
receiver receiver(
.RESETn(gresetn),
.PLLCLK(pllclk),
.I_SPDIF1(I_SPDIF1),
.I_SPDIF2(I_SPDIF2),
.I_SPDIF3(I_SPDIF3),
.I_SPDIF4(I_SPDIF4),
.rx1_ch0(rx1_ch0),
.rx1_ch1(rx1_ch1),
.rx2_ch0(rx2_ch0),
.rx2_ch1(rx2_ch1),
.rx3_ch0(rx3_ch0),
.rx3_ch1(rx3_ch1),
.rx4_ch0(rx4_ch0),
.rx4_ch1(rx4_ch1),
.chan_status_bit(chan_status_bit),
.block_start_flag(block_start_flag),
.sub_frame0_flag(sub_frame0_flag),
.sub_frame1_flag(sub_frame1_flag),
.lock_flag(lock_flag),
.rx1_sample_freq(rx1_sample_freq),
.rx2_sample_freq(),
.rx3_sample_freq(),
.rx4_sample_freq(),
.cp(cp),
.cpl(cpl)
);
chstatus_disp chstatus_disp(
.RESETn(RESETn),
.SYSCLK(SYSCLK),
.PLLCLK(pllclk),
.block_start_flag(block_start_flag[0]),
.lock_flag(lock_flag[0]),
.sub_frame0_flag(sub_frame0_flag[0]),
.sub_frame1_flag(sub_frame1_flag[0]),
.chan_status_bit(chan_status_bit[0]),
.OLED_SCL(OLED_SCL),
.OLED_SDA(OLED_SDA),
.OLED_DC(OLED_DC),
.OLED_CS(OLED_CS),
.OLED_RST(OLED_RST)
);
//ミキシング
wire O_Spdif_tx_data;
wire [`PCM_BIT+1:0] mix_ch0;
wire [`PCM_BIT+1:0] mix_ch1;
assign mix_ch0=rx1_ch0+rx2_ch0+rx3_ch0+rx4_ch0;
assign mix_ch1=rx1_ch1+rx2_ch1+rx3_ch1+rx4_ch1;
reg [`PCM_BIT-1:0] r_mix_ch0;
always @(posedge tx_clk)
if (TX_O_sub_frame0_flag==1'b1) begin
if (mix_ch0[`PCM_BIT+1]==1'b1 && mix_ch0[`PCM_BIT:`PCM_BIT-1]!=2'b11) r_mix_ch0<= 1'b1<<(`PCM_BIT-1); //負方向丸め込み
else if (mix_ch0[`PCM_BIT+1]==1'b0 && mix_ch0[`PCM_BIT:`PCM_BIT-1]!=2'b00) r_mix_ch0<=(1'b1<<(`PCM_BIT-1))-1; //正方向丸め込み
else r_mix_ch0<=mix_ch0[`PCM_BIT-1:0];
end
reg [`PCM_BIT-1:0] r_mix_ch1;
always @(posedge tx_clk)
if (TX_O_sub_frame0_flag==1'b1) begin
if (mix_ch1[`PCM_BIT+1]==1'b1 && mix_ch1[`PCM_BIT:`PCM_BIT-1]!=2'b11) r_mix_ch1<= 1'b1<<(`PCM_BIT-1); //負方向丸め込み
else if (mix_ch1[`PCM_BIT+1]==1'b0 && mix_ch1[`PCM_BIT:`PCM_BIT-1]!=2'b00) r_mix_ch1<=(1'b1<<(`PCM_BIT-1))-1; //正方向丸め込み
else r_mix_ch1<=mix_ch1[`PCM_BIT-1:0];
end
wire TX_O_sub_frame0_flag;
wire TX_O_sub_frame1_flag;
wire TX_O_block_start_flag;
//チャンネルステータス
reg ch;
always @(posedge tx_clk)
if (TX_O_sub_frame0_flag==1'b1) ch<=1'b1;
else if (TX_O_sub_frame1_flag==1'b1) ch<=1'b0;
reg [7:0] blk_cnt;
always @(posedge tx_clk)
if (TX_O_block_start_flag==1'b1) blk_cnt<=0;
else if(TX_O_sub_frame0_flag==1'b1) blk_cnt<=blk_cnt+1;
reg TX_I_chan_status_bit;
always @(posedge tx_clk)
if (blk_cnt== 2)TX_I_chan_status_bit<=cp; //cp bit
else if (blk_cnt== 9)TX_I_chan_status_bit<=1'b1; //category code(Mixer)
else if (blk_cnt== 12)TX_I_chan_status_bit<=1'b1; //category code
else if (blk_cnt== 15)TX_I_chan_status_bit<=cpl; //category code L bit
else if (blk_cnt== 20)TX_I_chan_status_bit<=ch; //channel (L)
else if (blk_cnt== 21)TX_I_chan_status_bit<=~ch; //channel (R)
else if (blk_cnt== 24)TX_I_chan_status_bit<=rx1_sample_freq[0]; //sample freq
else if (blk_cnt== 25)TX_I_chan_status_bit<=rx1_sample_freq[1]; //sample freq
else if (blk_cnt== 26)TX_I_chan_status_bit<=rx1_sample_freq[2]; //sample freq
else if (blk_cnt== 27)TX_I_chan_status_bit<=rx1_sample_freq[3]; //sample freq
else if (blk_cnt== 29)TX_I_chan_status_bit<=1'b1; //Clock accuary Level III
else if (blk_cnt== 33)TX_I_chan_status_bit<=rx1_sample_freq[4]; //sample freq
else if (blk_cnt== 34)TX_I_chan_status_bit<=rx1_sample_freq[5]; //sample freq
else if (blk_cnt== 36)TX_I_chan_status_bit<=1'b1; //word length 16bit
else TX_I_chan_status_bit<=1'b0;
SPDIF_TX_Top tx(
.I_clk(tx_clk), //input I_clk
.I_rst_n(gresetn), //input I_rst_n
.I_audio_d((ch==1'b1)?r_mix_ch0:r_mix_ch1), //input [15:0] I_audio_d
.I_validity_bit(1'b0), //input I_validity_bit
.I_user_bit(1'b0), //input I_user_bit
.I_chan_status_bit(TX_I_chan_status_bit), //input I_chan_status_bit
.O_audio_d_req(), //output O_audio_d_req
.O_validity_bit_req(), //output O_validity_bit_req
.O_user_bit_req(), //output O_user_bit_req
.O_chan_status_bit_req(), //output O_chan_status_bit_req
.O_block_start_flag(TX_O_block_start_flag), //output O_block_start_flag
.O_sub_frame0_flag(TX_O_sub_frame0_flag), //output O_sub_frame0_flag
.O_sub_frame1_flag(TX_O_sub_frame1_flag), //output O_sub_frame1_flag
.O_Spdif_tx_data(O_Spdif_tx_data) //output O_Spdif_tx_data
);
assign O_SPDIF=O_Spdif_tx_data;
assign LED[0]=~pll_locked;
assign LED[1]=1'b1;
assign LED[2]=~lock_flag[3];
assign LED[3]=~lock_flag[2];
assign LED[4]=~lock_flag[1];
assign LED[5]=~lock_flag[0];
endmodule
module chstatus_disp(
input wire RESETn,
input wire SYSCLK, //27MHz
input wire PLLCLK, //spdif rx clk
input wire block_start_flag,
input wire lock_flag,
input wire sub_frame0_flag,
input wire sub_frame1_flag,
input wire chan_status_bit,
output OLED_SCL,
output OLED_SDA,
output OLED_DC,
output OLED_CS,
output OLED_RST
);
reg [7:0]blk_cnt;
always @(posedge PLLCLK)
if (block_start_flag==1'b1) blk_cnt<=0;
else if (sub_frame1_flag==1'b1) blk_cnt<=blk_cnt+1;
wire [8:0] adb;
wire doutb;
reg [5:0] bit_cnt;
reg ch;
reg [1:0]x_cnt;
reg [2:0]y_cnt;
wire [7:0] di;
wire [12:0] vram_a;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) x_cnt<=0;
else x_cnt<=x_cnt+1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) y_cnt<=0;
else if (x_cnt==3)y_cnt<=y_cnt+1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) bit_cnt<=0;
else if (x_cnt==3 && y_cnt==7) bit_cnt<=bit_cnt+1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) ch<=1'b0;
else if (x_cnt==3 && y_cnt==7 && bit_cnt==63) ch<=~ch;
wire [15:0]char;
assign char=(y_cnt==0)?16'b00000000_00000000:
(y_cnt==1)?16'b11111110_00010000:
(y_cnt==2)?16'b10000010_00010000:
(y_cnt==3)?16'b10000010_00010000:
(y_cnt==4)?16'b10000010_00010000:
(y_cnt==5)?16'b10000010_00010000:
(y_cnt==6)?16'b10000010_00010000:
16'b11111110_00010000;
wire [7:0]char2;
assign char2=(doutb==1'b0)?char[15:8]:char[7:0];
assign di=(lock_flag==1'b0)?8'h11:
(x_cnt==0)?{char2[7],3'b000,char2[6],3'b000}:
(x_cnt==1)?{char2[5],3'b000,char2[4],3'b000}:
(x_cnt==2)?{char2[3],3'b000,char2[2],3'b000}:
{char2[1],3'b000,char2[0],3'b000};
assign vram_a[12]=ch;
assign vram_a[11]=1'b0;
assign vram_a[10]=bit_cnt[5];
assign vram_a[9:7]=y_cnt;
assign vram_a[6:2]=bit_cnt[4:0];
assign vram_a[1:0]=x_cnt;
oled oled(
.RESETn(RESETn),
.SYSCLK(SYSCLK),
.vram_clk(PLLCLK),
.vram_a(vram_a),
.vram_di(di),
.vram_we(1'b1),
.vram_do(),
.OLED_SCL(OLED_SCL),
.OLED_SDA(OLED_SDA),
.OLED_DC(OLED_DC),
.OLED_CS(OLED_CS),
.OLED_RST(OLED_RST)
);
dpram_512x1 dpram(
.reseta(~RESETn), //input reseta
.clka(PLLCLK), //input clka
.cea(1'b1), //input cea
.ocea(1'b1), //input ocea
.ada({sub_frame1_flag,blk_cnt}), //input [8:0] ada
.wrea(sub_frame0_flag | sub_frame1_flag), //input wrea
.dina(chan_status_bit), //input [0:0] dina
.douta(), //output [0:0] douta
.resetb(~RESETn), //input resetb
.clkb(PLLCLK), //input clkb
.ceb(1'b1), //input ceb
.oceb(1'b0), //input oceb
.wreb(1'b0), //input wreb
.dinb(1'b0), //input [0:0] dinb
.adb({ch,2'b00,bit_cnt}), //input [8:0] adb
.doutb(doutb) //output [0:0] doutb
);
endmodule
module oled(
input wire RESETn, //S1 pin 4
input wire SYSCLK, //27MHz onboard pin 52
input wire vram_clk,
input wire [12:0] vram_a,
input wire [7:0] vram_di,
output wire [7:0] vram_do,
input wire vram_we, //active high
output OLED_SCL,// pin 76 MAX 2MHz ,min pulse 200ns
output OLED_SDA,// pin 77
output OLED_DC, // pin 49
output OLED_CS, // pin 48
output OLED_RST// pin 47/(63)
);
reg [3:0] state;
reg we;
reg i_dc;
reg [7:0] di;
reg [12:0]adb;
wire [7:0]doutb;
wire ack;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) state<=1'b0;
else if (state==0) state<=1;
else if (state==1) state<=2;
else if (state==2 && ack==1'b1) state<=3;
else if (state==3 && ack==1'b1) state<=4;
else if (state==4 && ack==1'b1) state<=5;
else if (state==5 && ack==1'b1) state<=6;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) we<=1'b0;
else if (state==1) we<=1'b1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) i_dc<=1'b0;
else if (state==6 && ack==1'b1) i_dc<=1'b1;
always @(posedge SYSCLK)
if (state==1) di<=8'haf;
else if (state==2) di<=8'h10;
else if (state==3 && ack==1'b1) di<=8'h00;
else if (state==4 && ack==1'b1) di<=8'hb0;
else if (state==5 && ack==1'b1) di<=8'h00;
else if (state==6 && ack==1'b1) di<=doutb;
spi spi(
.RESETn(RESETn),
.SYSCLK(SYSCLK),
.DI(di),
.DC(i_dc),
.WE(we),
.ACK(ack),
.SCL(OLED_SCL),
.SDA(OLED_SDA),
.O_DC(OLED_DC)
);
always @(posedge SYSCLK)
if (state==5 && ack==1'b1) adb<=0;
else if (ack==1'b1) adb<=adb+1;
dpram_8192x8 vram(
.reseta(~RESETn), //input reseta
.clka(vram_clk), //input clka
.cea(1'b1), //clock enable signal, active-high
.ocea(1'b0), //invalid
.ada(vram_a), //input [12:0] ada
.douta(vram_do), //output [7:0] douta
.wrea(vram_we), //write enable input active high
.dina(vram_di), //input [7:0] dina
.resetb(~RESETn), //input resetb
.clkb(SYSCLK), //input clkb
.ceb(1'b1), //input ceb
.oceb(1'b0), //input oceb
.adb(adb), //input [12:0] adb
.doutb(doutb), //output [7:0] doutb
.wreb(1'b0), //input wreb
.dinb(8'h00) //input [7:0] dinb
);
assign OLED_CS=1'b0;
//assign OLED_DC=1'b0;
assign OLED_RST=RESETn;
endmodule
module spi(
input wire RESETn, //S1 pin 4
input wire SYSCLK, //27MHz onboard pin 52
input wire [7:0]DI,
input wire DC,
input wire WE,
output reg ACK,
output reg SCL,
output wire SDA,
output reg O_DC
);
reg [2:0]cnt1; //bit clock
reg [3:0]cnt2; //state
reg [7:0]d_lat;
reg dc_lat;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) ACK<=1'b0;
else if (cnt1==0 && cnt2 == 0 && WE==1'b1) ACK<=1'b1;
else ACK<=1'b0;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) cnt1<=0;
else if (cnt1 != 0) cnt1<=cnt1+1;
else if (cnt1 == 0 && cnt2 != 0) cnt1<=1;
else if (cnt1 == 0 && WE == 1'b1) cnt1<=1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) cnt2<=0;
else if (cnt1==0 && cnt2 != 0)cnt2<=cnt2+1;
else if (cnt1==0 && cnt2 == 0 && WE==1'b1) cnt2<=1;
always @(negedge RESETn or posedge SYSCLK)
if (RESETn==1'b0) SCL<=1'b1;
else if (cnt1==0 && cnt2 == 0 && WE==1'b1) SCL<=1'b0;
else if (cnt1==0 && cnt2 != 0) SCL<=cnt2[0];
always @(posedge SYSCLK)
if (cnt1==0 && cnt2 == 0 && WE==1'b1) d_lat<=DI;
else if (cnt1==0 && cnt2[0]==1'b0) d_lat<={d_lat[6:0],1'b0};
always @(posedge SYSCLK)
if (cnt1==0 && cnt2 == 0 && WE==1'b1) dc_lat<=DC;
always @(posedge SYSCLK)
if (cnt1==0 && cnt2[0]==1'b1) O_DC<=dc_lat;
assign SDA=d_lat[7];
endmodule
module receiver(
input wire RESETn,
input wire PLLCLK,
input wire I_SPDIF1,
input wire I_SPDIF2,
input wire I_SPDIF3,
input wire I_SPDIF4,
output wire [`PCM_BIT+1:0] rx1_ch0,
output wire [`PCM_BIT+1:0] rx1_ch1,
output wire [`PCM_BIT+1:0] rx2_ch0,
output wire [`PCM_BIT+1:0] rx2_ch1,
output wire [`PCM_BIT+1:0] rx3_ch0,
output wire [`PCM_BIT+1:0] rx3_ch1,
output wire [`PCM_BIT+1:0] rx4_ch0,
output wire [`PCM_BIT+1:0] rx4_ch1,
output wire [3:0] chan_status_bit,
output wire [3:0] block_start_flag,
output wire [3:0] sub_frame0_flag,
output wire [3:0] sub_frame1_flag,
output wire [3:0] lock_flag,
output wire [5:0] rx1_sample_freq,
output wire [5:0] rx2_sample_freq,
output wire [5:0] rx3_sample_freq,
output wire [5:0] rx4_sample_freq,
output wire cp,
output wire cpl
);
wire rx1_cp,rx1_cpl;
wire rx2_cp,rx2_cpl;
wire rx3_cp,rx3_cpl;
wire rx4_cp,rx4_cpl;
//SPDIF1 受信
recv rx1(
.RESETn(RESETn),
.RXCLK(PLLCLK),
.I_SPDIF(I_SPDIF1),
.ch0_data(rx1_ch0),
.ch1_data(rx1_ch1),
.chan_status_bit(chan_status_bit[0]),
.block_start_flag(block_start_flag[0]),
.sub_frame0_flag(sub_frame0_flag[0]),
.sub_frame1_flag(sub_frame1_flag[0]),
.lock_flag(lock_flag[0]),
.sample_freq(rx1_sample_freq),
.cp(rx1_cp),
.cpl(rx1_cpl)
);
//SPDIF2受信
recv rx2(
.RESETn(RESETn),
.RXCLK(PLLCLK),
.I_SPDIF(I_SPDIF2),
.ch0_data(rx2_ch0),
.ch1_data(rx2_ch1),
.chan_status_bit(chan_status_bit[1]),
.block_start_flag(block_start_flag[1]),
.sub_frame0_flag(sub_frame0_flag[1]),
.sub_frame1_flag(sub_frame1_flag[1]),
.lock_flag(lock_flag[1]),
.sample_freq(rx2_sample_freq),
.cp(rx2_cp),
.cpl(rx2_cpl)
);
//SPDIF3受信
recv rx3(
.RESETn(RESETn),
.RXCLK(PLLCLK),
.I_SPDIF(I_SPDIF3),
.ch0_data(rx3_ch0),
.ch1_data(rx3_ch1),
.chan_status_bit(chan_status_bit[2]),
.block_start_flag(block_start_flag[2]),
.sub_frame0_flag(sub_frame0_flag[2]),
.sub_frame1_flag(sub_frame1_flag[2]),
.lock_flag(lock_flag[2]),
.sample_freq(rx3_sample_freq),
.cp(rx3_cp),
.cpl(rx3_cpl)
);
//SPDIF4受信
recv rx4(
.RESETn(RESETn),
.RXCLK(PLLCLK),
.I_SPDIF(I_SPDIF4),
.ch0_data(rx4_ch0),
.ch1_data(rx4_ch1),
.chan_status_bit(chan_status_bit[3]),
.block_start_flag(block_start_flag[3]),
.sub_frame0_flag(sub_frame0_flag[3]),
.sub_frame1_flag(sub_frame1_flag[3]),
.lock_flag(lock_flag[3]),
.sample_freq(rx4_sample_freq),
.cp(rx4_cp),
.cpl(rx4_cpl)
);
assign cp=(rx1_cp) & rx2_cp & rx3_cp & rx4_cp;
assign cpl=rx1_cpl & rx2_cpl & rx3_cpl & rx4_cpl;
endmodule
module recv(
input wire RESETn,
input wire RXCLK, //rx用512fs
input wire I_SPDIF,
output wire [`PCM_BIT+1:0] ch0_data,
output wire [`PCM_BIT+1:0] ch1_data,
output reg [5:0]sample_freq,
output reg cp,
output reg cpl,
output wire chan_status_bit,
output wire block_start_flag,
output wire sub_frame0_flag,
output wire sub_frame1_flag,
output wire lock_flag
);
wire spdif_data_en;
wire [`PCM_BIT-1:0] audio_d;
SPDIF_RX_Top rx(
.I_clk(RXCLK), //input
.I_rst_n(RESETn), //input
.I_spdif_rx_data(I_SPDIF),//input
.O_audio_d(audio_d), //output [15:0]
.O_validity_bit(), //output
.O_user_bit(), //output
.O_chan_status_bit(chan_status_bit), //output
.O_spdif_data_en(spdif_data_en), //output
.O_block_start_flag(block_start_flag), //output
.O_sub_frame0_flag(sub_frame0_flag), //output
.O_sub_frame1_flag(sub_frame1_flag), //output
.O_parity_check_error(), //output
.O_lock_flag(lock_flag), //output
.O_spdif_recovery_clk() //output
);
reg [`PCM_BIT-1:0] ch0_tmp;
always @(posedge RXCLK)
if (sub_frame0_flag==1'b1) ch0_tmp<=audio_d;
reg [`PCM_BIT*2-1:0] pcmdata;
always @(posedge RXCLK)
if (sub_frame1_flag==1'b1) pcmdata<={ch0_tmp,audio_d};
else if (lock_flag==1'b0) pcmdata<=0;
assign ch0_data={pcmdata[`PCM_BIT*2-1],pcmdata[`PCM_BIT*2-1],pcmdata[`PCM_BIT*2-1:`PCM_BIT]};
assign ch1_data={pcmdata[`PCM_BIT-1],pcmdata[`PCM_BIT-1],pcmdata[`PCM_BIT-1:0]};
//チャンネルステータス保持
reg [7:0] blk_cnt;
always @(posedge RXCLK)
if (block_start_flag==1'b1) blk_cnt<=1;
else if(sub_frame0_flag==1'b1) blk_cnt<=blk_cnt+1;
always @(posedge RXCLK)
if (lock_flag==1'b0) begin
cp<=1'b1;
cpl<=1'b1;
sample_freq<=0;
end
else begin
if (blk_cnt==2 && spdif_data_en==1'b1) cp<=chan_status_bit;
if (blk_cnt==15 && spdif_data_en==1'b1) cpl<=chan_status_bit;
if (blk_cnt==24 && spdif_data_en==1'b1) sample_freq[0]<=chan_status_bit;
if (blk_cnt==25 && spdif_data_en==1'b1) sample_freq[1]<=chan_status_bit;
if (blk_cnt==26 && spdif_data_en==1'b1) sample_freq[2]<=chan_status_bit;
if (blk_cnt==27 && spdif_data_en==1'b1) sample_freq[3]<=chan_status_bit;
if (blk_cnt==30 && spdif_data_en==1'b1) sample_freq[4]<=chan_status_bit;
if (blk_cnt==31 && spdif_data_en==1'b1) sample_freq[5]<=chan_status_bit;
end
endmodule
top.cst
IO_LOC "LED[5]" 16; IO_PORT "LED[5]" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "LED[4]" 15; IO_PORT "LED[4]" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "LED[3]" 14; IO_PORT "LED[3]" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "LED[2]" 13; IO_PORT "LED[2]" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "LED[1]" 11; IO_PORT "LED[1]" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "LED[0]" 10; IO_PORT "LED[0]" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "OLED_RST" 63; IO_PORT "OLED_RST" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "OLED_CS" 48; IO_PORT "OLED_CS" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "OLED_DC" 49; IO_PORT "OLED_DC" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "OLED_SDA" 77; IO_PORT "OLED_SDA" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "OLED_SCL" 76; IO_PORT "OLED_SCL" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "PLL_CLK" 86; IO_PORT "PLL_CLK" IO_TYPE=LVCMOS18 PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8; IO_LOC "O_SPDIF" 57; IO_PORT "O_SPDIF" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "SEL_O" 34; IO_PORT "SEL_O" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8 BANK_VCCIO=3.3; IO_LOC "I_SPDIF4" 56; IO_PORT "I_SPDIF4" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3; IO_LOC "I_SPDIF3" 55; IO_PORT "I_SPDIF3" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3; IO_LOC "I_SPDIF2" 54; IO_PORT "I_SPDIF2" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3; IO_LOC "I_SPDIF1" 53; IO_PORT "I_SPDIF1" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3; IO_LOC "SCK" 35; IO_PORT "SCK" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3; IO_LOC "SYSCLK" 52; IO_PORT "SYSCLK" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3; IO_LOC "RESETn" 4; IO_PORT "RESETn" IO_TYPE=LVCMOS18 PULL_MODE=UP BANK_VCCIO=1.8;
timing.sdc
create_clock -name SYSCLK -period 37.037 -waveform {0 18.518} [get_ports {SYSCLK}] create_clock -name SCK -period 81.38 -waveform {0 40.69} [get_ports {SCK}] create_generated_clock -name tx_clk -source [get_ports {SCK}] -master_clock SCK -divide_by 4 [get_pins {clkdiv_1_s0/Q}] set_false_path -from [get_clocks {tx_clk}] -to [get_clocks {SCK}] -hold
その他GUI設定系
こちらも生成ソースベタ貼りでもいいのですが、GUIからの設定を記載します。
Configuration
Dual-Purpose Pinの設定で、SSPIをregular IOとして使用する設定が必要です。
IP Core系
SPDIF RX
PCMデータ幅を16bitに変更しています。
verilogソースの冒頭のdefineで、PCMビット幅をPCM_BITで可変にしています。24bitとかでも動くように作ったつもりです(未確認)。
SPDIF TX
Dual port BRAM(8bit x 8192)
OLEDの画像バッファ用メモリです。1dotが4bitのため、256x64/2=8192バイトのバッファが必要です。
Dual port BRAM(1bit x 512)
SPDIF チャンネルステータス保持用のメモリです。512word用意していますが、SPDIFの1blockあたり片chで192ビットなので、両chでも384ビットで足ります。
ただ、設計の簡略化で、アドレス最上位ビットでチャンネル切り替えとしたので、512wordのメモリを使用しています。
動作解説
CDR(clock data recovery)/クロック
DIX9211の動作を試してみる(受信編)で実験した動作になります。
SPDIF1入力をDIX9211に渡して、その信号に同期したクロック(SCK)を受け取っています。
基本的に、オーディオデータ系の処理はSCK(サンプリングレート48KHz時 12.288MHz)、もしくはそのクロックをTangNanoのPLLで4逓倍したものを使用します。
なぜ4逓倍したものを使用するかというと、TangNanoのIPコアのSPDIF receiverが受信サンプリングレート×512の以上のクロック周波数が必要で、一応96kHzまで受信できるようにするために12.288MHz x 4=49.152MHz=96KHz x 512としています。
SCKとpll出力のクロック(pllclk)は位相が合うため、同期クロックとなります。そのため、SCKとpllclk間のデータ乗せ換えは非同期吸収などは不要です。
なので、受信→ミキシング→送信までがすべてSCK系のクロックで動作します。
ちなみに、TangNanoのrPLLは入力周波数の設定が必要です。今回はサンプリングレート48KHz前提で12.288MHzとしています。ただ、異なるサンプリングレートを入れた場合、この設定した入力周波数と異なる周波数のクロックがrPLLに入ることになりますが、問題なく動くようです。
SPDIF receiver
verilogモジュール:recv
SPDIF受信&ビット拡張を行います。また、チャンネルステータスbitから使うもの(サンプリングレート、コピー管理)だけを抽出します。
サブフレーム0(=左ch)のデータを受信した段階でいったんch0_tmpに保持し、その後サブフレーム1(=右ch)のデータを受信した段階で、pcmdataに左chと右chのデータをセットで格納します。
ここで受信したデータをミキシング回路で取り込むタイミングは、送信のフレームに合わせて取るので、このようにしないと右chと左chが1サンプルずれて取り込まれる可能性があります。
チャンネルステータス信号は早々頻繁に変わるものではないため、受信した段階ですぐにミキシング回路に渡します。
また、入力信号が無効(lock_flag=0)の時は、pcmdataやチャンネルステータス信号をミュートします。
verilogモジュール:receiver
recvモジュールを4系統分まとめたwrapper階層になります。また、著作権保護(SCMS)の簡易管理もしています。
補足:SCMSについて
SCMSとはSPDIFの著作権保護機構です。前回の試行では、どうせDACにしかつながないから基本的にコピー禁止でいいやと思っていたのですが、ビットパーフェクトかどうかの検証をするためにはコピー禁止ではPCで録音できませんでした。
なので、(すべてコピーフリーとして出力するのが最も簡単なんですがそれだと問題ありそうなので)SCMSを軽く実装してみます。
これは、CDからDAT(死語)やMiniDisc(死語)にダビングするときの動きなどと同様です。DATもMiniDiscも同じなので、以下すべてDATと記載します(SCMS制定時はMiniDiscなんてなかったので、仕様書はだいたいDAT基準で記載されている)。
基本的には、
・CD→DATへのダビングは可
・CD→DAT→DATの孫コピーは不可
です。なので、著作権保護がかかっているものは1世代のみデジタルコピー可能という仕様です。当然ですが並列無限コピー(CDから大量のDATへダビング)は可能です。
逆にコピーフリーというものもあり、容易に入手できたものでは、BSアナログ(死語)のBmodeステレオ(死語)が48KHz 16bitでそうだったはずです。こちらについては、
・BSチューナー→DAT→DAT→DAT・・・
の無限デジタルコピーが無劣化で可能だったはずです。ダビング10になる前の地デジもそうかもしれませんし、今でも著作権保護のかかっていない地デジ放送(NHKの災害時など)はコピーフリーかもしれません。
なので、SPDIFに乗る信号としては、
・コピー不可
・1世代のみコピー可能
・無限にコピー可能
の3種類があることになります。どうも仕様をみるとCGMS(地デジ系の著作権保護)に別途規定があるようですが、基本的にはSCMSの上位互換のようです。
この管理はチャンネルステータスのbit2(Cp bit)とbit15(L bit)で管理されており、
Cp=1:無限にコピー可能
Cp=0、L=1:1世代のみコピー可能
Cp=0、L=0:コピー不可
が基本的な仕様です。ただし、例外があるようで、ソース機材が何かというものがcategory cord(bit8-14)に定義されており、CDプレーヤーの場合はCp=0、L=0でも1世代のみダビングは可能としてDAT側で認識するようです。
なので、ミキサーとして動作する場合、入力系統のうちもっともコピー制限が厳しいものを出力側に採用するべきですので、Cpビット、Lビットともに0のほうが厳しいほうですので、すべての入力をANDして使えばよさそうです。
単純にCp、Lだけで見ると、CDプレーヤーのように例外があるようで、きちんと実装しようとするともっと検討要素あるかもしれませんが、少なくとも厳しいほうに倒すのであればこれでよさそうです。
ミキサー/チャンネルステータス作成/pll制御
verilogモジュール:top
・PLL制御
rPLLには、DIX9211から出力したSCKを入力しています。DIX9211のSCKは、有効なSPDIF信号が入力されている場合、その信号からCDRで取り出したbitクロックが出力され、入力信号が無効な場合は水晶振動子で作られたクロックが出力されます。
つまり、入力信号の切り替えなどをすると、rPLLに入力するクロックが止まったり変化したりします。
当然その場合いったんrPLLのLockが外れるのですが、なぜか外れたまま復帰しないということがあります(バグ?使い方?)。
なので、rPLLのLockが外れた場合、一定期間rPLLに自動でリセットをかけるようにしています。
・ミキシングとチャンネルステータス
recvモジュールから受け取った4系統のpcmdataを単純加算します。16bitデータですが単純加算なので溢れます。
溢れた場合は上限/下限に張り付きます。
また、チャンネルステータス信号も作成します。チャンネルステータスは以下の内容で設定します。
補足:SPDIFのチャンネルステータス
| bit | 用途 | 今回の設定値 |
|---|---|---|
| 0 | 本フィールドのフォーマット | 0(民生仕様) |
| 1 | データ形式 | 0(Liner PCM) |
| 2 | Cpビット | 別途記載 |
| 3-5 | 追加形式情報。bit1の値により用途が変わる | 000(プリエンファシスなしPCM) |
| 6-7 | bit8以降のフォーマット。00以外無効 | 00 |
| 8-14 | カテゴリーコード(例、CD、DAT、Minidiscなど) | 0100100(ミキサー) |
| 15 | Lビット | 別途記載 |
| 16-19 | ソース番号 | 0000(ソース番号情報なし) |
| 20-23 | チャンネルナンバー | 左ch:1000、右ch:0100 |
| 24-27 | サンプリングレート | 入力1の値をそのまま使用 |
| 28-29 | クロック精度 | 01(クロック精度情報なし) |
| 30-31 | サンプリングレート(拡張) | 入力1の値をそのまま使用 |
| 32-35 | ワード長 | 0100(16bit) |
| 36-39 | オリジナルサンプリングレート。サンプリングレートコンバーターなどの場合に使う | 0000(オリジナルサンプリングレート情報なし) |
| 40-41 | CGMS-A | 00 |
| 42 | CGMS-A validity | 0:CGMS-A情報(bit40-41)無効 |
| 43 | 未使用 | 0 |
| 44-47 | オーディオサンプリング周波数係数。CGMSに使うらしい | 0000(係数情報なし) |
| 48 | PCMのLSBへの隠しデータ埋め込み追加情報。LSBに隠しデータ埋め込みがある場合はPCMデータ加工不可 | 0(埋め込み情報なし) |
| 49-54 | Aチャンネル用チャンネル番号。よくわからない | 000000(チャンネル1) |
| 55-60 | Bチャンネル用チャンネル番号。よくわからない | 000000(チャンネル1) |
| 61-62 | LEF再生音量 | 00(音量情報なし) |
チャンネルステータス表示
verilogモジュール:chstatus_disp、oled、spi
チャンネルステータスをOLEDに表示するモジュールになります。
chstatus_dispモジュールでは、チャンネルステータスを保持する系と、表示系の主に2つのカウンタを持っています。
チャンネルステータスを保持する系では、blk_cntで現在受信中のブロックをカウントし、dual port ramのアドレス(port A)として使用しています。
また、sub_frame1_flag(サブフレーム1=右ch受信時に1になる)をアドレス最上位に使用することで、RAMの後半に右chのデータが記録されます。
表示系では、カウンタが分割されていますが、実質的にはSYSCLKで動作する12bitのカウンタ{ch,bit_cnt,y_cnt,x_cnt}を構成しています。
一文字を8x8dotで構成して、x_cntとy_cntでfont内の座標をカウントしています。1dotは4bitですが、OLEDのSPIは8bit単位(2dot単位)で送信するため、x_cntは2bitカウンタになっています。この値とramからのリード値を使って、表示するdot(白or黒)を決めています。また、SPDIF信号が入力されていない(lock_flag=0)の時はグレー塗りつぶしとしています。実際に表示する値は4bitのグレースケールですが、2dot分をまとめてdi[7:0]に格納されます。
次に、今求めたdot情報を、oled verilogモジュールを介してvramに書き込みます。vramは8192word x 8bitで、1wordあたり2dotとなります。そのため、書き込むvramのアドレス(vram_a)は、
{ch,0,bit_cnt[5],y_cnt,bit_cnt[4:0],x_cnt}
とすることで、横1列にチャンネルステータス32bit分、片ch分が2行で表示され、両チャンネル合わせて4行の表示になります。
oled verilogモジュール内では、OLED基板の初期化と、vramからのデータ転送を行います。
初期化はpower on resetのみ行い、SPIへ 0xAF,0x10,0x00,0xB0,0x00、と書き込んでいます。これは0xAFはOLED表示ON、その後の4byteはOLEDボード内のVRAM書き込み開始アドレスの設定コマンドです。
その後、カウンタでdpramのアドレスをインクリメントしつつ、dpramから読みだしたデータをSPIに流し続けます。spi verilogモジュールはデータを受け取るとACKを返すため、ACKを見てアドレスをインクリメントします。
ちなみに、SPIのCSがずっとLに固定されていますので、もし万が一SPI CLKがノイズの影響などを受けると永久にビットずれ状態になり復帰しないので、本当はこのような実装は良くありません。
また、OLEDボードのVRAMアドレス設定もpower on時の初回だけしか行っていないので、なんらかの都合でずれるとこちらも永久に復帰しません。
そのため、本当はdpramのデータをすべてOLEDボードに転送完了した時点で、いったんCSを戻し、vramアドレス設定コマンドからやり直すほうが良いでしょう。
また、ステート状態(state)が7になると永久に復帰できませんので、ここも改善の余地があります。
spi verilogモジュールはoled verilogモジュール配下にあり、OLEDボードへのSPI送信を行います。これはSYSCLK(27MHz)基準で動作しています。
今回使用したsh1122ボードのSPIスペックは下記の通りです。
まず、SCLのMinが500ns=2MHzです。回路構成を簡略化するために、SYSCLK(27MHz)を16分周して、約1.69MHzで動作させることにします。
また、SIのsetup/holdタイムは、SCLの半周期よりも短いため、基本的にはSCLの立下りと同時に出力していく方式で問題ありません。
ただし、A0(DC)のSetupタイムはSCLの半周期よりも長いため、こちらは前周期のSCLの立ち上がりで出力するのが良いです。ただし、この信号はbitごとに変化するものではないので、最終bitのSCL立下りまでに確定していればよいので、最初のbitで指定してしまうのが簡単です。
spi モジュールはWE信号がアクティブ(1)になったのをみて動き始めます。WEを受け付けられる(前のデータの送信中でない)場合、応答としてACKを返します。ACKを返した後、SCLやSDA(SI)などを操作し、OLEDボードにデータを送信しています。
動かす
さて今回、入力1からの信号はビットパーフェクトに出力されることを目標としています。
そちらの検証については、単純に、PCでwavファイルを再生、それを別PCで録音、wavファイル同士を比較することで確認できます。
とはいえwindows側が色々といらんことをしてくれるので、まず普通にこのミキサーを通さずに再生→録音してビットパーフェクトが再現できるかの確認が必要です。
こちらでビットパーフェクトが再現できた環境としては、下記のソフトを使用しました。
・再生側:foobar2000で排他モード
・録音側:WaveSpectra
・比較:WaveCompare
WaveCompareはwavファイルの先頭の無音部分を飛ばして実データ部分のみを比較してくれるため、比較の際の頭出し作業が不要なるので便利です。
結果、想定通り、ミキサーを経由してもマスターとした入力についてはビットパーフェクトにスルー、マスター以外の入力ではデータ欠落が発生していることが確認できました。
とりあえず作りたかった最低限のものは作れた感じです。
改善案
- 写真手前左側の穴あき基板部には、赤外線受光モジュールをつけて、リモコンからなんか色々ごにょごにょできるようにしたいです。
例えば入力ごとのボリューム設定や、マスターとなる入力の切り替えなど。 - 「sampling rate:48kHz」のように、 もうちょっと画面表示を丁寧にしたいです。
全入力の主要情報を一覧表示するモードと、個別の詳細情報を表示するモードを切り替えるなど。
上記のようなことをやろうとすると、ロジックのステートマシンだけで実装するのはかなりめんどくさいため、簡易なCPU等を実装するのがお手軽と思います。リソースかなり余ってますし。
時点の改良案で、 サンプリングレートコンバーターの実装があります。これはどのくらいまじめに作るかでかなり難易度があがります。
異なるサンプリングレート間のコンバーターであれば、さほど難しくもないのですが、同一周波数の非同期ソースをいい感じに同期化しようとするとかなりめんどくさいです。
また、さらなる改良案として、せっかく余っているHDMIからの音声もミキシングできるようになると便利かも?というのもあります。
が、現実的にはHDCPを解除できないかぎりあまり実用性は無いのかなという気もします。HDCP解除の実装は個人では無理でしょう。できたとしても、実際作るとどこからか文句言われそうな気がします。
まあHDMI入力だけあってHDMI出力が無いと使いにくいので、結局外部にHDMIスプリッターが必要になりますし、であれば音声スプリッター使えばよいですね。
それよりもHDMI出力として使って、SPDIFの全入力のステータスの一覧などを表示できるようにしたほうが使い道あるかもしれません。
投稿者の人気記事


-
lyricalmagical
さんが
2025/07/20
に
編集
をしました。
(メッセージ: 初版)
-
lyricalmagical
さんが
2025/07/20
に
編集
をしました。
-
lyricalmagical
さんが
2025/07/20
に
編集
をしました。
-
lyricalmagical
さんが
2025/10/11
に
編集
をしました。
(メッセージ: typo修正)
ログインしてコメントを投稿する