用Verilog写一个控制码类型的状态机,用来控制一个A+B+C+/-D类型的多周期数据通路,这个运算被分为R←A,R←R+B,R←R+C.R←R+ D.或R←R- D四个周期,启动运算后,只需要一定的硬件资源,在几个时钟周期内对有限的硬件进行重用从而完成需要的功能。控制码类型的状态机和FSM类型的状态机不一样,控制码类型的状态机需要提前列好控制码表,当然最为简单常用的其实是FSM类型的状态机
核心代码:
用于与其他模块交互以及和外部信号连接的Virtual_Lab_Top.v文件中的代码:
//状态机实验
`default_nettype none
module Virtual_Lab_Top //信号的输入和输出
(
input wire CLOCK,
input wire [4:0] BUTTON, //按键
input wire [35:0] SWITCH, //开关
output wire [35:0] LED, //指示灯
output wire [3:0] HEX0 //数码管
);
reg start,done_moore;
wire done;
wire [9:0]control;
//输入端口赋值给内部信号
wire [3:0] A = SWITCH[3:0];
wire [3:0] B = SWITCH[7:4];
wire [3:0] C = SWITCH[11:8];
wire [3:0] D = SWITCH[15:12];
wire mode = SWITCH[16];
wire start_asyn = BUTTON[0];
wire _reset =BUTTON[1];
//内部信号赋值给输出端口(数码管)观察
wire [3:0] result;
assign HEX0 = result;
assign LED[0] = done_moore;
//启动信号
always@(posedge CLOCK or posedge _reset or posedge done)
begin
if(_reset==1 || done==1)
start<=1'b0;
else
start<= start_asyn;
end
//计算完成信号
always@(posedge CLOCK or posedge _reset or posedge start_asyn)
begin
if(_reset==1 || start_asyn==1)
done_moore<=1'b0;
else
done_moore<= done;
end
controller u10(CLOCK,_reset,start,mode,control,done);
data_path u11(CLOCK,mode,A,B,C,D,control,result);
endmodule
用于初始化控制码存储器以及控制状态机运行的控制器模块controller.v文件:
module controller(
input CLOCK,_reset,start,
input mode,
output reg[4:0] control,
output reg done
);
reg [2:0]mpc;//control memory
(*ramgtyle="M512"*) reg[10:0]cm[0:5]; //control memory,using
//a built-in memory
//reg [0:5][10:0] cm;
reg [1:0] ccode;//branch type
reg [2:0] jump_address;//branch address
reg load;
//--Initialize the CM,---
initial begin
cm[0]=11'h400;//wait for start=1
cm[1]=11'h020;//initialize
cm[2]=11'h060;
cm[3]=11'h0E0;
cm[4]=11'h768;//if mode=0,done=1,goto 0
cm[5]=11'h3F8;//done=1,goto 0
end
//------------MUX----------
always@(*)
begin
case(ccode)
2'b00:load<=1'b0;//next instruction
2'b01:load<=1'b1;//unconditional jump
2'b10:if (start==0) //conditional jump if start=0
load<=1'b1;
else
load<=1'b0;
2'b11:if(mode==0) //conditional jump if mode=0
load<=1'b1;
else
load<=1'b0;
endcase
end
//---------MPC-----------
always@(posedge CLOCK or posedge _reset)
begin
if(_reset == 1)
mpc <=3'b000;
else
if(load==0)
mpc <= mpc+1;
else
mpc <= jump_address;
end
//--------CM----------------
always@(*)
begin
ccode=cm[mpc][10:9];
control=cm[mpc][8:4];
done=cm[mpc][3];
jump_address=cm[mpc][2:0];
end
endmodule
用于控制在特定状态下进行特定数据处理的数据处理模块data_path.v文件:
module data_path(
input CLOCK,mode,
input [3:0] A,B,C,D,
input [4:0]control,
output reg [3:0]result
);
reg [3:0] MUX1,MUX2,MUX;
wire m=control[0];
wire e=control[1];
wire s0=control[2],
s1=control[3],
s2=control[4];
always@(posedge CLOCK)//registers and cntr
begin
case({s2,s1})
2'b00:MUX1<=B;
2'b01:MUX1<=C;
2'b10:MUX1<=D;
2'b11:MUX1<=D;
default:MUX1 <= MUX1;
endcase
case(s0)
1'b0:MUX2<=A;
1'b1:MUX2<=4'h0;
default:MUX2<=MUX2;
endcase
end
always@(posedge CLOCK)
begin
if(e)
begin
if(m==1'b0)
begin
if(s0==1'b0)
result<=MUX2;
else if({s2,s1}==2'b10 && mode==1)
result<=result;
else
result<=result+MUX1;
end
else
result<=result-MUX1;
end
end
endmodule
另外也要接入内部时钟,跟我之前流水灯的文章中一样,采用偶分频方法将原有的时钟频率分频,参数RATIO调到100左右,不行再调大或调小时钟频率试试
WeLab虚拟面板设置图片:
start为启动键,mode置1为A+B+C-D,置0为A+B+C+D,A、B、C、D输入四个四位二进制运算数据,reset为重置按键,数码管显示的是运算结果。输入运算数据后确定运算模式然后按下启动键就能得到运算结果
注意:该工程写的是时序电路,时序电路非常关键的是最小时钟周期即最大时钟频率的确定,时钟周期太短会使得硬件无法在时钟周期内无法完成预定的工作就进入下一个周期,太长会使得整个运算还未完成软件就返回了不正确的显示数据,因为时钟周期环环紧扣,从而引发一系列的时序逻辑错误。若确定代码部分没有错误,但是却无法运行出正确的功能,那就应该是选定的时钟周期不合适。博主尚未掌握准确的电路延时计算方法,所以只能耐心地调试时钟分频系数来调大或调小时钟周期,试出一个合适的时钟周期