# P1 课下学习

UPD: 2021/10/23

# 字符串自动机的简单做法

今天参考 roife.github.io 上的做法,学习了 Verilog 字符串自动机的一点骚操作

对于后缀自动机,我们可以不去做状态转移,而是考虑建立一个字符缓冲区 reg [127:0] buffer ,每次读到的字符都存到缓冲区中,然后去时刻检查缓冲区的字符串后缀是否符合题意,这样复杂的状态机问题就被简化成了简单的字符串匹配问题了(之前的感觉都白学了...

但是这个方法好像不是万能的,目前来看只能匹配定长的、不连续的字符串,下举两例说明:

  1. 比如 [a-zA-Z]+[0-9]+ 这样的不定长匹配就不行(因为你不知道输入究竟有多长)
  2. 比如上次 Pre 课上的 cscore 匹配问题,如果输入是 cscorecscore ,是要一次匹配完的,这种就不行,因为 buffer 的后缀中会出现 recsc 这种情况,没办法跟 cscorerecscore 这种情况区分

把用缓冲区做的 BlockChecker 代码贴一下

`timescale 1ns / 1ps
module BlockChecker(
    input clk,
    input reset,
    input [7:0] in,
    output reg result
    );
    function [7:0] lower;
    input [7:0] in;
        if(in >= "a" && in <= "z") lower = in;
        else if(in >= "A" && in <= "Z") lower[7:0] = in[7:0] + ("a" - "A");
        else lower = in;
    endfunction
    reg [127:0] buffer;
    reg [31:0] mismatch_cnt;
    reg fail;
    initial begin
        buffer = " ";
        mismatch_cnt = 0;
        fail = 0;
        result = 1;
    end
    always @(posedge clk or posedge reset) begin
        if(reset) begin
            buffer <= " ";
            result <= 1;
            fail <= 0;
            mismatch_cnt <= 0;
        end else begin
            buffer <= {buffer[119:0], lower(in)};
        end
    end
    always @(*) begin
        if(!fail) begin
            if(buffer[47:0] == " begin") mismatch_cnt = mismatch_cnt + 1;
            else if(buffer[55:8] == " begin" && buffer[7:0] != " ") mismatch_cnt = mismatch_cnt - 1;
            else if(buffer[31:0] == " end") mismatch_cnt = mismatch_cnt - 1;
            else if(buffer[39:8] == " end" && buffer[7:0] != " ") mismatch_cnt = mismatch_cnt + 1;
            else if(buffer[39:8] == " end" && buffer[7:0] == " " && $signed(mismatch_cnt) < 0) fail = 1;
            else mismatch_cnt = mismatch_cnt;
        end
    end
    always @(*) begin
        result = ~fail && (mismatch_cnt == 0);
    end
endmodule

# 字符串自动机的 Testbench 简单写法

每次写字符串自动机的 testbench 都得写成这样

#2; reset = 0; in = "B";
#2; in = "e";
#2; in = "g";
#2; in = "I";
#2; in = "n";
#2; in = " ";
#2; in = " ";
#2; in = "e";
//... 此处省略 40 + 行

非常的讨厌,所以今天 get 到了简单用循环移位去做的简单写法(以 BlockerChecker 的 Testbench 为例)

`timescale 1ns / 1ps
module BlockChecker_tb;
	// Inputs
	reg clk;
	reg reset;
	reg [7:0] in;
	// Outputs
	wire result;
	reg [0:1023] data;
	integer i = 0;
	
	// Instantiate the Unit Under Test (UUT)
	BlockChecker uut (
		.clk(clk), 
		.reset(reset), 
		.in(in), 
		.result(result)
	);
	always #1 clk = ~clk;
	initial begin
		// Initialize Inputs
		clk = 0;
		reset = 1;
		in = 0;
		data = "begin enDbegin xyzz eNd BeGin begin end endbegin end ";
		while(!data[0:7]) data = data << 8;
		#2;
		reset = 0;
		for(i = 0; i < 53; i=i+1) begin
			in[7:0] = data[0:7];
			data = data << 8;
			#2;
		end
		$finish;
	end
endmodule

可以看到,定义了一个 reg [0:1023] data注意,这里一定是从小端开始,否则你读到 in 端口里面的数据就是反过来的 ASCII 码就不对了),然后给 data 用一串优雅的字符串直接赋上初值,由于我们赋值默认被挤到了最右端,所以用 while(!data[0:7]) data = data << 8; 一直移位,直到找到我们想要的数据为止,然后用一个 for 循环,直接开始把值打到 in 端口里面去,非常的方便

# ISE 和 ISim 的使用

由于 Pre 课下时,我机智的配好了 VSCode 环境,所以一直没用 ISE 写 Verilog 代码,但是在 Pre 上机时,我被机房的辣鸡机器坑了一下,于是了解了一下 ISE 和 ISim 的进阶用法(吐槽:为啥机房的 VSCode 没有 Verilog 插件!!!)

  • 首先 ISE 自带的编辑器非常难用,可以考虑给他换成 Notepad++ (这个自带高亮), VSCode (机房的不一定带高亮插件)

  • ISim 第一次仿真结束后会提示你保存个什么东西,那个是波形配置文件,一定保存一下(我知道谁肯定都不想每次调一下信号的大小,再把临时变量一个个添加到观察窗口去

  • **ISim 调试发现仿真波形的问题,不需要把 ISim 窗口关掉,然后再 ISE 里面改完重新编译,这样太慢了!!!** 更机智的做法是直接再编辑器里面改好代码,记得保存,然后点击右上角的 Re-Launch 按钮就行了,重新加载的波形就是新代码的波形了,Testbench 和模块本身都可以这样改,总之打开 ISim 之后就别关掉就对了

TODO:打算明天继续了解的内容

  1. Verilog 的 function,task,$signed 和 $unsigned,向量信号的赋值顺序
  2. 尝试找一找字符串自动机的写法套路
  3. 写自动评测机

UPD:2021/10/24


# 向量赋值

Verillog 还支持指定 bit 位后固定位宽的向量域选择访问。

  • [bit+: width] : 从起始 bit 位开始递增,位宽为 width
  • [bit-: width] : 从起始 bit 位开始递减,位宽为 width
// 下面 2 种赋值是等效的
A = data1[31-: 8];
A = data1[31:24];
// 下面 2 种赋值是等效的
B = data1[0+: 8];
B = data1[0:7];

# 字符串

字符串保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。因此寄存器变量的宽度应该足够大,以保证不会溢出

字符串不能多行书写,即字符串中不能包含回车符。** 如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;** 如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据。例如,为存储字符串 "run.runoob.com", 需要 14*8bit 的存储单元

reg [0:14*8-1] str;
str = "run.runoob.com";

# 函数

函数的定义和使用类似于寄存器,如下所示

function [7:0] lower(
    input [7:0] in;
)
    if(in >= "A" && in <= "Z") lower = in + "a" - "A";
    else lower = in;
endfunction

使用时直接用

buffer[127:0] = {buffer[119:0], lower(in)};

# 多个模块利用 for 循环例化

不能直接在 for 循环里面连接模块,用下面的方法( generate...end

full_add uut0(.A(a[0]),
              .B(b[0]),
              .C(c[0]),
              .Cin(cin==1'b1 ? 1'b1 : 1'b0),
              .Cout(temp[0])
             );
genvar i;
generate
    for(i=1; i<8; i=i+1) begin: generate_adder		//for 循环没有 i++ 了,begin 后面一定要加一个 label
        full_add uut1(.A(a[i]),
                      .B(b[i]),
                      .C(c[i]),
                      .Cin(temp[i-1]),
                      .Cout(temp[i])
                     );
    end
endgenerate
assign cout = temp[7];

UPD:2021/10/27


# ISE 改默认编辑器为 VSCode/Notepad++

今天上机意外发现机房的 VSCode 有 Verilog 插件,因此可以利用改配置的方法让 ISE 调用 VSCode 的编辑器,从而实现代码补全和语法高亮等功能

方法:

  • VSCode :打开 ISE,找到 Edit > Preference... > ISE General > EditorsEditorCustom ,然后在后面的 Command Line syntax 中输入 "{G:\Program Files\Microsoft VS Code\Code.exe} -r . -r -g $1:$2" (这里把花括号的内容替换成你的 VSCode 的安装位置即可)
  • Notepad++ :先安装,然后在上述位置输入 "{C:\Program Files\Notepad++\notepad++.exe} $1 -n$2" 即可