上一篇我们设计了 SPI 的状态机,那麽我们今天要来引用 SPI 状态机模块来实现整个 SPI 的模块,并撰写 testbench 来验证电路的正确性。
先来看看这个 SPI 模块该有哪些输入输出脚吧:
输入:
输出:
module uasge_SPI_4wire(
en,
clock_50M,
rst_n,
CS,
mosi,
miso,
din,
dout,
SCLK,
finish
);
/*-------ports declaration------*/
input clock_50M;
input rst_n;
input miso;
input en;
input [15:0] din;
output [15:0] dout;
output CS;
output mosi;
output SCLK;
output finish;
reg [15:0] dout;
wire CS;
wire mosi;
wire SCLK;
wire finish;
宣告变数:
需要用到的变数有:
/*-------variables------*/
reg [3:0] counter;
reg [15:0] regdata_i;
reg [15:0] regdata_o;
reg tick_SPI;
reg SCLK_temp;//mux of SCLK_temp or 1'b1
reg [5:0] cnt_SPI; //1MHZ counter
wire countEN;
wire rstcount;
wire SHEN;
wire LDEN;
引用状态机模组
/*-------module instantiate------*/
SPI U0(
.clk_sys(clock_50M),
.SCLK_temp(SCLK_temp),
.rst_n(rst_n),
.tick_SPI(tick_SPI),
.SCLK(SCLK),
.CS(CS),
.count(counter),
.countEN(countEN),
.rstcount(rstcount),
.ready(en),
.finish(finish),
.SHEN(SHEN),
.LDEN(LDEN)
);
cnt_SPI
/*-------1M counter------*/
always@(posedge clock_50M or negedge rst_n)begin
if(!rst_n)cnt_SPI <= 6'd0;
else begin
if(cnt_SPI<6'd49)cnt_SPI <= cnt_SPI + 6'd1;
else cnt_SPI <= 6'd0;
end
end
做出 SCLK_temp
/*-------make SCLK------*/
always@(posedge clock_50M or negedge rst_n)begin
if(!rst_n)begin
SCLK_temp <= 1'b0;
end
else begin
if(cnt_SPI == 6'd24) SCLK_temp <= 1'b0;
else if(cnt_SPI == 6'd49)SCLK_temp <= 1'b1;
else SCLK_temp <= SCLK_temp;
end
end
为什麽这边是 24 跟 49 呢?因为 0-24 & 25-49 会刚好把 clk 切为一半,这样写就会分别在正缘且 cnt 为 24 & 49 时 SCLK_temp 反向。
tick_SPI
/*-------tick_SPI------*/
always@(posedge clock_50M or negedge rst_n)begin
if(!rst_n)tick_SPI <= 1'b0;
else begin
if(cnt_SPI==6'd48)tick_SPI <= 1'b1;
else tick_SPI <= 1'b0;
end
end
来解释一下位甚麽这边是 48,因为我想让其他动作有在 SCLK 负缘时动作的效果,而 SCLK 会在 cnt = 49 时发生负缘,所以如果我想让其他事情也在 cnt = 49 时动作,那麽我 tick 必须提前一个 clk 升起来才行,所以这里才是 48 而非 49。
靠 rstcount&countEN 来控制 counter
/*-------ctrl counter------*/
always@(posedge clock_50M or negedge rst_n)begin
if(!rst_n)begin
counter <= 4'd0;
end
else begin
if(tick_SPI)begin
if(rstcount) counter <= 4'd0;
else if(countEN)counter <= counter + 4'd1;
else counter <= 4'd0;
end
else counter <= counter;
end
end
靠 LDEN&SHEN 来控制我们的 regdata_i
/*-------data in------*/
always@(posedge clock_50M or negedge rst_n)begin//SCLK's negedge
if(!rst_n)begin
regdata_i <= 16'd0;
end
else begin
if(tick_SPI)begin
if(LDEN) regdata_i <= din;
else if(SHEN)regdata_i <= {regdata_i[14:0], 1'b0};
else regdata_i <= regdata_i;
end
else regdata_i <= regdata_i;
end
end
这里使用左移是因为 SPI 为 MSB 先传。
蒐集 miso 的 data
/*-------collect miso data------*/
always@(posedge clock_50M or negedge rst_n)begin//SCLK's posedge
if(!rst_n)regdata_o <= 16'd0;
else begin
if(tick_SPI)begin
if(SHEN)regdata_o <= {regdata_o[14:0], miso};
else regdata_o <= regdata_o;
end
else begin
regdata_o <= regdata_o;
end
end
end
这边来解释一下为什麽用 SCLK 的负缘收资料,因为我设计的模组是预想外部输入会在 SCLK 正缘时改变资料,所以为了确保资料的正确,要在 SCLK 的负缘收比较恰当。
於传输结束後将 dout 送出
/*-------data out------*/
always@(posedge clock_50M or negedge rst_n)begin
if(!rst_n)begin
dout <= 16'd0;
end
else begin
if(finish)dout <= regdata_o;
else dout <= dout;
end
end
endmodule
还有最後的 mosi 还没处理
/*-------assign wire------*/
assign mosi = (SHEN)?(regdata_i[15]):(1'b0);
当没有要传资料时其实给什麽值都行,因为此时 SCLK 没有在动作,也意味着 slave 端并不会收取资料。
Quartus 的 State Machine Viewer
`timescale 1ns / 1ns
module spi_tb();
reg sysClk, rst_n, tick_tx, miso;
reg [7:0]address, txdata;
reg [15:0]rxdata;
wire [15:0]data_get;
wire sclk, cs_n, mosi;
wire finish;
localparam Freq_i = 50000000;
localparam durTime = 1000;
integer i;
uasge_SPI_4wire UUT(
.en(tick_tx),
.clock_50M(sysClk),
.rst_n(rst_n),
.CS(cs_n),
.mosi(mosi),
.miso(miso),
.din({address,txdata}),
.dout(data_get),
.SCLK(sclk),
.finish(finish)
);
always@(posedge sclk, negedge rst_n)begin
if(!rst_n) miso <= 1'b0;
else if(i>=0&&i<16)miso <= rxdata[15-i];
else miso <= 1'b0;
end
always@(posedge sclk, negedge rst_n)begin
if(!rst_n)i <= 0;
else i <= i + 1;
end
always #10 sysClk = ~sysClk;
initial begin
sysClk = 0;
rst_n = 0;
tick_tx = 0;
address = 8'h7A;
txdata = 8'h81;
rxdata = 16'h4689;
rst_n = 0;
#1000 rst_n = 1;
#1000;
#1000 tick_tx = 1;
#1000 tick_tx = 0;
repeat(20) #durTime;
$stop;
end
endmodule
这里的输入资料 16 bit 是由 8 bit 的 address 以及 8 bit 的 txdata 所组成,
而 testbench 会将 rxdata 於 SCLK 的正缘依序由 MSB 传出。
这边可以看到 din 为 7a81,同样为水蓝色信号线也确实有在 SCLK 负缘时改变输出值,而且值为 0111_1010_1000_0001,确实为 7a81。
再来是收的部分,可以看到粉红色信号线是在 SCLK 正缘传送 0100_0110_1000_1001(4689),而在 data_get 也确实在资料传送结束时吐出 4689。
最後也来可以看一下 RTL Viewer
看起来没有合成出过多冗余的部分,还可以~
以上就是我们的 4 线 SPI 教学喽~~~~
<<: Day20 AR抬头显示器(HUD)与一般的差异 你是5岁就抬头还是3岁才抬头的呢?
>>: [Day 20] 资料标注 (1/2) — Forget about the price tag ♫
前置作业-HTML & CSS 使用 Google 查 Weather App 会有很多很多...
只要谈到AWS资安议题绝对不能不提到 AWS Shared Responsibility Model...
大家好,我是乌木白,今天是倒数最後一天,虽然在最後几天我们没什麽讲技术方面的问题,第一个是我觉得我...
还记得 React Native 可以同时完成 iOS / Android 装置吧? 跨平台装置如...
在写React的时候其实有分为两种写法 Class Component this.state or ...