目录
1 Zynq-7000 系列芯片简介
PS:Processing System
PL:Processing Logic
芯片框图:
一些缩写:
MIO (Multiplexing IO, PS 域可复用的 IO,因为此 IO 管脚的配臵在一定范围内有灵活性);
EMIO (Extensible MIO, 对 MIO 的扩展, 将 PL 域的 IO 口直接连到 PS 域);
GIC (General Interrupt Controller, 通用的中断控制器);
IRQ (Interrupt Request, 中断请求);
OCM (On Chip Memory, 片上存储);
DMA (Direct Memory Access, 直接存储访问);
2 Vivado 开发环境安装
有问题自行百度
3 Vivado 全流程概览 - LED 流水灯( PL)
- 新建工程,选好对应的器件或者开发板
- 添加并编写需要实现的Verilog源代码
- 执行Vivado下面RTL Analysis->Open Elaboration Design->Schematic
- 行为仿真
- 分析&添加 I/O 约束
- 综合&添加时序约束
- 布局布线(P&R) 和 Bit 流生成
- 下载到 FPGA
Main Code Analysis
//----------------------------------------------------------------------------
// Project Name : pl_led_stream
// Description : Led streaming.
//----------------------------------------------------------------------------
// Version Comments
//------------ ----------------
// 0.1 Created
//----------------------------------------------------------------------------
module pl_led_stream(
output reg [3:0] led, // LED4 ~ LED1, 1 - on, 0 - off,既是wire型又是reg型变量!!!这块加reg好昂是因为后面alway说的条件需要是reg型的变量。相当于定义了reg的变量,用assign将该reg变量联系到wire型的输入输出
input clk, // FPGA PL part input clock, 50 MHz
input rst_n // FPGA global reset pin, press down generate low level
);
reg [27:0] cnt;//28bit reg,50MHz的时钟,计数25 000 000次可以到0.5s,28bit的数能hold住。
reg [1:0] style;
always @(posedge clk, negedge rst_n)//always类似于c语言的while,@后面跟的是条件;不带@相当于while(1)
begin//begin end相当于c语言的{}块语句,
if(!rst_n)//如果rst_n是低电平,执行下面的赋值语句
begin
cnt <= 28'd0;//'d十进制,'h十六进制,'b二进制。计数值赋值为0。
style <= 2'd0;//在语句里面<=是非阻塞赋值,在表达式里面<=是小于等于比较符。
end
else
begin
cnt <= cnt + 1'b1;//cnt自加1
if(cnt == 28'd24_999_999)//count in 0.5s with 50 MHz
begin
cnt <= 28'd0;//到达最大计数值之后清零。
style <= style + 1'b1;//style是2bit的数,所以只能取值0~3。
end
end
end
always @(style)//style是一个reg变量,这个应该是好任意边沿触发的意思,上升沿或者下降沿,只要数据变化就会触发!!
begin
case(style)//这块的case跟c语言里面的switch case不大一样,没有break也会跳出分支。
2'b00 : led = 4'b0001;//输出的led既是wire型又是reg型!!!分配引脚之后就可以点亮LED。
2'b01 : led = 4'b0010;
2'b10 : led = 4'b0100;
2'b11 : led = 4'b1000;
endcase
end
endmodule
4 硬件环境的调优 - 按键检测( PL)
Main Code Analysis
//----------------------------------------------------------------------------
// Project Name : pl_key_detect
// Description : pl key detect, handle the jetter when press & release.
//----------------------------------------------------------------------------
// Version Comments
//------------ ----------------
// 0.1 Created
//----------------------------------------------------------------------------
module pl_key_detect(
output reg pl_led1, pl_led2, pl_led3, pl_led4, // PL LED1 ~ PL LED4, 1 - on, 0 - off
input pl_key1, pl_key2, pl_key3, pl_key4, // PL KEY1 ~ PL KEY4, 0 - down, 1 - up
input clk, // FPGA PL part input clock, 50 MHz
input rst_n // FPGA Global reset, async, low-level active
);
//////////////////////////////////////////////////////////////////////// detect key press down
reg [3:0] key_press_down;
reg [3:0] key_press_down_r;
always @(posedge clk, negedge rst_n)
//边沿跟电平不能同时用,不同的边沿可以同时作为条件
begin
if(!rst_n)//如果rst为低电平
begin
key_press_down <= 4'b1111;//复位的时候初始化为全1
key_press_down_r <= 4'b1111;//复位的时候初始化为全1
end
else
begin
key_press_down <= {pl_key4, pl_key3, pl_key2, pl_key1};//非阻塞赋值,key跟输入引脚绑定。
key_press_down_r <= key_press_down;
//重点!!!非在阻塞赋值!!!这一行的key_press_down跟上一行的key_press_down不是一个东西!!!!
//这块体现了两个DFF的级联?哪体现DFF?_r是repeat的意思?
end
end
/*
-------------------------------------------------------------------------------------------------
rst no key have key pressed | key release
val press press not release | assume sample on glitch (1111 -> 0111 -> xxxx)
-------------------------------------------------------------------------------------------------
key_press_down : (1 1 1 1) 1 1 1 1 0 1 1 1 0 1 1 1 | 1 1 1 1 ~> 0 1 1 1
~key_press_down : (0 0 0 0) 0 0 0 0 1 0 0 0 1 0 0 0 | 0 0 0 0 1 0 0 0
key_press_down_r : (x x x x) 1 1 1 1 1 1 1 1 0 1 1 1 | 0 1 1 1 1 1 1 1
------------------------------------------------------------------------------------------------- &
key_press_down_conf : 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 | 0 0 0 0 ~> 1 0 0 0
-------------------------------------------------------------------------------------------------
*> here get the press info confirm *> glitch, cannot sustain beyond 20ms, no affect
*/
//上面注释里面的斜线是将上一次采样的值挪到下一次的非阻塞赋值里面,也就是说key_press_down_r是上一次的采样获取的key_press_down。
wire [3:0] key_press_down_conf;//不知为何申请了一个4bit的wire型变量,为什么是wire型?
assign key_press_down_conf = key_press_down_r & (~key_press_down);//按位取反后按位与,作用就是捕捉下降沿!!!
//这一步可以判断出是否有button按下,按下之前是高,按下之后是低,按下之后低取反跟上一次的高进行与,就可以捕捉到一次下降沿!!!
// against pressed down jetter,消除按下去的抖动
reg [19:0] cnt;
always @(posedge clk, negedge rst_n)
begin
if(!rst_n)
cnt <= 20'd0;
else if(key_press_down_conf != 4'd0) // key pressed down found, clear the count
cnt <= 20'd0;
else
cnt <= cnt + 1'b1;//这段期望在按下去没有释放的那段时间计数!!
end
//上面有两种情况会将计数器清零,一个是复位,另一个是检测有按键端有下降沿发生!!
reg [3:0] sampled_key_info;
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
sampled_key_info <= 4'b1111;
else if(cnt == 20'd99_9999) //按下去没有释放的时间达到20ms。
// the typical key jetter sustain 5ms~10ms, here wait 20ms, then sample the key info
sampled_key_info <= {pl_key4, pl_key3, pl_key2, pl_key1};
// here attend to generate latch for sampled_key_info no change at least 20 ms
end
// against release jetter,消除释放的抖动
reg [3:0] sampled_key_info_r;
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
sampled_key_info_r <= 4'b1111;
else
sampled_key_info_r <= sampled_key_info;
end
/*
----------------------------------------------------------------------------------------------
rst no key have key pressed key | pressed key release and not care sample on
val press press not release | the glitch or not, due to the & always get 0
----------------------------------------------------------------------------------------------
sampled_key_info : (1 1 1 1) 1 1 1 1 0 1 1 1 0 1 1 1 | x 1 1 1
~sampled_key_info : (0 0 0 0) 0 0 0 0 1 0 0 0 1 0 0 0 | x 0 0 0
sampled_key_info_r : (x x x x) 1 1 1 1 1 1 1 1 0 1 1 1 | 0 1 1 1
---------------------------------------------------------------------------------------------- &
detect_key_info: 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
----------------------------------------------------------------------------------------------
*> here get the pressed key
*/
wire [3:0] detect_key_info;//下面assign语句必须要用wire型的变量
assign detect_key_info = sampled_key_info_r & (~sampled_key_info);
///////////////////////////////////////////////////////////////////////////////////////////// led ctrl
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
pl_led1 <= 1'b0;
pl_led2 <= 1'b0;
pl_led3 <= 1'b0;
pl_led4 <= 1'b0;
end
else begin
case(detect_key_info)
4'b0001 : pl_led1 <= ~pl_led1;//从这块看出用的是按钮,不是开关
4'b0010 : pl_led2 <= ~pl_led2;
4'b0100 : pl_led3 <= ~pl_led3;
4'b1000 : pl_led4 <= ~pl_led4;
//default: // no need, due to need generate latch
endcase
end
end
endmodule
Summary
-
重点!!!非在阻塞赋值!!!这一行的key_press_down跟上一行的key_press_down不是一个东西!!!!
key_press_down <= {pl_key4, pl_key3, pl_key2, pl_key1};//非阻塞赋值,key跟输入引脚绑定。
key_press_down_r <= key_press_down;//这句里面的key_press_down跟上一句的key_press_down不一样。
-
两种情况必须要用wire形变量
- assign语句必须要用wire型的变量
- 原件实例化的接口输出
-
wire型和reg型变量
一般情况下,wire型变量会被综合成导线,reg型变量会被综合成寄存器register。不是绝对的,知识大部分情况下会这样。
-
获取bit变化的办法
assign key_press_down_conf = key_press_down_r & (~key_press_down); //这一步可以判断出是否有bit变化,有变化的bit会直接置位,没有变化的bit固定为0
-
cnt计数计数
50MHz的时钟,一个时钟沿有2*10^-8 second,20ms对应2*10^-2 second,所以需要10^6次的计数值。
-
sampled_key_info是采样的的key信息?sampled_key_info_r是sampled_key_info的上一次采样值,历史值。
这两个为什么不放在同一个always块里面?sampled_key_info需要判断20ms计时,计时到了之后直接配置为四个key的当前采样值,sampled_key_info_r则不需要判断计数,只需要判断是否复位。。硬要写在一个always块里面应该也是可以的。。
-
detect_key_info、key_press_down_conf的作用都是捕捉一下下降沿,产生一个短脉冲!!!不同的是detect_key_info要求这个下降沿要持续超过20ms才产生短脉冲?
sampled_key_info相当于是判断key_press_down_conf的最后一个脉冲(持续20ms没有新脉冲)之后才关联到key,应该是低电平,detect_key_info才会产生一个脉冲;sampled_key_info是一个锁存器!!
sampled_key_info本身在不停采样!!!有下降沿毛刺的话会将sampled_key_info的采样时间往后推(因为key_press_down_conf脉冲的产生会给cnt清零);sampled_key_info本身跟key也是关联的!!常规20ms采样就会刷新sampled_key_info,相当于detect_key_info是在一个更粗的时钟下进行采样
-
有个小建议,cnt计数满了20ms(20'd99_9999)之后也清零一下,否则会计数应该是2^20,而不是准确的20'd99_9999,不过这里二者大小差不多,也是可以的。。
-
注意本栗子代码中有两个地方产生latch,一个是没有else的if语句,另一个是没有default的case语句!!!
Verify
试着将上面的成许配置到step的开发板里面验证一下:
- 看一下step的原理图,找四个button和四个led;
- 将程序添加IO约束,
。。。Step的开发板是坏的,娘希匹。。。
使用testbench看一下吧。。
直接用z010的开发板!!
- 看一下z010的原理图。
5 和片外芯片打交道 - IIC 读写 EEPROM( PL)
Main Code Analysis
//----------------------------------------------------------------------------
// Project Name : pl_iic
// Description : EEPROM 24CLC04 Byte Read, Random Write, with IIC serial.
//----------------------------------------------------------------------------
// Version Comments
//------------ ----------------
// 0.1 Created
//----------------------------------------------------------------------------
module pl_iic(
output SCL, // FPGA output clk signal for 24LC04, 400 KHz (due to now 3.3v Vcc)
inout SDA, // serial input/output address/data
output [3:0] OUT_LED_DATA, // read value from 24LC04, for PL LED to display
input clk, // FPGA input clk, 50 MHz
input rst_n, // FPGA global reset
input PL_KEY1, // press down is 0, means write EEPROM start
input PL_KEY2 // press down is 0, means read EEPROM start
);
//////////////////////////////////////////////////////////////////////////////////////////////// key detect
//----------------------------------------------------- key press down confirm
reg [1:0] key_press_down;
reg [1:0] key_press_down_r;
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
key_press_down <= 2'b11;
key_press_down_r <= 2'b11;
end
else begin
key_press_down <= {PL_KEY2, PL_KEY1};
key_press_down_r <= key_press_down;
end
end
wire [1:0] key_press_down_conf;
assign key_press_down_conf = key_press_down_r & (~key_press_down);
//------------------------------------------------------ 20ms hysteresis range
reg [19:0] cnt_k;
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt_k <= 20'd0;
else if(key_press_down_conf != 2'd0) // key pressed down found, start count
cnt_k <= 20'd0;
else
cnt_k <= cnt_k + 1'b1;
end
reg [1:0] sampled_key_info;
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
sampled_key_info <= 2'b11;
else if(cnt_k == 20'd99_9999) // 20ms jetter covered, sample the key info
sampled_key_info <= {PL_KEY2, PL_KEY1};
end
wire PL_KEY1_pressed;
wire PL_KEY2_pressed;
assign PL_KEY1_pressed = sampled_key_info[0];
assign PL_KEY2_pressed = sampled_key_info[1];
//////////////////////////////////////////////////////////////////////////////////////////////// generate SCL (serial clock)
/*
clk = 50 MHz, period = 20 ns
SCL = 400 KHz, period = 2500 ns
from 24LC04 datasheet, for 3.3v Vcc input, the SCL pulse should
Thigh >= 600 ns , -> so here set SCL High Time 1000 ns
Tlow >= 1300 ns , -> so here set SCL Low Time 1500 ns
2500 / 20 = 125, so the count of one SCL period should 0 ~ 124
Format the SCL waveform as following,
count = 0 -> SCL posedge
count = 1000 / 20 = 50 -> SCL negedge
count = 25 -> SCL High-Level middle
count = 50 + 1500 / 20 / 2 ~= 87 -> SCL Low-Level middle
*/
reg [6:0] cnt;
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
cnt <= 7'd0;
else if(cnt == 7'd124) //cnt最多会计数到124,
cnt <= 7'd0;
else
cnt <= cnt + 1'b1;
end
reg SCL_r;
always @(posedge clk, negedge rst_n) begin
if(!rst_n)
SCL_r <= 1'b0;
else begin
if(cnt == 7'd0)
SCL_r <= 1'b1; // SCL posedge
else if(cnt == 7'd50)
SCL_r <= 1'b0; // SCL negedge
end
end
assign SCL = SCL_r;
// SCL special position label
`define SCL_POSEDGE (cnt == 11'd0)
`define SCL_NEGEDGE (cnt == 11'd50)
`define SCL_HIG_MID (cnt == 11'd25)
`define SCL_LOW_MID (cnt == 11'd87)
//////////////////////////////////////////////////////////////////////////////////////////////// wr, rd
// 24LC04 special parameter label
parameter WRITE_CTRL_BYTE = 8'b1010_0000, // select 24LC04 first 256 * 8 bit
READ_CTRL_BYTE = 8'b1010_0001, // select 24LC04 first 256 * 8 bit
WRITE_DATA = 8'b0000_0101, // Write data is 5
WRITE_READ_ADDR = 8'b0001_1110; // Write/Read address is 0x1E
reg SDA_r;
reg SDA_en;
assign SDA = SDA_en ? SDA_r : 1'bz; // SDA_en == 1, means SDA as output, it will get SDA_r
// SDA_en == 0, means SDA as input, it drived by the 24LC04, so high-z SDA_r out line
reg [3:0] OUT_LED_DATA_reg;
assign OUT_LED_DATA = OUT_LED_DATA_reg;
parameter IDLE = 5'd0,
// Write state (BYTE WRITE, refer to 24LC04 datasheet)
START_W = 5'd1,
SEND_CTRL_BYTE_W = 5'd2,
RECEIVE_ACK_1_W = 5'd3,
SEND_ADDR_BYTE_W = 5'd4,
RECEIVE_ACK_2_W = 5'd5,
SEND_DATA_BYTE_W = 5'd6,
RECEIVE_ACK_3_W = 5'd7,
STOP_W = 5'd8,
// Read state (RANDOM READ, refer to 24LC04 datasheet)
START_R_1 = 5'd9,
SEND_CTRL_BYTE_1_R = 5'd10,
RECEIVE_ACK_1_R = 5'd11,
SEND_ADDR_BYTE_R = 5'd12,
RECEIVE_ACK_2_R = 5'd13,
START_R_2 = 5'd14,
SEND_CTRL_BYTE_2_R = 5'd15,
RECEIVE_ACK_3_R = 5'd16,
RECEIVE_DATA_R = 5'd17,
STOP_R = 5'd18;
reg [4:0] state;
reg [3:0] write_byte_cnt;
reg [7:0] write_byte_reg;
always @(posedge clk, negedge rst_n) begin
if(!rst_n) begin
state <= IDLE;
write_byte_cnt <= 4'd0;
write_byte_reg <= 8'd0;
OUT_LED_DATA_reg <= 4'b0000; // LED all off
SDA_en <= 1'b0;
end
else begin
case(state)
IDLE: begin
SDA_en <= 1'b1;
SDA_r <= 1'b1;
if(PL_KEY1_pressed == 1'b0) begin
state <= START_W;
end
else if(PL_KEY2_pressed == 1'b0)
state <= START_R_1;
else
state <= IDLE;
end
//------------------------------------------ BYTE WRITE FSM START
START_W: begin
if(`SCL_HIG_MID) begin
SDA_r <= 1'b0;
write_byte_cnt <= 4'd0;
write_byte_reg <= WRITE_CTRL_BYTE;
state <= SEND_CTRL_BYTE_W;
end
else
state <= START_W;
end
SEND_CTRL_BYTE_W: begin
if(`SCL_LOW_MID) begin
case(write_byte_cnt)
0: SDA_r <= write_byte_reg[7];
1: SDA_r <= write_byte_reg[6];
2: SDA_r <= write_byte_reg[5];
3: SDA_r <= write_byte_reg[4];
4: SDA_r <= write_byte_reg[3];
5: SDA_r <= write_byte_reg[2];
6: SDA_r <= write_byte_reg[1];
7: SDA_r <= write_byte_reg[0];
default: ;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b0; // wait the 24LC04 to reponse ACK, so SDA as input
state <= RECEIVE_ACK_1_W;
end
else
state <= SEND_CTRL_BYTE_W;
end
end
RECEIVE_ACK_1_W: begin
if(`SCL_NEGEDGE) begin
write_byte_reg <= WRITE_READ_ADDR;
SDA_en <= 1'b1;
state <= SEND_ADDR_BYTE_W;
end
else
state <= RECEIVE_ACK_1_W;
end
SEND_ADDR_BYTE_W: begin
if(`SCL_LOW_MID) begin
case(write_byte_cnt)
0: SDA_r <= write_byte_reg[7];
1: SDA_r <= write_byte_reg[6];
2: SDA_r <= write_byte_reg[5];
3: SDA_r <= write_byte_reg[4];
4: SDA_r <= write_byte_reg[3];
5: SDA_r <= write_byte_reg[2];
6: SDA_r <= write_byte_reg[1];
7: SDA_r <= write_byte_reg[0];
default:;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b0; // wait the 24LC04 to reponse ACK, so SDA as input
state <= RECEIVE_ACK_2_W;
end
else
state <= SEND_ADDR_BYTE_W;
end
end
RECEIVE_ACK_2_W: begin
if(`SCL_NEGEDGE) begin
write_byte_reg <= WRITE_DATA;
SDA_en <= 1'b1;
state <= SEND_DATA_BYTE_W;
end
else
state <= RECEIVE_ACK_2_W;
end
SEND_DATA_BYTE_W: begin
if(`SCL_LOW_MID) begin
case(write_byte_cnt)
0: SDA_r <= write_byte_reg[7];
1: SDA_r <= write_byte_reg[6];
2: SDA_r <= write_byte_reg[5];
3: SDA_r <= write_byte_reg[4];
4: SDA_r <= write_byte_reg[3];
5: SDA_r <= write_byte_reg[2];
6: SDA_r <= write_byte_reg[1];
7: SDA_r <= write_byte_reg[0];
default: ;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b0; // wait the 24LC04 to reponse ACK, so SDA as input
state <= RECEIVE_ACK_3_W;
end
else
state <= SEND_DATA_BYTE_W;
end
end
RECEIVE_ACK_3_W: begin
if(`SCL_NEGEDGE) begin
SDA_en <= 1'b1;
state <= STOP_W;
end
else
state <= RECEIVE_ACK_3_W;
end
STOP_W: begin
if(`SCL_LOW_MID)
SDA_r <= 1'b0;
else if(`SCL_HIG_MID) begin
SDA_r <= 1'b1;
OUT_LED_DATA_reg <= 4'b1111; // when write succeed, all LED turn on
state <= IDLE;
end
end
//------------------------------------------ BYTE WRITE FSM END
//------------------------------------------ RANDOM READ FSM START
START_R_1: begin
if(`SCL_HIG_MID) begin
SDA_r <= 1'b0;
write_byte_cnt <= 4'd0;
write_byte_reg <= WRITE_CTRL_BYTE;
state <= SEND_CTRL_BYTE_1_R;
end
else
state <= START_R_1;
end
SEND_CTRL_BYTE_1_R: begin
if(`SCL_LOW_MID) begin
case(write_byte_cnt)
0: SDA_r <= write_byte_reg[7];
1: SDA_r <= write_byte_reg[6];
2: SDA_r <= write_byte_reg[5];
3: SDA_r <= write_byte_reg[4];
4: SDA_r <= write_byte_reg[3];
5: SDA_r <= write_byte_reg[2];
6: SDA_r <= write_byte_reg[1];
7: SDA_r <= write_byte_reg[0];
default: ;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b0; // wait the 24LC04 to reponse ACK, so SDA as input
state <= RECEIVE_ACK_1_R;
end
else
state <= SEND_CTRL_BYTE_1_R;
end
end
RECEIVE_ACK_1_R: begin
if(`SCL_NEGEDGE) begin
SDA_en <= 1'b1;
write_byte_reg <= WRITE_READ_ADDR;
state <= SEND_ADDR_BYTE_R;
end
else
state <= RECEIVE_ACK_1_R;
end
SEND_ADDR_BYTE_R: begin
if(`SCL_LOW_MID) begin
case(write_byte_cnt)
0: SDA_r <= write_byte_reg[7];
1: SDA_r <= write_byte_reg[6];
2: SDA_r <= write_byte_reg[5];
3: SDA_r <= write_byte_reg[4];
4: SDA_r <= write_byte_reg[3];
5: SDA_r <= write_byte_reg[2];
6: SDA_r <= write_byte_reg[1];
7: SDA_r <= write_byte_reg[0];
default: ;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b0; // wait the 24LC04 to reponse ACK, so SDA as input
state <= RECEIVE_ACK_2_R;
end
else
state <= SEND_ADDR_BYTE_R;
end
end
RECEIVE_ACK_2_R: begin
if(`SCL_NEGEDGE) begin
SDA_en <= 1'b1;
SDA_r <= 1'b1; // for START_R_2
state <= START_R_2;
end
else
state <= RECEIVE_ACK_2_R;
end
START_R_2: begin
if(`SCL_HIG_MID) begin
SDA_r <= 1'b0;
write_byte_cnt <= 4'd0;
write_byte_reg <= READ_CTRL_BYTE;
state <= SEND_CTRL_BYTE_2_R;
end
else
state <= START_R_2;
end
SEND_CTRL_BYTE_2_R: begin
if(`SCL_LOW_MID) begin
case(write_byte_cnt)
0: SDA_r <= write_byte_reg[7];
1: SDA_r <= write_byte_reg[6];
2: SDA_r <= write_byte_reg[5];
3: SDA_r <= write_byte_reg[4];
4: SDA_r <= write_byte_reg[3];
5: SDA_r <= write_byte_reg[2];
6: SDA_r <= write_byte_reg[1];
7: SDA_r <= write_byte_reg[0];
default: ;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b0; // wait the 24LC04 to reponse Read Data, so SDA as input
state <= RECEIVE_ACK_3_R;
end
else
state <= SEND_CTRL_BYTE_2_R;
end
end
RECEIVE_ACK_3_R: begin
if(`SCL_NEGEDGE) begin
state <= RECEIVE_DATA_R;
end
else
state <= RECEIVE_ACK_3_R;
end
RECEIVE_DATA_R: begin
if(`SCL_HIG_MID) begin
case(write_byte_cnt)
0: write_byte_reg[7] <= SDA;
1: write_byte_reg[6] <= SDA;
2: write_byte_reg[5] <= SDA;
3: write_byte_reg[4] <= SDA;
4: write_byte_reg[3] <= SDA;
5: write_byte_reg[2] <= SDA;
6: write_byte_reg[1] <= SDA;
7: write_byte_reg[0] <= SDA;
default: ;
endcase
write_byte_cnt <= write_byte_cnt + 1'b1;
if(write_byte_cnt == 4'd8) begin
write_byte_cnt <= 4'd0;
SDA_en <= 1'b1; // 24LC04 response data over, so make SDA as output
state <= STOP_R;
end
end
else
state <= RECEIVE_DATA_R;
end
STOP_R: begin
if(`SCL_LOW_MID)
SDA_r <= 1'b0;
else if(`SCL_HIG_MID) begin
SDA_r <= 1'b1;
OUT_LED_DATA_reg <= write_byte_reg[3:0]; // when read done, LED display the data
state <= IDLE;
end
end
endcase
end
end
endmodule
Summary
-
显然这个需要结合状态机statemachine来看,可以参考《夏宇闻-Verilog经典教程》里面的有限状态机。
有限状态机是由寄存器组和组合逻辑构成的硬件时序电路,其状态(即由寄存器组的1和0的组合状态所构成的有限个状态)只可能在同一时钟跳变沿的情况下才能从一个状态转向另一个状态,究竟转向哪一状态还是留在原状态不但取决于各个输入值,还取决于当前所在状态。
-
双向端口SDA使用的是inout!!
快快快
-
关于SCL_r跟SCL
always语句块里面只能出现reg型的变量,SCL是wire型,需要用assign进行连线。
-
SCL时钟的产生
SCL_r在cnt计数到0的时候非阻塞赋值1,给一个上升沿;计数到50的市州非阻塞赋值0,给一个下降沿。
cnt总共会计数到124,也就是125个数,对应400KHz;
-
always块里面只能对reg型变量进行操作,所以波形的变化主要凹体现在SCL_r变量的变换,实际上SCL_r跟SCL output口通过assign连接在一起。
-
宏定义使用的时候需要加上'引用符号。'ifdef之类的使用应该也是需要单引号''的,查一下
`define SCL_NEGEDGE (cnt == 11'd50) `define SCL_HIG_MID (cnt == 11'd25) `define SCL_LOW_MID (cnt == 11'd87) if(`SCL_HIG_MID) begin ;//略 end
-
IIC相关
- IIC在时钟低电平的时候允许切换数据信号,高电平只有起始位和结束位允许变化。
- 因为IDLE状态下两根线都是拉高,所以,时钟高电平状态下的下降沿表示传输开始,时钟高电平状态下的上升沿表示传输结束。
- 关于采样,好像是在SCL的高电平中间采样?确认一下
- 关于ACK,主机发送完一个byte,从机应该给一个ACK信号,也就是第9个bit变为低;
-
关于Control Code
总共8bits,前7bit为设备地址device address,最后1bit为读写操作。
这个EEP将device address改了一下,前7bit里面的后2bit用作block选择,相当于里面集成了多个IIC设备。。 -
双向口的通用处理方式
三态门对应的应该就是输入输出切换,输入对应高阻。
assign SDA = SDA_en ? SDA_r : 1'bz; // SDA_en == 1, means SDA as output, it will get SDA_ r // SDA_en == 0, means SDA as input, it drived by the 24LC04, so high-z SDA_r out line
-
状态机的状态使用了parameter,定义了一些读写状态;
parameter是可以修改的,调用模块的时候是可以被修改的,这块不应该定义成固定值吗?
状态机的定义可以用parameter 定义,但是不推荐使用`define 宏定义的方式,因为'define 宏定义在编译时自动替换整个设计中所定义的宏,而parameter 仅仅定义模块内部的参数,定义的参数不会与模块外的其他状态机混淆。例如一个工程里面有两个module 各包含一个FSM,如果设计时都有IDLE 这一名称的状态,如果使用'define 宏定义就会混淆起来,如果使用parameter 则不会造成任何不良影响。
这块记住状态机用parameter就可以了,多个状态机用define的话容易冲突。
-
栗子里面的IIC状态机如下所示,仅仅是实现了一些状态的停留和前向执行。
-
从161行开始到474行,剖析一些读写的细节
从上面的状态机图可以看到,读写占用一条总线,但是互不相干;
开始读写之前要确保state(reg型变量)是IDLE状态。
各个状态解释如下。
-
主机Host写入Write
- IDLE:检测按键判断是否有需要写的东西或者需要读取的东西;有的话跳转到各自的分支。
- START_W:在SCL一个周期内的高电平的中间给一个下降沿。
- SND_CB_W:在SCL一个周期内的低电平的中间(该Bit对应时钟的上一个SCL时钟的低电平的中间),将8个bit依次改变;靠近start位的为高位bit。需要在8bit结束后转换状态到等待ACK_1,且需要将SDA配置成输入,写bit计数变量清零。
- RSV_AK1_W:ACK这个好像没有判断ACK返回来到底是高还是低,也就是说根本没有读取SDA。在时钟下降沿的时候配置好了下一个状态需要用到的数据。。配置SDA回到输出状态。。
- SND_ADD_W:在SCL一个周期内的低电平的中间,将8个bit依次改变;靠近上一个ACK的为高位bit。需要在8bit结束后转换状态到等待ACK_2,且需要将SDA配置成输入,写bit计数变量清零。
- RSV_AK2_W:ACK这个好像没有判断ACK返回来到底是高还是低,也就是说根本没有读取SDA。在时钟下降沿的时候配置好了下一个状态需要用到的数据。。配置SDA到输出状态。。
- SND_DAT_W:在SCL一个周期内的低电平的中间,将8个bit依次改变;靠近上一个ACK的为高位bit。需要在8bit结束后转换状态到等待ACK_3,且需要将SDA配置成输入,写bit计数变量清零。
- RSV_AK3_W:ACK这个好像没有判断ACK返回来到底是高还是低,也就是说根本没有读取SDA。在时钟下降沿的时候配置好了下一个状态需要用到的数据。。配置SDA到输出状态。
- STOP_W:SDA在其对应时钟的上一个时钟的低电平的中间进行拉低,在其对应时钟的高电平的中间进行拉高。STOP主要标志就是在时钟SCL高电平期间拉高SDA。
-
主机Host读取Read
-
START_R_1:在对应的SCL时钟的高电平的中间,给一个下降沿,跟写操作是一样的。
-
SND_CB1_R:在SCL一个周期内的低电平的中间(该Bit对应时钟的上一个SCL时钟的低电平的中间),将8个bit依次改变;靠近start位的为高位bit。需要在8bit结束后转换状态到等待ACK_1,且需要将SDA配置成输入,写bit计数变量清零。
-
RSV_AK1_R:ACK这个好像没有判断ACK返回来到底是高还是低,也就是说根本没有读取SDA。在时钟下降沿的时候配置好了下一个状态需要用到的数据。。配置SDA回到输出状态。。
-
SND_ADD_R:在SCL一个周期内的低电平的中间(该Bit对应时钟的上一个SCL时钟的低电平的中间),将8个bit依次改变;靠近start位的为高位bit。需要在8bit结束后转换状态到等待ACK_1,且需要将SDA配置成输入,写bit计数变量清零。
-
RSV_AK2_R:ACK这个好像没有判断ACK返回来到底是高还是低,也就是说根本没有读取SDA。时钟下降沿的时候配置好了下一个状态需要用到的数据。。配置SDA回到输出状态。。这个ACK稍微特殊,SDA配置回输出的时候同时配置值为1
相当于分割了两帧
-
START_R_2:在对应的SCL时钟的高电平的中间,给一个下降沿。
-
SND_CB2_R:在SCL一个周期内的低电平的中间(该Bit对应时钟的上一个SCL时钟的低电平的中间),将8个bit依次改变;靠近start位的为高位bit。需要在8bit结束后转换状态到等待ACK_1,且需要将SDA配置成输入,写bit计数变量清零。
-
RSV_AK3_R:ACK这个好像没有判断ACK返回来到底是高还是低,也就是说根本没有读取SDA。在时钟下降沿的时候配置好了下一个状态需要用到的数据。。这个ACK也优点特殊,不需要将SDA切回输出
-
RSV_DAT_R:在SCL一个周期内的高电平的中间(该Bit对应的SCL时钟的高电平),将8个bit依次读取,靠近start位的为高位bit。最后将SDA配置成输出
-
STOP_R:SDA在其对应时钟的上一个时钟的低电平的中间进行拉低,在其对应时钟的高电平的中间进行拉高。STOP主要标志就是在时钟SCL高电平期间拉高SDA。
-
-
-
关于ACK
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。
-
关于IIC主机
一个时钟周期是先高电平后低电平;
- 对于主机Write来说,数据切换用上一个时钟周期的低电平的中间切换数据,留给从机下一个时钟的高电平来采样。Start位的低电平跟第一个byte的第一个bit的高电平搭配;一个Byte的最后一个bit的低电平中间开始配置ACK,SDA配置为输入,从机应该拉个低!ACK对应的时钟的下降沿用来装载/配置下一个Byte的数据。ACK对应的低电平的中间用来配置下一个Byte的第一个bit的数据或者用来给Stop位拉低,Stop位对应的时钟的高电平的中间用来拉高SDA。
- 对于主机Read来说,需要注意的是分了两帧,第一帧同Write,结束的ACK需要确保输出的时候是高;第二帧SDA输出部分变为输入,见上面状态机说明。
知识点
关于<=符号
在“表达式”(expression)中,"<="作为逻辑比较运算符;
在“语句”(statement)中,"<="作为非阻塞赋值的一部分。
verilog中,一个语法结构不可能同时允许“表达式”和“语句”,如果某处可以出现表达式,那么就不允许出现语句;
如果某处可以出现语句,那么一个单独的表达式就不能出现在那里。
如果预期出现的是表达式,那么其中的 <= 就解释成逻辑比较运算符;
如果预期出现的是语句,那么其中的 <= 就解释成非阻塞赋值的一部分,整个语句就是非阻塞赋值。
阻塞赋值和非阻塞赋值
见《高手也搞不清楚的十项基本功》
1、 时序逻辑一定用非阻塞赋值”<=”,一旦看到敏感列表有 posedge 就用”<=”。
2、 组合逻辑一定用”=”,一旦敏感列表没有 posedge 就用”=”,一旦看到 assign 就用”=”。
3、时序逻辑和组合逻辑分成不同的模块,即一个 always 模块里面只能出现非阻塞赋值”<=”或者”=”。如
果发现两种赋值并存,一个字”改”,心存侥幸可能会给后续工作带来更多麻烦。上三条,对新手而言不必追求为什么,需要的就是条件反射的照章办事。
#1: 当为时序逻辑建模,使用“非阻塞赋值”。
#2: 当为锁存器(latch)建模,使用“非阻塞赋值”。
#3: 当用always块为组合逻辑建模,使用“阻塞赋值”
#4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻
塞赋值”。
#5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。
#6: 不要在两个或两个以上always块里面对同一个变量进行赋值。
#7: 使用$strobe以显示已被“非阻塞赋值”的值。
#8: 不要使用#0延迟的赋值
always语句详解
1、语句格式:
always <时序控制> //一个 always 模块里面只能出现非阻塞赋值”<=”或者”=”。
<语句>
always语句由于其不断活动的特性,只有和一定的时序控制逻辑结合在一起才有用。如果一个always没有时序控制,它将会产生死锁,例如:
always clk = ~clk;
加上时序控制 always可以用来产生时钟信号:always #10 clk = ~clk 10个时间单位翻转一次。
always类似于c语言的while,@后面跟的是条件;不带@相当于while(1)
一个 always 模块里面只能出现非阻塞赋值”<=”或者”=”。
连续赋值和过程赋值
连续赋值语句用于组合逻辑的建模。等式左边是wire类型的变量。等式右边可以是常量、由运算符如逻辑运算符、算术运算符参与的表达。在initial或always外的assign赋值语句称为连续赋值语句,一般在描述纯组合电路时使用。
连续赋值:对应wire型变量
过程赋值:对应reg型变量
连续赋值
连续赋值语句用于组合逻辑的建模。等式左边是wire类型的变量。等式右边可以是常量、由运算符如逻辑运算符、算术运算符参与的表达。在initial或always外的assign赋值语句称为连续赋值语句,一般在描述纯组合电路时使用。
过程赋值
Verilog HDL 中提供两种过程赋值语句initial 和always 语句,用这两种语句来实现行为的建模。这两种语句之间的执行是并行的,若与语句块(begin ....end)相结合,则语句块中的执行是按顺序执行的。
数值表示
'd十进制,'h十六进制,'b二进制。
case语句
赋值
赋全0、全x或者全z可采用'b0、'bx或者'bz的方式;
0、x、z可以代表2进制的1bit,8进制的3bit,16进制的4bit,10进制的全部
赋全1可采用赋~0或赋-1的方式较为简洁。
~为按位取反,-1为全F。
Verilog里面module跟C语言里面function的区别
Verilog的module参数是module里面的parameter语句提供的;Verilog的module圆括号里面的东西是模块的输入输出;C语言圆括号里面的东西就是形参。
Verilog跟C语言有很多不一样的地方,注意观察和总结。
参数(parameter)型
看着类似宏定义!!不太一样,可以使用#()将参数传递到module里面。
在Verilog HDL中用parameter来定义常量,parameter型数据是一种常数型的数据,其说明格式如下:
parameter 参数名1=表达式,参数名2=表达式, …, 参数名n=表达式;
模块间传递参数:
module Decode(A,F);
parameter Width=1, Polarity=1;
……………
endmodule
module Top;
wire[3:0] A4;
wire[4:0] A5;
wire[15:0] F16;
wire[31:0] F32;
Decode #(4,0) D1(A4,F16);//其中的#(4,0)向实例化的Decode模块内部传递两个参数值
Decode #(5) D2(A5,F32);//其中的#(5)向实例化的Decode模块内部传递一个参数值
endmodule
神奇!defparam语句在一个模块中改变另一个模块的参数:
module Test;
wire W;
Top T ( );
endmodule
module Top;
wire W
Block B1 ( );
Block B2 ( );
endmodule
module Block;
Parameter P = 0;
endmodule
module Annotate;
defparam//Test模块里面实例化了一个Top模块为T,T里面实例化的Block模块B1、B2里面的参数都可以更改;
//注意原始的模块原型prototype申明里面的参数未改动
Test.T.B1.P = 2,
Test.T.B2.P = 3;
endmodule
关于顺序块语句和阻塞赋值
顺序语句是执行完一句再执行下一句,如果有非阻塞就要按照并行处理,再说几个概念:
并行,顺序:verilog主要的模块之间都是并行执行的,例如各个always之间、always与assign之间、assign之间,如果你在一个always中要对a赋值,而在另一个always中要使用a的值,这时候就要注意了,两者并行的,处理先后不能确定。你当前处理的a,是这个时钟被赋值的还是上一时钟被赋值的,意义可能完全不同,这就是并行需要考虑的问题。
而在always内部,一般使用了begin...end。这里面的内容都是顺序执行的,比如b=a; c=b,先执行一条,再执行下一条,那就是c=a了如果里面有两组if/else,就是先执行前一组,再执行后一组。但是如果是非阻塞,那就要特殊对待,多个非阻塞赋值是在一个块结束时一起执行的,比如b<=a; c<=b,那就跟之前不同了,当执行c<=b 时b还没有变化成a的值,因此这个赋值的结果是b被赋值前的值,这两条语句其实是独立的、并行的。好处是放得先后顺序没关系,只要在一个块内,随便写。这个不是很好理解,怎么说了begin...end之间是顺序,到了非阻塞就又变成并行执行的呢。不好理解也没办法, verilog就是这样,先告诉你是这样的,然后又告诉你这样行不通,习惯就好了,另外掌握几条原则:组合逻辑用阻塞赋值,时序逻辑用非阻塞赋值,同一个模块中不要既用阻塞又用非阻塞...
关于wire型和reg型
wire型的赋值必须要用assign;
reg型必须要在always块等语句内部进行赋值;
类似下面,即被定义成output wire型,又被定义成reg型的变量,需要当成reg型进行赋值。
module xxx
(
input clk,
input rst_n,
output reg [7:0] led,
output c
);
assign c = clk;
always
led[0:0] = clk;//直接用assign给led赋值会出现报错。
endmodule