/******************************p215 15.2 1101序列检测********************************/
/*----------------------------------
Filename: squence_detector.v
Function: 检测序列1101(可重叠检测)
Author: Coin
Date: 2020-07-19 15:06:46
-----------------------------------*/
module squence_detector(clk, reset, d, y);
//定义输入输出端口
input clk, reset, d;
output y;
//内部寄存器及连线定义
reg [4 : 0] current_state, next_state;
wire y;
//状态定义
//其实化简状态图后,s4与s1的状态转移去向和输出均相同,可以合并
parameter s0 = 5'b00000, s1 = 5'b00010, s2 = 5'b00100, s3 = 5'b01000, s4 = 5'b10000;
//时序逻辑实现状态转移
always@(posedge clk or negedge reset)
begin
if(!reset) current_state <= s0;
else current_state <= next_state;
end
//组合逻辑实现状态转移条件判断
always@(current_state or d)
begin
case(current_state)
s0: next_state = d ? s1 : s0;
s1: next_state = d ? s2 : s0;
s2: next_state = d ? s2 : s3;
s3: next_state = d ? s4 : s0;
s4: next_state = d ? s1 : s0;
endcase
end
//时序逻辑实现输出,Moore机,y的变化比current_state的变化晚一拍
always@(posedge clk or negedge reset)
begin
if(!reset) y <= 1'b0;
else
case(current_state)
s0: y <= 1'b0;
s1: y <= 1'b0;
s2: y <= 1'b0;
s3: y <= 1'b0;
s4: y <= 1'b1;
endcase
end
//本例中由于只有一个输出,也可用assign语句实现,y的变化与current_state同步
assign y = (current_state == s4) ? 1 : 0;
endmodule
/*---------------------------------------
Filename: squence_detector_t.v
Function: 1101(可重叠)序列检测器测试模块
Author: Coin
Date: 2020-07-19 16:34:22
---------------------------------------*/
`timescale 1ns/1ns
`define halfperiod 5
module squence_detector_t;
//定义内部连线及寄存器
reg clk, reset;
reg [23 : 0] data; //定义一个24位的寄存器用于存放待测数据码
wire d, y;
//生成测试信号逻辑
initial
begin
clk = 0; reset = 1;
#5 reset = 0;
#20 reset = 1;
data = 24'b0011_1110_1001_1011_1010_0100;
#(`halfperiod * 200) $stop; //执行100个时钟周期
end
//产生是时钟信号
always #`halfperiod clk = ~clk;
//用循环移位的方式待测数据码流(盘佛珠)
always@(negedge clk) #5 data = {data[22 : 0], data[23]};
assign d = data[23]; //将最高位输入到序列检测器中
//实例化
squence_detector m0(.clk(clk), .reset(reset), .d(d), .y(y));
endmodule
/******************************p215 15.1 10010序列检测********************************/
module sequence_det(
clk
, rst
, din
, moore_o
, mealy_o
);
input clk ;
input rst ;
input din ;
output reg moore_o ;
output reg mealy_o ;
reg [4:0] state_c,state_n;
//=====================================================\
//**************** Define Parameter **************
//======================================================/
//状态编码:one-hot
localparam Idle = 5'b00001;
localparam S1 = 5'b00010;
localparam S10 = 5'b00100;
localparam S100 = 5'b01000;//S10010可与之合并
localparam S1001 = 5'b10000;
//状态转移
always@(posedge clk or posedge rst) begin
if(rst) state_c <= Idle;
else state_c <= state_n ;
end
//次态生成
always@(*) begin
if(rst) state_n = Idle;
else begin
case(state_c)
Idle : state_n = din? S1 :Idle ;
S1 : state_n = din? S1 : S10 ;
S10 : state_n = din? S1 : S100 ;
S100 : state_n = din? S1001 : Idle ;
S1001 : state_n = din? S1 : S10010 ;
default: state_n = Idle ;
endcase
end
end
/*Moore Machine
always@(posedge clk or posedge rst) begin
if(rst) moore_o <= 1'b0;
else begin
case(state_c)
S10010: moore_o <= 1'b1 ;
default: moore_o <= 1'b0 ;
endcase
end
end
*/
//Mealy Machine
always@(posedge clk or posedge rst) begin
if(rst) mealy_o <= 1'b0;
else mealy_o <= (state_c = S1001 && din ==0)?1'b1:1'b0;
//输出不仅与state_c有关,还与当前的输入有关
end
endmodule
top模块:M0+M1+M2
M0:产生时钟和data[3:0]数据输入信号,相当于测试模块;但是M1的输出ack信号是M0的输入,故又不只是测试模块,因此也要并入top中
M1:4bit并行数据转换为4位连续为一帧的的串行数据
M2:对4位连续的串行数据帧译码,输出16位并行译码信号
M1的通信协议
scl为不断输出的时钟信号;
如果scl为高:sda由1变0时刻,串行数据流开始,sda由0变1时刻,串行数据结束。
sda信号的串行数据位,必须在scl为低电平时变化。
在scl为高时,sda的变化用于指示数据流的开始或结束;在scl为低时,sda的变化才代表数据的传递。
/******************************p217 15.4 并转串+串行帧译码输出M1模块********************************/
module p2sda(sclk,scl,data,sda,rst_n,d_ena);
input sclk,rst_n,d_ena;
input [3:0] data;
reg [3:0] databuf;
output scl,ack;
inout sda;
reg link_sda;
reg sda_buf;
reg [7:0] state_c,state_n;
assign sda = link_sda ? sda_buf : 1'bz; //link_sda控制sda_buf输出到串行总线sda上,电平敏感
always@(posedge d_ena) begin //请求新数据时,在d_ena上升沿处将并行数据存入databuf;边沿敏感
databuf <= data;
end
parameter READY = 8'b0000_0000,
START = 8'b0000_0001,
BIT1 = 8'b0000_0010,
BIT2 = 8'b0000_0100,
BIT3 = 8'b0000_1000,
BIT4 = 8'b0001_0000,
BIT5 = 8'b0010_0000,
STOP = 8'b0100_0000,
IDLE = 8'b1000_0000;
//由并行输入时钟 二分频产生串行输出时钟scl,scl的周期为sclk的2倍
always@(posedge sclk or negedge rst_n) begin
if(!rst_n) scl <= 1;
else scl <= ~scl;
//主状态机:产生控制信号,根据databuf中保存的数据,按照通信协议产生sda串行信号
//时序逻辑状态转移
always@(posedge sclk or negedge rst_n) begin
if(!rst_n) state_c <= READY;
else state_c <= state_n;
end
//组合逻辑
always@(*) begin //我认为本不需要BIT5来提前把sda拉低,再去让sda产生结束高电平,我猜想是在查看仿真信号时有视觉上方便的地方,一个凸起的高脉冲便于查找
case(state_c)
READY: state_n = d_ena ? START : state_c; //d_ena==1表示 并行数据databuf已到达,进入开始状态START
START: state_n = (scl && d_ena) ? BIT1 : state_c; //scl为高 且 并行数据在传输,则sda开始串行数据流传输第一个bit, 即进入下一状态BIT1
BIT1: state_n = !scl ? BIT2 : state_c; //scl为低时,送出串行数据最高位databuf[3](在输出always块操作),并进入下一状态BIT2
BIT2: state_n = !scl ? BIT3 : state_c; //scl为低时,送出串行数据 databuf[2](在输出always块操作),并进入下一状态BIT3
BIT3: state_n = !scl ? BIT4 : state_c; //scl为低时,送出串行数据 databuf[1](在输出always块操作),并进入下一状态BIT4
BIT4: state_n = !scl ? BIT5 : state_c; //scl为低时,送出串行数据最低位databuf[0](在输出always块操作),并进入下一状态BIT5
BIT5: state_n = !scl ? STOP : state_c; //scl为低时,送出数据0,先把sda拉低,为产生sda高电平结束信号做仿真信号查看时的视觉准备(在输出always块操作),并进入下一状态STOP
STOP: state_n = scl ? IDLE : state_c; //scl为高 进入IDLE状态,不是READY状态,这个状态是为了将输出信号link_sda拉低,留出一拍的信号复位时间
IDLE: state_n = READY; //IDLE是一拍信号复位时间,在这个状态里把link_sda拉低
default:state_n = 8'bz;
endcase
end
//时序逻辑输出 sda_buf:data的串行化输出信号/link_sda/ack:向M0请求信数据的信号
always@(negedge sclk or negedge rst_n) begin //sclk下降沿控制主状态机state;而scl上升沿在start_flag为1控制mstate
if(!rst_n) begin
sda_buf <= 0;
link_sda<= 0;
ack <= 0;
end
else begin
case(state_c)
READY: begin link_sda<=d_ena?1:0; ack<=!d_ena?1:0; end //利用ack信号要求新的4位数据输入
START: begin sdabuf <=0; end
BIT1: begin sdabuf <=(!scl)? databuf[3]:sdabuf; ack<=0; end
BIT2: begin sdabuf <=(!scl)? databuf[2]:sdabuf; end
BIT3: begin sdabuf <=(!scl)? databuf[1]:sdabuf; end
BIT4: begin sdabuf <=(!scl)? databuf[0]:sdabuf; end
BIT5: begin sdabuf <=(!scl)? 0 :sdabuf; end
STOP: begin sdabuf <= scl ? 1 :sdabuf; end
IDLE: begin link_sda<=0; end
default: begin sdabuf <=1; link_sda<=0; end
endcase
end
end
endmodule
/******************************p217 15.5 并转串+串行帧译码输出M2模块********************************/
//按照协议接受串行数据源,对串行数据帧进行处理,并按照数据帧的值在相应位输出高电平
module out16hi(scl,sda,outhigh);
input scl,sda;
output [15:0] outhigh;
reg [4:0] mstate_c,mstate_n; //对串行数据的输入进行状态编码
reg [3:0] pdata,pdatabuf; //记录串行数据输入的一帧4bit,串行数据sda->pdata->一帧传输结束后寄存至pdatabuf->译码为outhigh
reg STARTFLAG,ENDFLAG; //数据开始和结束标志位
//串行数据传输开始标志位(其实是传输进行标志位,传输全程STARTFLAG都是1)
always@(negedge sda) begin //检测到sda下降沿,则等待scl拉高,然后开始接收串行数据
if(scl) STARTFLAG <= 1; //串行数据开始标志位
else if(ENDFLAG) STARTFLAG <= 0; //若ENDFLAG==1,STARTFLAG归零
end
//串行数据传输结束标志位
always@(posedge sda) begin
if(scl) begin //检测到sda上升沿,则等待scl拉高,然后结束接收串行数据,ENDFLAG=1,并将pdata传入寄存器??
ENDFLAG <= 1;
pdatabuf<= pdata;
end
else
ENDFLAG <= 0;
end
parameter MBIT0 = 5'b0_0001,
MBIT1 = 5'b0_0010,
MBIT2 = 5'b0_0100,
MBIT3 = 5'b0_1000,
MBIT4 = 5'b1_0000;
//主状态机,接收串行数据帧,并寄存在pdata
//时序逻辑-状态转移
always@(posedge scl) begin
mstate_c <= mstate_n;
end
//组合逻辑-状态说明
always@(posedge scl) begin
if(STARTFLAG)
case(mstate_c)
MBIT0: mstate_n = MBIT1;
MBIT1: mstate_n = MBIT2;
MBIT2: mstate_n = MBIT3;
MBIT3: mstate_n = MBIT4;
MBIT4: mstate_n = MBIT0;
default:mstate_n = 5'bz;
end
//时序逻辑-输出 pdata
always@(posedge scl) begin
case(mstate_c)
MBIT0: begin pdata[3] <= sda; $display("输出pdata[3]"); end
MBIT1: begin pdata[2] <= sda; $display("输出pdata[2]"); end
MBIT2: begin pdata[1] <= sda; $display("输出pdata[1]"); end
MBIT3: begin pdata[0] <= sda; $display("输出pdata[0]"); end
MBIT4: begin $display("暂停输出"); end
default:begin $display("bug"); end
end
//将接收的串行数据帧译码为相应位的高电平
always@(pdatabuf) begin
case(pdatabuf)
4'b0001: outhigh = 16'b0000_0000_0000_0001;
4'b0010: outhigh = 16'b0000_0000_0000_0010;
4'b0011: outhigh = 16'b0000_0000_0000_0100;
4'b0100: outhigh = 16'b0000_0000_0000_1000;
4'b0101: outhigh = 16'b0000_0000_0001_0000;
4'b0110: outhigh = 16'b0000_0000_0010_0000;
4'b0111: outhigh = 16'b0000_0000_0100_0000;
4'b1000: outhigh = 16'b0000_0000_1000_0000;
4'b1001: outhigh = 16'b0000_0001_0000_0000;
4'b1010: outhigh = 16'b0000_0010_0000_0000;
4'b1011: outhigh = 16'b0000_0100_0000_0000;
4'b1100: outhigh = 16'b0000_1000_0000_0000;
4'b1101: outhigh = 16'b0001_0000_0000_0000;
4'b1110: outhigh = 16'b0010_0000_0000_0000;
4'b1111: outhigh = 16'b0100_0000_0000_0000;
4'b0000: outhigh = 16'b1000_0000_0000_0000;
default: outhigh = 16'bz;
endcase
end
/******************************p222 15.6 并转串+串行帧译码输出M0模块********************************/
//类测试模块:数据初始化和生成,时钟模块生成
//当有ack==1信号来临,data[3:0]存入data_buf,data_buf通过state由高位到低位写入sda_buf
`timescale 1ns/1ns
`define halfperiod 10
module sigdata(ack,rst_n,data,ack,sclk);
input ack;
output rst_n,sclk;
reg rst_n,sclk;
output reg [3:0] data;
always #(`halfperiod) slck=~sclk;
initial begin
rst_n = 0;
#10 rst_n = 1;
#(`halfperod*2+3) rst_n = 0;
end
//测试信号data输入4'b0000
initial begin
sclk = 0;
data = 0;
#(`halfperod*100) $stop;
end
//每收到一个ack正边沿信号,等候半个时钟加一丢丢然后将data+1
always@(posedge ack) begin
#(`halfperiod/2+3) data=data+1;
end
/******************************p223 15.7 并转串+串行帧译码输出top模块********************************/
`timescale 1ns/1ns
`include "p2sda.v"
`include "out16hi.v"
`include "sigdata.v"
module top;
reg rst_n;
wire [3:0] data;
wire [15:0]outhigh;
wire sclk,scl,sda;
sigdata M0(.sclk(sclk),.data(data),.d_ena(e_ena));
p2sda M1(.sclk(sclk),.d_ena(d_ena),.scl(scl),.sda(sda),.rst_n(rst_n),.data(data));
out16hi M2(.scl(scl),.sda(sda),.outhigh(outhigh));