前言
上一篇博客我们讲述了游戏的基本原理和具体的模块分工,虽然我们是说是按照自顶向下设计方式,但我们在分析好之后还是应该从底层模块开始,衔接好中间变量并确定我们需要的内容。
这篇博客开始就要详细的说明每一个模块的功能了。
按照游戏顺序来,我们应该先说随机数;从实现的难度上来说,我们也应该先声明一下随机数模块的问题。
随机数? 伪随机数?
学过C的都知道C的随机数虽然是叫做随机数,但是实际上完全就是通过一个种子进行不断的计算实现的伪随机数,或者我们添加一个时间种子。
在上一篇博客中我们提到了两种实现随机数的方式:
实现方法参考:取计数器的输出或使用线性反馈移位寄存器(LFSR)生成伪随机数);
这两种方法的本质上和C的时间种子有异曲同工之妙,接下来我们将进行阐述。
我们的随机数原理是这样的,首先主模块从一开始就定义一个15位的种子(因为我们是5个八进制数,也就是一共十五位的二进制数),在程序开始就让其进行自增,当我们按下s0,也就是随机数模块的使能信号有效,我们读入这个种子然后将其处理。(如果是处理,就属于LFSR的方式,不处理就是第一种的直接读取计数器的值,这里我们还是处理一下)
LFSR
线性反馈移位寄存器(Linear Feedback Shift Register),说明在这里。
说白了其实就是通过将一组数据不断按照周期进行循环移位,然后将其中几位进行异或的操作,打乱了种子的内容,看着像随机数而已。
这是通过LFSR输出一个15位二进制数的模块,输入为时钟信号和种子,以及使能端。
`timescale 1ns / 1ps
//一次生成一个五位八进制随机数
module RanGen(
input clk,
input en,
input [14:0]seed,
output [14:0]rand_num
);
reg [14:0]num = 15'b00000_00000_00000; //对输出赋值
assign rand_num = num;
reg if_in1 = 1'b0; //判断是否输入
reg if_in2 = 1'b0;
always@(posedge en)
begin
if_in1 <= ~if_in1;
end
always@(posedge clk)
begin
if(if_in1 != if_in2) //模块开始,读入种子
begin
num <= seed;
if_in2 <= if_in1;
end
else //LFSR
begin
num[0] <= num[14];
num[1] <= num[0];
num[2] <= num[1];
num[3] <= num[2];
num[4] <= num[3]^num[14];
num[5] <= num[4]^num[14];
num[6] <= num[5]^num[14];
num[7] <= num[6];
num[9] <= num[7];
num[8] <= num[8]^num[14];
num[10] <= num[9];
num[11] <= num[10];
num[12] <= num[11]^num[14];
num[13] <= num[12];
num[14] <= num[13];
end
end
endmodule
细节部分:
- 首先我们使用了一个reg寄存器为输入的wire型赋值,这是因为我们无法直接对输出进行初始化,所以需要使用另外一个变量进行处理。
- 一组的if_in使能端,这是因为我们需要在en使能信号上升沿进行处理,但是如果直接将posedge加在clk时钟信号中(使用if(en)的方式),我们知道实际按键的时间是很长的,我们的时钟信号又很快,导致我们在上层模块中取到的随机数就会相同(这几个数都是种子,因为赋值语句还没结束,num一直等于种子)
- 循环异或的在上面,这里个人感觉就只要打乱顺序就行了,应该没什么具体的要求。
接着说我们的游戏——模块调用
然后我是采用一个模块来调用这个子模块,通过时钟信号计时的方式每次读取一个随机数,一共五次。
(就五个周期的时间,所以上面的种子输入才使用了上升沿+一组使能信号来触发)
代码:
`timescale 1ns / 1ps
//生成随机数
module random(
input en,
input clk,
input [14:0]seed,
input s_four,
output reg [14:0]random_number0,
output reg [14:0]random_number1,
output reg [14:0]random_number2,
output reg [14:0]random_number3,
output reg [14:0]random_number4
);
wire [14:0]ret; //模块调用返回值
reg [14:0]seed_in; //输入种子
reg [2:0]counter = 3'b000; //计数器(因为非阻塞赋值,所以case需要从001开始)
reg if_in1 = 1'b0; //一组输入使能
reg if_in2 = 1'b0;
RanGen r(clk,en,seed_in,ret); //LFSR模块调用
always@(posedge en) //开始输入
begin
seed_in <= seed;
if_in1 <= ~if_in1;
end
always@(posedge clk or posedge s_four) //异步清零
begin
if(s_four)
counter <= 3'b000;
else if(if_in2 != if_in1)
begin
case(counter)
4'b0001:random_number0 <= ret;
4'b0010:random_number1 <= ret;
4'b0011:random_number2 <= ret;
4'b0100:random_number3 <= ret;
4'b0101:random_number4 <= ret;
endcase
if(counter != 3'b100)
counter <= counter+1;
else
if_in2 <= if_in1;
end
else;
end
endmodule
分析
- 首先我们没有上来就对counter == 3’b000开始取值,是因为要错开刚开始的输入(会导致随机数为0),但我们这样的取法第一位一定是种子。(可以通过从2开始,将上限改变的方式,错开第一个,但是因为种子本身有也有随机性,并没有必要)
(这里的截图是我们经过了放缩得到的,因为原来的计数器数字过大,仿真时间太长)