Verilog'da Basit SPI modülü tasarımı

Başlatan Mucit23, 28 Şubat 2017, 11:19:45

Mucit23

Hocam hazır kodlara bakıyorum ama çok karmaşık. Genelde hepsi full dublex haberleşme için yazılmış. İncelediğim örneklerde Bir sürü Register'lar vs kontrol edilerek tam anlamıyla bir spi modülü tasarlanmış.  Benim bu kadar kapsamlı bir koda ihtiyacım yok.

Yapmak istediğim kısaca şudur.
Enable girişinden bir start sinyali gelince paralel girişteki 8 bitlik datayı SPI formatında clock ve Data pini ile seri olarak dışarı çıkarıp beklesin yeter. Bunu yapmaya çalışıyorum.

Clock üretmede problem yaşıyorum.
Normalde Clock girişinin Düşen kenarında datanın n. bitini dışarı vermem gerekiyor. Bunu yapıyorum. Daha sonra Clock girişinin Yükselen kenarında Clock çıkışını 1 yapmam gerekiyor. İşte burada takılıyorum. Clock üretme işlemini veri gönderirken yapmam gerekiyor. Ama bunu bir türlü yapamadım.

Sadece yöntem gösterseniz yeterli benim için.





superconductor

Hocam Deo-Nano kit üzerindeki adc chipi bu şekilde okuyorum bir inceleyin istediğiniz bu şekilde bir yapı olmalı. DIN pini ile 3 bit kanal seçimi gidiyor. DB etiketliler adc den gelen datalar.
[IMG]http://i65.tinypic.com/kdtthu.png[/img]


module adcController(clk, chnSel, data, cs, mosi, miso);

	input clk;
	input [2:0]chnSel;
	input miso;

	output cs;
	reg cs;
	output mosi;
	reg mosi;

	output [11:0]data;
	reg [11:0]data;

	initial
	begin
		cs <= 0;
		mosi <= 0;
	end

	reg [7:0]divider;


	always @(negedge clk)
	begin
		
		if(divider == 0)
		begin
			cs <= 0;
		end
			
		if(divider > 1 && divider < 5)	
		begin 
			mosi <= chnSel[divider-2];
		end
		
		if(divider >= 5)
		begin
			mosi <= 0;
			data[divider - 5] <= miso;
			
		end
		
		if(divider == 20)
		begin
			divider <= 0;
			cs <= 1;
		end
		else
		begin
			divider <= divider + 1;
		end

	end
endmodule


kod içinde clk üretmedim chipin clk girişi ile adccontroller modülüne giren clk aynı hatta bağlı. İsterseniz clkOut diye bir output oluşturup clk girişine assign edebilirsiniz.

Mucit23

Benzer bir işi bende yaptım ama clock işini çözemedim sadece. Bendeki sistemde clock sinyalinin herzaman çıkmaması lazım. Shift register üzerinde kullanıcam bunu. Bu yüzden Data ile birlikte Clock sinyali çıkması gerekiyor. Diğer türlü boş data içeri alınabilir.
İşin mantığını biraz biraz anlamaya başladım. Şu clock işinide çözersem tamamdır.

@superconductor hocam bir şarta bağlı olarak Clock girişini Clock çıkışına nasıl eşitlerim.
assign etme işlemini doğrudan if içerisinde yaparsam hata veriyor.
Birde bunu bi always bloğu içerisine almak gerekiyor. Bunu nasıl yapmam lazım?

Mucit23

Verilog işi biraz biraz oturmaya başladı ama hala anlamadığım birçok nokta var.

Şuanda istediğimi yapmak üzereyim. Bir hata alıyorum sebebini tahmin ettiğim şeymi anlamaya çalışıyorum.

Sorum şudur.
İki farklı always bloğu olsun.

İki bloktada ortak olarak kullanılan reg tipi değişken olunca berleyici bana "Can't handle registered multi driver" hatası veriyor. Hatanın anlamı anladığım kadarıyla şudur. Aynı değişkenler iki farklı blokta kullanılıyor. Burada işlemler paralel olarak yapıldığı için aynı anda iki bloktada aynı registere müdahale etme gibi bir olasılık var. Bu durumdan dolayı derleyici hata veriyor. Doğrumudur sizce? Bu durumu nasıl aşarım?

Şuanda aşağıdaki gibi bir kod yazdım.
module ParalelToSerial (
	output  Busy,
	output  Clkout,
	output  Dout,
	input   Clkin,
	input [7:0]Din,
	input   En
);

//`#start body` -- edit after this line, do not edit this line
    reg busy;
    reg tx_en;
    reg dout;
    reg clkout;   
    reg [3:0]cnt_spi;
    
    always @ (posedge Clkin)
    begin
       if (En == 1'b1)
       begin
         tx_en <= 1'b1;
         busy <= 1'b1;
       end
       
    end
    
    
    always @ (negedge Clkin)
    begin    
       if(tx_en == 1'b1)
       begin
         clkout <= Clkin;
       end
       
       if(tx_en == 1'b1)
       begin       
            dout <= Din[cnt_spi];
            cnt_spi <= cnt_spi + 1; 
       end
       
       if(cnt_spi==8)  //aktarım tamamlandı
       begin
         busy <= 1'b0;
         tx_en <= 1'b0;
       end
    end

    assign Busy = busy;
    assign Dout = dout;
    assign Clkout = clkout;

endmodule


2. Sorum assign ile yaptığım atama işlemleri always bloğunun içerisinde kullanılabiliyormu? Eğer kullanılamıyorsa yazdığım programın başına veya sonuna yazmam farkedermi?

3. Sorum Bazı uygulamalarda aşağıdaki gibi işlemler görüyorum

Data <= {Data[6:0], 1'b0};

Data 8 bitlik reg tipi değişken. Burada ne yapılmış olabilir? Güzel parantez içerisindeki işlemi anlayamadım.

Dediğim gibi yapı kafamda bi otursa yürüyecem. Bu tür soruları anlayamıyorum.

muuzoo

#19
Verilog uzmanlık alanım değil ama VHDL'den yola çıkarak yorumlarsak,

1) tx_en ve busy sinyalini iki farklı noktadan ve biri yükselen kenarda diğeri ise düşen kenarda değiştirmeye kalkmışsınız. Böyle bir kullanıma kızar sentezleyiciler. Çoğu durumda özel register yapıları hariç çift kenar tetiklemeye izin vermezler. Hatanızın sebebi aynı kaynağı iki farklı noktadan değer atamaya çalışıyorsunuz. Multiple driver hatasının sebebi o. İki farklı always bloğu içinden değer değiştirmeye çalışmışsınız.

2) verilog bilgim fazla değil, sadece tahminen bu atama VHDL deki sinyal ataması gibi o yüzden yine tahmini olarak bir sorun olmaaması lazım nerede yazarsanız yazın.

3) O işlem sola kaydırma işlemi olarak yorumlanabilir. 8 bitlik verinin en son 7 bitini alıp [6 :0]  sonuna 0 eklerseniz veriyi sola kaydırmış olursunuz. Bu işlem o işe yarıyor. Yani shift işlemi.
gunluk.muuzoo.gen.tr - Kişisel karalamalarım...

Mucit23

@muzoo

hocam sonuca baya yaklaştım. Cevabınız için teşekkür ederim.

Clock ile ilgili problemim devam ediyor onu çözersem hedefime ulaşmış olacağım.

Tasarladığım modül'de enable girişine bir adet buton bağladım. Butona basınca sisteme 0 gidiyor
modülün içindeki verilog kodum aşağıdaki gibi
module ParalelToSerial (
	output  Busy,
	output  Clkout,
	output  Dout,
	input   Clkin,
	input [7:0]Din,
	input   En
);

//`#start body` -- edit after this line, do not edit this line
    reg busy;
    reg tx_en;
    reg dout;
    reg clkout;   
    reg [3:0]cnt_spi;
    
    initial begin
      clkout <= 1'b0;
      dout <= 1'b0;
      busy <= 1'b0;
      tx_en <= 1'b0;
      cnt_spi <= 1'b0;
    end
    
    always @ (posedge Clkin)
    begin
       if(tx_en == 1'b1)
       begin
         clkout <= Clkin;
       end
    end
    
    always @ (negedge Clkin)
    begin  
       if (En == 1'b0 && busy == 1'b0)
       begin
         tx_en <= 1'b1;
         busy <= 1'b1;
       end
       
       if(tx_en == 1'b1)
       begin       
            dout <= Din[cnt_spi];
            cnt_spi <= cnt_spi + 1; 
       end
       
       if(cnt_spi==8)  //aktarım tamamlandı
       begin
         cnt_spi <= 1'b0;
         busy <= 1'b0;
         tx_en <= 1'b0;
       end
    end

    assign Busy = busy;
    assign Dout = dout;
    assign Clkout = clkout;
    
//`#end` -- edit above this line, do not edit this line
endmodule


Data çıkışı açıkçası tam istediğim gibi çalışıyor. Butona bastığım sürece Paralel girişimdeki data seri olarak dışarı çıkıyor. Bunu Osiloskopta görebiliyorum.
Yalnız clock çıkışı olmadı.

always bloğu içerisinde clock çıkışı üretemiyorum.

Bunu farklı bir yöntemle yapmam lazım. Bu konuda önerilerinize açığım.

Bir Sorum olacak. internette always bloğunun yapısını incelerken aşağıdaki gibi bir kullanım gördüm birkaç yerde
always @ (*)

şart kısmına yıldız koymak ne anlama geliyor? Bu yapı PSoC editörümdeki verilog derleyicisinde çalışmıyor. Sentezleme aşamasında hata veriyor.  Bu kullanım VHDL içinmi geçerli? Nasıl çalışıyor?

superconductor

Hocam enable girişi ile clock sinyalini AND' leyip, always bloğu dışında clockOut' a assign etmeyi denermisiniz.

Mucit23

Hocam evet oldu şimdi. Aklıma gelmiyor nedense AND işlemi.

Koduda biraz düzenledim.

dout <= Din[cnt_spi]; Bu işlemi yaparken data nedense 2 clock palsinden sonra gelmeye başlıyordu. Şimdi bir tane tx_buf tanımlayıp veriyi aşağıdaki kod ile shift ederek gönderiyorum.

tx_buf <= {tx_buf[6:0], 1'b0};

Bu şekilde yaptığım zaman tam olarak standart spi formatında çıktı aldım.


1. kanal data (0xAA)
2. kanal clock
3. kanal busy

Sıkıntı yok gibi görünüyor. Şimdi biraz yazılımı düzenlemem lazım.

Verilog ile ilgili bir soru sorayım.

tx_buf <= {tx_buf[6:0], 1'b0};

Bu satırda nasıl sihft yapıldığını anlayamadım.  {} ile ne gibi kullanımlar verilog'da. Bunlar ne diye geçer nasıl öğrenebilirim?

muuzoo

@Mucit23

Alıntı Yap
tx_buf <= {tx_buf[6:0], 1'b0};

Bu satırda nasıl sihft yapıldığını anlayamadım.  {} ile ne gibi kullanımlar verilog'da. Bunlar ne diye geçer nasıl öğrenebilirim?

Mantık çok basit. Diyelim ki verimiz şu olsun "1100 0000"

1) tx_buf[6:0] ile ilgili verinin en son 7 bitini seçtik. Yani "100 0000"
2){} işareti birleştirme operatorü olarak yorumlanabilir "concatenation" işlemi. {tx_buf[6:0],1'b0} diyerek aslında {1000000,0} verisini birleştir demiş oldunuz.
3 Yeni değerimiz "1000 0000" oldu. Görüdğünüz gibi veriyi sola kaydırmış olduk.
gunluk.muuzoo.gen.tr - Kişisel karalamalarım...

Mucit23

@muuzoo  teşekkürler anladım şimdi. Eğer ilk önce düşük biti göndermek istersek aşağıdaki gibi bir kullanım olacaktı sanırım

dout <=tx_buf[0];
tx_buf <= {1'b0, tx_buf[7:1]};

superconductor

@Mucit23 hocam, Psoc üzerinde verilogda yazdığınız modül ile mcu arasında nasıl veri alışveriş yapıyorsun? Paralel IO tarzı birşey mi? Benimde ilgimi çekti, hemen bulabileceğim bir kit önerir misin?

Mucit23

Hocam ilk başta bende bilmiyordum ama öğrendim. Cypress'in forumuna sorarak öğrendim.
http://www.cypress.com/forum/psoc-5-known-problems-and-solutions/verilog-c-bidirectional-data-transfer
http://www.cypress.com/comment/392376#comment-392376

C tarafından Verilog'a göndermeyi daha önce sormuştum. Onu şuanda yapabiliyorum. Bugün Verilog Tarafındaki bilgiyiokuma işlemini sordum. Onuda hemen gösterdiler yalnız henüz deneyemedim.

Aslında bunu yapmanın birkaç yolu var. Eğer Verilog'da hazırladığınız modülde Input ve Output'lara Şema üzerinden Control_Reg Veya Status_Reg gibi hazır donanımlarla ulaşabiliyorsunuz.

Örneğin Devre şemasına Control_Reg donanımı ekleyince C tarafında Proje ağacına Control_Reg'e erişmek için fonksiyonlar otomatikmen oluşturuluyor. Siz bu fonksiyonlara sadece bir veri gönderiyorsunuz. Gönderdiğiniz veri Devre şemasında nere yönlendirdiyseniz oraya gidiyor. Seçenek çok gerçekten. Tasarımı size kalmış.

Yukarıda verdiğim linklerde şema ile hiç uğraşmadan veri nasıl gönderilir veya alınır o konu anlatılıyor. Örnekde var.

Cypress'in forumu çok aktif. Herhalde firmanın ücretli çalışanları sürekli forumu takip ediyorlar. Bazı kullanıcılar mesaj yazdıktan sonra hemen cevap verebiliyorlar. Destekleri oldukça iyi.

Çalışmalarımı şuanda CY8CKIT-050 PSoC® 5LP kartı ile yapıyorum. Kart üzerinde yeterli seviyede çevre donanımı var.  Fakat 99$ fiyatı var. Eğer hızlı başlangıç yapmak istiyorsanız şu kartıda kullanabilirsiniz.
http://www.cypress.com/documentation/development-kitsboards/psoc-4-cy8ckit-049-4xxx-prototyping-kits

Fiyatı sadece 4$!! Bende bu da var yalnız tam anlamıyla uğraşamadım. Bunun kendi üzerinde programlayıcı yok Kart üzerinde PSOC 4100 ve USB seri dönüştürücü var. BoatLoader ile programlamak gerekiyor. Kendi dökümanlarında anlatılıyordu nasıl yapılacağı ben uğraşmadım açıkçası.

Fiyatları pahalı olmasa Şu PSoC 5 serici mükemmel çipler içerisine 80Mhz CortexM3 MCU, FPGA DMA, ADC, DAC, OPAMP, CAPSENSE Çeşitli Haberleşme protokolleri (USB I2C SPI CAN...) vs her türlü var. 

Bu kadar yaygın kullanılmamasının sebebi piyasaya yüksek bir fiyatla girmiş olması sanırım.

Mucit23

Arkadaşlar bir yerde takıldım ufaktan bir fikre ihtiyacım var.

Benim yapmış olduğum verilog modülünde Start girişinden lojik 1 verdiğim zaman girişteki 1 byte veriyi gönderip dursun istiyorum. Daha sonra tekrar 1 byte veri göndermek istersem modülü resetlerim diye düşündüm.

Fakat resetleme işlemini yapamıyorum. Modülde bir reset girişi var. Bu reset girişinden lojik 1 uyguladığım zaman SPI bloğundaki bazı bayrakları sıfırlamam gerekiyor.

Yazdığım kod bu şekilde.
`include "cypress.v"
//`#end` -- edit above this line, do not edit this line
// Generated on 04/11/2017 at 15:17
// Component: ParalelToSerial
module ParalelToSerial (
	output  Busy,
	output  Clkout,
	output  Dout,
	input   Clkin,
	input  [7:0] Din,
	input   Reset,
	input   Start
);

//`#start body` -- edit after this line, do not edit this line

    reg dout;
    reg clkout;
    reg reset;
    reg tx_en;
    reg [7:0]tx_buf;
    reg [3:0]cnt_spi;

    initial begin
      reset <= 1'b0;
      clkout <= 1'b0;
      tx_en <= 1'b0;
      cnt_spi <= 1'b0;
    end
    
    always @ (negedge Clkin)
    begin  
       if (Start == 1'b1 && reset == 1'b0)
       begin      
         reset <= 1'b1;
         tx_buf <= Din;
         tx_en <= 1'b1;
       end
       
       if(tx_en == 1'b1)// shift işlemi yap
       begin    
            tx_buf <= {tx_buf[6:0], 1'b0};
            cnt_spi <= cnt_spi + 1; 
       end
       
       if(cnt_spi==7)  //aktarım tamamlandı
       begin
         cnt_spi <= 1'b0;
         tx_en <= 1'b0;
       end
    end

    assign Busy = tx_en;
    assign Dout = tx_buf[7];
    assign Clkout = tx_en & Clkin;    
    
//`#end` -- edit above this line, do not edit this line
endmodule


Reset girişini farklı bir always bloğunda sorguluyorum. Reset 1 ise always gerçekleşecek fakat bu işlemi yaptığım zaman  "Can't handle registered multi driver" 

Aynı register'lara birden fazla always bloğu içerisinde erişmeye çalışıca sürekli bu hatayı alıyorum ve elimi kolumu bağlıyor.

Şuanda kodum, Start girişi 1 ise 1 byte veriyi gönderip duruyor fakat tekrar göndermek için reset registerini sıfırlamam gerekiyor onuda yapamadım.

@muuzoo @superconductor  hocam bu işi düzgün bir şekilde nasıl yapabilirim? Yani SPI protokolünü oturtturdum ama kontrol işleri tam olmadı. Bu sorunu çözmem gerekiyor.


muuzoo

@Mucit23 reset işlemini farklı bir always içinde sorgulatmak yerine neden aynı always içinde yapmıyorsun. Ekteki kod verilog için asenkron reset örneği.

always @(posedge clk or posedge reset) begin
    if(reset == 1'b1)
       reg <= 0; //reset condition
    else
       reg <= whatever; // non-reset condition
end


Bu da senkron reset örneği. Hangisi işine yararsa:

always @(posedge clk) begin
   if(reset)
      reg <= 0; //reset condition
   else
      reg <= whatever; //non-reset condition
end
gunluk.muuzoo.gen.tr - Kişisel karalamalarım...

Mucit23

@muuzoo hocam verilogda yeniyim bu yüzden saçma sapan işlere kalkışmam normaldir.  :) Kod tecrübem yok.

Hocam bu resetleme işlemini yapamadım. Şuanda bahsettiğiniz şekilde yaptım hata vermiyor ama düzgünde çalışmıyor. Yukarıdaki kodda yapmak istediğim şudur aslında

Data girişinden 1 byte veri yükleyip Start girişi 1 yapılırsa o 1 byte veriyi seri olarak gönderip dursun. Tekrar gönderim için Modülü resetleyeyim ve Start girişi tekrar 1 olursa yine girişteki veriyi gönderip dursun. Böyle bir mantık düşünüyorum ama daha iyi çözümlerde olabilir.

Bunu siz olsaydınız nasıl yapardınız? Ben bir Reset flag'ı ile yapmaya çalışıyorum ama herzamanki gibi Mikroişlemciye kod yazar gibi düşünüp verilogda kodlama yapınca paralel işlemlerde hep çuvalladım. :-[ Bazı şeyler hala tam oturmuş değil.  :-\ Bu konuda önerinize ihtiyacım var.