在上一篇文章《FPGA的SPI从机模块实现》中,已经实现了SPI的从机模块,如何通过SPI总线与FPGA内部其他模块进行通信,是本文的主要讨论内容。
module dac_reg_rw_spi(clk, nrst, rec_flag, rec_data, send_flag, sending_flag, send_data, dac_clk, dacref_clk, dacref_fudu, dacsin_output);
input clk, nrst; //模块系统时钟、复位
//与spi模块交互引脚 input rec_flag; //spi字节接收标志 input[7:0] rec_data; //spi接收数据缓存寄存器 input sending_flag; //spi模块正在发送数据标志位
//与spi模块交互引脚 output send_flag; //dac控制模块存在需要发送数据标志位,主要负责触发spi发送 output[7:0] send_data; //spi需要发送的数据
//与外部dac通信引脚 output[11:0] dacref_fudu; //直接输出到dacref中 output[11:0] dacsin_output; output dac_clk; output dacref_clk;
这里假定先发送一个字节的命令,紧接着通过spi发送、接收dac控制模块所需的数据。所以定义命令字如下:
//指令代号 parameter read_dacref_fudukongzhizi=8'b00000001; //读取dacref的幅度控制字 parameter read_dacsin_xiangweikongzhizi=8'b00000010; //读取dacsin的相位控制字 parameter read_dacsin_pinlvkongzhizi=8'b00000011; //读取dacsin的频率控制字 parameter set_dacsin_pinlvkongzhizi=8'b00100001; //写入dacsin的频率控制字 parameter set_dacsin_xiangweikongzhizi=8'b00100010; //写入dacsin的相位控制字 parameter set_dacref_fudukongzhizi=8'b00100011; //写入dacref的幅度控制字 parameter set_dac_clk_pdf=8'b00100100; //设置dac时钟预分频 parameter reset_to_default=8'b11100000; //复位dac控制模块 parameter start_dac=8'b11100001; //开启dac模块 parameter stop_dac=8'b11100010; //停止dac模块
//dac配置的影子寄存器(dac运作依据的值) reg[11:0] dacref_fudukongzhizi_shadow; reg[31:0] dacsin_pinlvkongzhizi_shadow; reg[9:0] dacsin_xiangweikongzhizi_shadow; reg[3:0] dac_clk_pdf_shadow;
reg[3:0] rw_reg_status; //处理spi接收发送数据状态机 reg[3:0] rw_reg_status_temp; ////处理spi接收发送数据状态机(影子寄存器) reg[7:0] rec_data_temp; //8位spi数据接收缓存 reg[3:0] delay_cnt; //发送数据延时计数器 //dac配置寄存器临时值 reg[31:0] dacsin_pinlvkongzhizi; reg[11:0] dacref_fudukongzhizi; reg[9:0] dacsin_xiangweikongzhizi; reg[7:0] send_data; //与myspi模块通信脚 reg send_flag; //与myspi模块通信脚 reg[2:0] byte_sended_cnt; //发送数据字节数计数器 reg[2:0] byte_received_cnt; //接收数据字节数计数器 reg dac_start_flag; //dac使能脚 reg dacref_clk; //spi通信处理状态机,需要注意的是,clk时钟频率必须为sck时钟频率约10倍以上,保证正确操作。 always @ (posedge clk or negedge nrst) begin if(~nrst) begin
//初始化上述寄存器 rw_reg_status <= 4'h0; rw_reg_status_temp <= 4'h0; //处理spi接收发送数据状态机(影子寄存器) rec_data_temp <= 8'h00; dacsin_pinlvkongzhizi <= 32'h00412345; dacsin_xiangweikongzhizi <= 10'h123; dacref_fudukongzhizi <= 12'h800; delay_cnt <= 4'b0000; byte_sended_cnt <= 3'b000; send_flag <= 1'b0; byte_received_cnt <= 3'b000; dac_start_flag <= 1'b0; dacref_clk <= 1'b0; dacref_fudukongzhizi_shadow <= 12'h800; dacsin_pinlvkongzhizi_shadow <= 32'h00423456; dacsin_xiangweikongzhizi_shadow <= 10'h200; dac_clk_pdf_shadow <= 4'h1; end else begin case (rw_reg_status) 4'b0000: begin //从机接收指令 if(rec_flag) begin rec_data_temp <= rec_data; rw_reg_status <= 4'b0001; //进入命令解析 end end 4'b0001: begin //指令解析,跳转相应状态 case (rec_data_temp) reset_to_default: begin rw_reg_status <= 4'b1110; end read_dacref_fudukongzhizi: begin rw_reg_status <= 4'b0011; //读dacref的幅度控制字 end read_dacsin_xiangweikongzhizi: begin rw_reg_status <= 4'b0010; //读dacsin的相位控制字 end read_dacsin_pinlvkongzhizi: begin rw_reg_status <= 4'b0110; //读dacsin的频率控制字 end set_dacsin_pinlvkongzhizi: begin rw_reg_status <= 4'b1101; //设置dacsin的频率控制字 rw_reg_status_temp <= 4'b0101; end set_dacsin_xiangweikongzhizi: begin //设置dacsin的相位控制字 rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0100; end set_dacref_fudukongzhizi: begin //设置dacref的幅度控制字 rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b1100; end set_dac_clk_pdf: begin //设置dac时钟预分频值 rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b1001; end start_dac: begin rw_reg_status <= 4'b1010; //rw_reg_status_temp <= 4'b0000; end stop_dac: begin rw_reg_status <= 4'b1011; //rw_reg_status_temp <= 4'b0000; end default: begin rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0000; end endcase end //---------------------------------------------------- 4'b0011: begin //先发送幅度控制字高八位字节然后发送低八位字节 if(~sending_flag) begin //判断spi是否处于发送状态 case (byte_sended_cnt) 3'b000: begin send_data <= {4'b0000, dacref_fudukongzhizi_shadow[11:8]}; rw_reg_status_temp <= 4'b0011; rw_reg_status <= 4'b0111; end 3'b001: begin send_data <= dacref_fudukongzhizi_shadow[7:0]; rw_reg_status_temp <= 4'b0011; rw_reg_status <= 4'b0111; end default: begin byte_sended_cnt <= 3'b000; rw_reg_status_temp <= 4'b0000; rw_reg_status <= 4'b0000; //发送完成 end endcase end else begin send_flag <= 1'b0; end end //---------------------------------------------------- 4'b0010: begin if(~sending_flag) begin //判断spi是否处于发送状态 case (byte_sended_cnt) 3'b000: begin send_data <= {6'b000000, dacsin_xiangweikongzhizi_shadow[9:8]}; rw_reg_status_temp <= 4'b0010; //4'b0110; rw_reg_status <= 4'b0111; end 3'b001: begin send_data <= dacsin_xiangweikongzhizi_shadow[7:0]; rw_reg_status_temp <= 4'b0010; //4'b0110; rw_reg_status <= 4'b0111; end default: begin rw_reg_status <= 4'b0000; //发送完成 rw_reg_status_temp <= 4'b0000; byte_sended_cnt <= 3'b000; end endcase end else begin send_flag <= 1'b0; end end //---------------------------------------------------- 4'b0110: begin if(~sending_flag) begin //判断spi模块是否处于发送状态 case (byte_sended_cnt) 3'b000: begin send_data <= dacsin_pinlvkongzhizi_shadow[31:24]; rw_reg_status_temp <= 4'b0110; rw_reg_status <= 4'b0111; //4'b0100; end 3'b001: begin send_data <= dacsin_pinlvkongzhizi_shadow[23:16]; rw_reg_status_temp <= 4'b0110; rw_reg_status <= 4'b0111; //4'b0100; end 3'b010: begin send_data <= dacsin_pinlvkongzhizi_shadow[15:8]; rw_reg_status_temp <= 4'b0110; rw_reg_status <= 4'b0111; end 3'b011: begin send_data <= dacsin_pinlvkongzhizi_shadow[7:0]; rw_reg_status_temp <= 4'b0110; rw_reg_status <= 4'b0111; end default: begin rw_reg_status <= 4'b0000; //发送完成 rw_reg_status_temp <= 4'b0000; byte_sended_cnt <= 3'b000; end endcase end else begin send_flag <= 1'b0; end end //通用状态 4'b0111: begin
//dac控制模块向spi模块提出发送请求,即生成send_flag脉冲 send_flag <= 1'b1; if(delay_cnt == 4'b0011) begin delay_cnt <= 4'b0000; rw_reg_status <= rw_reg_status_temp; byte_sended_cnt <= byte_sended_cnt+1; end else begin delay_cnt <= delay_cnt+1; end end //---------------------------------------------------- 4'b0101: begin //if(rec_flag) begin case (byte_received_cnt) 3'b000: begin if(rec_flag) begin //spi字节接收完成标志位 dacsin_pinlvkongzhizi[31:24] <= rec_data; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b001; end end 3'b001: begin if(rec_flag) begin dacsin_pinlvkongzhizi[23:16] <= rec_data; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b010; end end 3'b010: begin if(rec_flag) begin dacsin_pinlvkongzhizi[15:8] <= rec_data; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b011; end end 3'b011: begin if(rec_flag) begin dacsin_pinlvkongzhizi[7:0] <= rec_data; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b100; end end 3'b100: begin dacsin_pinlvkongzhizi_shadow <= dacsin_pinlvkongzhizi; byte_received_cnt <= 3'b101; end 3'b101: begin rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0000; byte_received_cnt <= 3'b000; end endcase //end end //----------------------------------------------------
在spi接收到命令字时,下一个系统时钟clk上跳沿则进入此状态,此时rec_flag可能仍然是有效,所以会先进入4'b1101模块等待rec_flag标志位复位后再接收数据,其他状态其实大同小异,这里不一一描述。 4'b0100: begin //if(rec_flag) begin case (byte_received_cnt) 3'b000: begin if(rec_flag) begin dacsin_xiangweikongzhizi[9:8] <= rec_data[1:0]; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b001; end end 3'b001: begin if(rec_flag) begin dacsin_xiangweikongzhizi[7:0] <= rec_data; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b010; end end 3'b010: begin dacsin_xiangweikongzhizi_shadow <= dacsin_xiangweikongzhizi; byte_received_cnt <= 3'b011; end 3'b011: begin rw_reg_status <= 4'b1101; //4'b0000; rw_reg_status_temp <= 4'b0000; byte_received_cnt <= 3'b000; end endcase //end end //---------------------------------------------------- 4'b1100: begin //if(rec_flag) begin case (byte_received_cnt) 3'b000: begin if(rec_flag) begin dacref_fudukongzhizi[11:8] <= rec_data[3:0]; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b001; end end 3'b001: begin if(rec_flag) begin dacref_fudukongzhizi[7:0] <= rec_data; rw_reg_status <= 4'b1101; byte_received_cnt <= 3'b010; end end 3'b010: begin dacref_fudukongzhizi_shadow <= dacref_fudukongzhizi; byte_received_cnt <= 3'b011; end 3'b011: begin dacref_clk <= 1'b1; if(delay_cnt == 4'b0011) begin delay_cnt <= 4'b0000; byte_received_cnt <= 3'b111; end else begin delay_cnt <= delay_cnt+1; end end 3'b111: begin dacref_clk <= 1'b0; rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0000; byte_received_cnt <= 3'b000; end endcase //end end 4'b1101: begin if(~rec_flag) begin //字节接收完成标志位复位等待 rw_reg_status <= rw_reg_status_temp; end end //---------------------------------------------------- 4'b1110: begin dacsin_pinlvkongzhizi <= 32'h00454321; dacsin_xiangweikongzhizi <= 10'h234; dacref_fudukongzhizi <= 12'h321; rw_reg_status_temp <= 4'b0000; rw_reg_status <= 4'b1101; end 4'b1111: begin if(delay_cnt == 4'b0011) begin delay_cnt <= 4'b0000; rw_reg_status <= 4'b0000; end else begin delay_cnt <= delay_cnt+1; end end //---------------------------------------------------- 4'b1010: begin dac_start_flag <= 1'b1; rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0000; end //---------------------------------------------------- 4'b1011: begin dac_start_flag <= 1'b0; rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0000; end //---------------------------------------------------- 4'b1001: begin if(rec_flag) begin dac_clk_pdf_shadow <= rec_data[3:0]; rw_reg_status <= 4'b1101; rw_reg_status_temp <= 4'b0000; end end endcase end end
//dac时钟分频模块 reg dac_clk; reg[3:0] dac_clk_cnt; //分频,后面可以对dac_clk_cnt进行预分频处理 reg[3:0] dac_clk_pdf; //预分频 always @ (posedge clk or negedge nrst) begin if(~nrst) begin dac_clk <= 1'b0; dac_clk_cnt <= 4'b0; dac_clk_pdf <= 4'h1; end else begin if(dac_start_flag) begin if(dac_clk_cnt == dac_clk_pdf) begin dac_clk_cnt <= 4'b0; dac_clk <= ~dac_clk; dac_clk_pdf <= dac_clk_pdf_shadow; end else begin dac_clk_cnt <= dac_clk_cnt+1; end end end end //dac输出模块 assign dacref_fudu = dacref_fudukongzhizi_shadow; //直接输出到dacref中 //reg[11:0] dacsin_output; reg[31:0] leijiazi; reg[9:0] dac_rom_addr; //assign dacsin_enable = nrst&dac_start_flag; always @ (posedge clk or negedge nrst) begin if(~nrst) //dacsin失能 begin leijiazi <= {dacsin_xiangweikongzhizi_shadow, 22'h000000}; //累加字存储器 dac_rom_addr <= 10'h000; end else //dacsin使能 begin if(dac_start_flag) begin leijiazi <= leijiazi+dacsin_pinlvkongzhizi_shadow; dac_rom_addr <= leijiazi[31:22]; end else begin dac_rom_addr <= 10'h000; end end end sin_table U3( .clka(clk), .addra(dac_rom_addr), .douta(dacsin_output) );这里用到了名为sin_table的ROM软核,使用Block RAM组合成12位数据输出,10位数据深度(即1024个存储空间)的ROM,空间为12bits*1024。
题外话:既然提到了核,那么想当然联想到他们的分类:软核、固核和硬核三种。
软核:属于综合之前的RTL模型,只经过功能仿真,最后需要进行综合及布线后才能使用。但是不同的布线环境对其效果是不一样的,存在发送错误的可能性。
固核:带有局部规划信息的网表,对时序有一定约束后的产物,只需要通过布线工具就可以使用。
硬核:就是经过验证的设计版图,其物理版图不允许再进行修改,模块时序要求非常严格,可靠性相当高。
module myspi(nrst, clk, ncs, mosi, miso, sck, rec_flag, rec_data, send_flag, sending_flag, send_data); //miso主入从出,mosi主出从入 input clk, nrst; input ncs, mosi, sck; input send_flag; input[7:0] send_data; output[7:0] rec_data; output miso; output sending_flag; output rec_flag;这样,spi模块就加入与dac控制模块的通信线路了,是不是很方便。
module dac_top(clk, nrst, ncs, mosi, miso, sck, dac_clk, dacref_fudu, dacsin_output, dacref_clk); input clk, nrst, ncs; input mosi, sck; output miso; output dac_clk; output dacref_clk; output[11:0] dacref_fudu; output[11:0] dacsin_output; wire send_flag, rec_flag, sending_flag; wire[7:0] rec_data; wire[7:0] send_data; myspi U1( .clk(clk), .nrst(nrst), .ncs(ncs), .mosi(mosi), .miso(miso), .sck(sck), .rec_flag(rec_flag), .rec_data(rec_data), .send_flag(send_flag), .sending_flag(sending_flag), .send_data(send_data) ); dac_reg_rw_spi U2( .clk(clk), .nrst(nrst), .rec_flag(rec_flag), .rec_data(rec_data), .send_flag(send_flag), .sending_flag(sending_flag), .send_data(send_data), .dac_clk(dac_clk), .dacref_fudu(dacref_fudu), .dacsin_output(dacsin_output), .dacref_clk(dacref_clk) ); endmodule
module dac_top_test; // Inputs reg clk; reg nrst; reg ncs; reg mosi; reg sck; // Outputs wire miso; wire dac_clk; wire[11:0] dacref_fudu; wire[11:0] dacsin_output; // Instantiate the Unit Under Test (UUT) dac_top uut ( .clk(clk), .nrst(nrst), .ncs(ncs), .mosi(mosi), .miso(miso), .sck(sck), .dac_clk(dac_clk), .dacref_fudu(dacref_fudu), .dacsin_output(dacsin_output) ); initial begin // Initialize Inputs clk = 0; nrst = 0; ncs = 1; mosi = 0; sck = 0; // Wait 100 ns for global reset to finish #100; nrst = 1; #20; ncs = 0; #100; mosi = 0; //先发送高位 00100011 写入频率控制字 #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #100; mosi = 0; //发送 00001111 #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #100; mosi = 1; //发送 11111110 #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #100; mosi = 0; //发送 00000001 #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #100; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; // #100; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; #100 sck = 1; #100 sck = 0; // #100; mosi = 1; //发送 11100001 启动dac控制模块 #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #50000; mosi = 0; //延时50000个时间单元后再次修改频率控制字 #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #100; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 1; #100; sck = 1; #100; sck = 0; // #100; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; // #100; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; // #100; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; mosi = 0; #100; sck = 1; #100; sck = 0; end always #5 clk=~clk; //sck必须为clk的频率的十分之一或低于十分之一 endmodule