# P1 课下学习
UPD: 2021/10/23
# 字符串自动机的简单做法
今天参考 roife.github.io 上的做法,学习了 Verilog 字符串自动机的一点骚操作
对于后缀自动机,我们可以不去做状态转移,而是考虑建立一个字符缓冲区 reg [127:0] buffer
,每次读到的字符都存到缓冲区中,然后去时刻检查缓冲区的字符串后缀是否符合题意,这样复杂的状态机问题就被简化成了简单的字符串匹配问题了(之前的感觉都白学了...)
但是这个方法好像不是万能的,目前来看只能匹配定长的、不连续的字符串,下举两例说明:
- 比如
[a-zA-Z]+[0-9]+
这样的不定长匹配就不行(因为你不知道输入究竟有多长) - 比如上次 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:打算明天继续了解的内容
- Verilog 的 function,task,$signed 和 $unsigned,向量信号的赋值顺序
- 尝试找一找字符串自动机的写法套路
- 写自动评测机
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
>Editors
改Editor
为Custom
,然后在后面的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"
即可