序
水平太差了。
第一题
题干
第一题是一个组合逻辑的运用。给出一个位宽为 36 的输入 a ,代表一个 6 x 6 的矩阵。再给出一个位宽为 9 的输入 ,代表一个 3 x 3 的卷积核。要求输出一个位宽为 16 的输出 ans ,代表卷积后的 4 x 4 的矩阵。卷积的具体算法也给出了。由于输入是从低到高依次填入矩阵,所以矩阵元素 。
总的来说,第一题只要照葫芦画瓢即可。
第二题
题干
一个应用题类型的题。有一个 op 输入,表示四种操作。分别是往一个长度 128 的数组最低位添加元素(原有元素往高位移动 1 位)、读取指定位的元素、统计某元素的个数、以及保持上一周期输出。
思考
光看题干其实这题并不难,但是需要掌握很多东西才能做。首先是 Verilog 中数组的定义:
1 | <类型> <位宽声明> <数组名> <长度声明>; |
其中长度声明和位宽声明一样是 [高位:低位]。访问元素直接用下标即可。
然后是给数组初始化,在 initial 块和 reset 触发时,用 for 循环完成。在 reset 触发时,记得使用非阻塞赋值。
直到这时我才知道 initial 块应当被归类到组合逻辑中,使用阻塞赋值。
1 | integer i; |
题目的关键在于统计某元素的个数。如果说在时序逻辑块里面写这样的代码:
1 | integer i; |
这就错了。考虑某一上升沿,开始执行这一部分代码。cnt 初始化为 0。当满足 if 条件,cnt 为 0 的快照在赋值语句右侧,也就是 cnt = cnt(为0) + 1。只有当 always 完全结束,右侧才会赋给左侧。但 for 循环仍在继续,第二次满足 if 条件,又执行 cnt = cnt(为0) + 1,因此 for 循环无论执行多少次,等 always 结束,cnt 只会变成 1。再次执行 always,再次执行 for,再次结束后,cnt 会变成 2(如果 if 还能满足的话)。宏观上,这表现为 cnt 每个上升沿 + 1,而非一次性得到 cnt 真正的值。
有两种解决方案:
1 | //(换成阻塞赋值) |
第三题
题干
设计一个 Moore 机,对一个 json 序列进行有效性检测并统计键值对数量。每个周期内,串行输入 json 序列的一个字符。
json 的格式已简化如下:
1 | {"key1":"value1","key2":"value2","key3":"value3"} |
中间不会有任何空白字符。
对于所有输入的 json 序列,都满足以下格式规范:
-
每个 json 序列以左大括号 “{” 为开始,以右大括号 “}” 为结束。内部不包含嵌套大括号。
-
每个 json 序列内部由不定组键值对组成,每组键值对之间用逗号 “,” 隔开。可以没有键值对,也可以有多组键值对。最多包含 255 组键值对。
-
键值对的格式严格符合
"key":"value"。其中内部的key和value可被替换且至少有一个字符(不为空字符串),例如"test":"CO"。字符串由大小写字母和数字组成,不包含嵌套双引号,长度最多为 15 个字符。 -
相邻两个 json 序列间用若干个空格隔开(可以没有空格)。例如
{"fruit":"apple"} {}{"animal":"cat"}。
输出介绍:
-
cur_num: 输出当前 json 序列的键值对数目。具体的,在每个 json 序列输入完毕的下一个时钟上升沿更新该 json 序列中的键值对数目。 -
max_num: 输出历史输入的 json 序列的键值对数目的最大值。具体的,每次cur_num更新时,同步更新max_num为历史所有cur_num中的最大值。
特别情况:
-
正常情况下 json 序列键值对中的字符串不为空,即至少有一个字符。
-
若 json 序列键值对中的字符串出现空串,即
{"":"value"} 或 {"key":""},则该 json 序列为无效序列,其cur_num视为8'd0。 json 序列为有效序列当且仅当内部所有键值对的字符串都不是空串。 -
除了上述情况外,不会出现其他非法情况。即输入的 json 序列只有两种:1. 合法 json 序列,所有键值对的字符串均不为空串。 2. 无效 json 序列,至少有一个字符串是空串。
思考
这题要求多,状态也多。在输入 “{” 时,要考虑下一个是 “}” 的情况。这时需输出 cur_num 为 0,max_num 不变。如果输入左引号后接着输入右引号,那么整个 json 都是无效的,在输入 “}” 后同样 cur_num 为 0,max_num 不变。
在更新 max_num 的时候我遇到了问题。按理说,对于时序逻辑,我们必须避免使用阻塞赋值。但是此处我却不得不用上它:
1 | if (!isInvalid) cur_num <= cur_num + 1; |
倘若要更新 max_num,问题就出现了。非阻塞赋值把 cur_num 做一个快照,然后赋给 max_num。也就是说 max_num 拿到的是 + 1 前的 cur_num,导致出现延迟。倘若要实现理想的效果,则需要改为:
1 | if (!isInvalid) cur_num = cur_num + 1; |
isInvalid 用来标志该 json 串是否为空串或异常串。
这的确是对的。
不过我们还是遵从代码规范吧,不要用阻塞赋值,于是我改了这样一版:
1 | if (!isInvalid) cur_num <= cur_num + 1; |
这根本不对。我们就假设 cur_num = 0,max_num = 0 好了。现在我要把 cur_num + 1,然后赋给 max_num。但是由于非阻塞赋值的原因,在 if 条件比较的时候比的是两个 0,也就是说条件都进不去。
这时候读者朋友肯定要说了:“你把条件改成 cur_num >= max_num 不就行了?”诶,事实证明,这其实还是错的。如果我的 max_num = 0,然后碰到一个空串会怎样呢?首先 isInvalid = 1,进入 else cur_num <= 0 的条件。cur_num 都是 0 了,那肯定不需要更新 max_num 了。然而,max_num 拿到的是之前的快照,甚至还 + 1,这下更不更新就不好说了。所以最正确的写法是:
1 | if (!isInvalid) cur_num <= cur_num + 1; |
这题作为一个有限状态机的题,确实略微复杂,特别是需要深刻理解阻塞赋值和非阻塞赋值的差异。如果抛弃实用性来说,只要知道自己为什么要用阻塞赋值,在时序逻辑中使用是可以理解的。然而实用性大于天,所以还是用非阻塞吧~