- 规划状态,确定是Mealy型还是Moore型,这决定了状态数多一个还是少一个。
- 画出状态转移图
- 规划输出端口是Moore型还是Mealy型
- 确定端口的延迟,如果带上寄存器,可以延迟1个周期
以字符串匹配“abc”为例,
如果是Moore型状态机,一共4个状态,可以记为S0 -> Sa -> Sb -> Sc
如果是Mealy型状态机,一共3个状态,可以记为S0 -> Sa -> Sb
Moore型会多一个Done(匹配完成)的状态,就是Sc,这个状态直接和输出关联;
Mealy型在Sb状态时,input == c ,output 直接等于1,
这就叫做:“当输入为c时,输出直接为1”
当一个时钟过来后,c被“打”进去,此时状态反而回到原点。
首先,异步复位是简单的
当CLR(clear)为1时,reg直接变成0
错误的同步复位:
当clk为1时,要是CLR来个小抖动,直接复位,和要求“在clk上升沿时复位不同” :(
异步复位到非0状态:
这个地方就有trick了,直接贴上答案:
这时的register存储的不一定是真正的state,下一个state计算好后,等着时钟周期一来,更新register
和Logisim不同,Verilog配合ISE,允许我们设计更加复杂的状态机。
我们设计时,要注意下列点:
-
设计好状态转移。当我们使用Verilog时, 设计的状态机比Logisim复杂更多,通过analyze circuit来构建状态转移逻辑太麻烦,
所以要我们先设计好状态 -
设计好内置的reg,这些reg会在一定的条件下被触发,
而往往result就是这些reg的组合逻辑输出。
这个和Logisim正好反过来,异步复位写的麻烦一点
rst_n 指的是 reset when negative,即低有效的复位
同步复位,which is easy
always @(posedge clk)
if(!rst_n) begin
RESET;
end
else begin
show time;
end
异步复位,
这里以reset when negative为例
posedge clk后面接着的是 negedge rst_n
如果reset when positive,接着的是posedge rst
always @(posedge clk or negedge rst_n)
if(!rst_n) begin
RESET;
end
else begin
show time;
end
function <返回值的类型或范围>(函数名);
<端口说明语句> // input XXX
<变量类型说明语句> // reg YYY
......
begin
<语句>
......
函数名 = ZZZ; // 函数名就相当于输出变量;
end
endfunction
for循环的写法
integer i;
for (i = 0; i < MAX; i = i + 1) begin
// do what u want
end
这里,我并不是要强调for循环的语法规则,
而是想说两个重点:
-
for循环一般在组合逻辑中作为一种函数出现,
和always(*)相伴,如果不放在always块中,而是直接出现,
会报错。 -
for循环一般用于计数,而计数时,会有中间变量,
那么每次启动for循环用于计数时,
要保证中间变量被清零了。
P2上有同学栽了跟头,我P4直接挂在这上面。
当然,可以用函数来代替for循环,
不过,经我测试,函数的for循环,
中间变量在loop中,也得清零,否则会累加。
附上一个正常的计数代码段,
功能是判断RegRead1这个wire里“1”的个数是不是奇数:
wire odd; // is odd??
reg[31:0] num; // how many "1" in RegRead1??
initial begin
num = 0;
end
integer i;
always@(*) begin
num = 0;
// loop start, intermediate variable set to 0
for(i = 0; i < 32; i = i + 1) begin
if(RegRead1[i] == 1) begin
num = num + 1;
end
end
end
assign odd = num[0]
我们平常用什么?:
.space
专门用来分配int型的变量,后面的单位是字节(byte)
比如:
array: .space 40
40个字节,足够存10个integer了。
.asciiz
专门用来存一个字符串,这个和python的想法类似,没有char类型的变量
如:
str: .asciiz "Hello World"
把字符串的声明放在最后。
因为前面放了array,常常用到lw、sw来操作array,array的地址必须是4的倍数
具体的流程是这样的:if-then-else
实际用label实现是这样的:if-(else->end)-(then->end)
这和C语言的不同之处在于:
C是条件判断为False时,跳转到else
MIPS是条件为True时跳转到then,else直接接在判断语句后面
if:
beq $, $, then
else:
do sth
j if_end
then:
do sth
j if_end
if_end:
值得一提的是,实现跳转,直接由beq、bgt,而不必使用中间变量,
千万不要忘了,最后的if_end
以
for(i = 0; i < n; i++>){}
为例
.text
li $t0, 0
Loop:
beq $t0, $a0, Loop_end
do sth
add $t0, $t0, 1
j Loop
Loop_end:
done
循环是从load immediate开始,再以Loop_end结束
循环不是从Loop开始的,这是认知的一个误区
一定不要忘记了:
-
整个循环以 li $t0, 0开始的
-
do sth 后,一定要记得i++, 然后再jump到开头的Loop去
-
同样地,判断和跳转直接用扩展指令
-
使用了.space声明了array。单位是byte
-
store word和load word
-
store byte和load byte
用了数组,肯定存很多数,然后再取出来
展示一下如何存integer:
Store:
beq $t0, $s0, Store_end
li $v0, 5
syscall
sll $t1, $t0, 2 # t1 = t0*4
sw $v0, array($t1)
add $t0, $t0, 1
j Store
在C语言中一个i,就能完成 int 或者char 的存取,
在MIPS中
integer要中间变量i*4,
char可以直接用i来存取
因为MIPS最小的存储单元是byte
多重循环,很容易让人看不清结构 现在以两个嵌套的for循环为例
for(i=0; i<n; i++){
for(j=0; j<n; j++){
Do Something
}
}
.text
li $t0, 0
Loop_i:
beq $t0, $s0, Loop_i_end
li $t1, 0
Loop_j:
beq $t1, $s0, Loop_j_end
Do Something
add $t1, 1
j Loop_j
Loop_j_end:
add $t0, $t0, 1
j Loop_i
Loop_i_end:
End
还是要强调,循环不是从Loop开始, 而是从load immediate开始
寄存器的分配:
-
传递参数的寄存器:
$a0 - $a3
-
传递返回值的寄存器:
$v0 - $v1
-
返回地址的寄存器,保存调用者的地址
调用函数的需求:
将程序员变量 $s
存到 $a
把 $a
传入,返回的值存储在 $v0
里。
调用的前后,$s
的值不变。
所以:
-
调用前,自己保留好
$s
-
调用后,把改变好的
$a
请回来。
函数内部实现:
-
通过
$a
计算好返回值; -
把返回值存到
$v0
-
不建议用
$v0
和$a0
,因为可能会有输入和输出。 -
store word 和 load word 不要写反了
最关键的地方,在于子函数。
子函数就是函数内部再进入一级的函数
为了把C翻译成汇编,首要的就是,写出一个“清晰”的C代码。
什么是“清晰”的C代码?
我们不需要关心C代码如何实现了我们的需求,
只要关心C代码翻译成汇编时是否易懂。
Factorial-1:
int fac(n):
{
if(n == 1){
return 1;
}else{
return n*fac(n-1)
}
}
或许这是一个明了的C代码,
但对于翻译汇编的人而言,这并不是一个“清晰”的C代码
Factorial-2:
int fac(n){
if(n == 1){
return 1;
}else{
t = n; // n : $a
// this step : store $a
n = fac(n-1); // after a jal-ra, n will change
n = n * t; // what we need return is orignal n muti fac(n)
return n;
}
}
递归总是有终止条件,终止条件翻译成汇编比较简单
我们为什么很难把递归的C和汇编联系在一起?
在我看来,是因为:
递归在汇编中,“传值”
递归在C语言中,“传参”
本来是作为参数的 $a ,最后成为了函数返回值的容器
这样的“弊端”是,传入递归的参数个数不能超过寄存器个数
而在C语言中,从栈的角度考虑,参数传进去,返回来的值和参数无关
如果使用 $v
来保存最后的返回值,那么 $a
也还是会被改变
可以在“推荐阅读.md”中获取相应资料对应的互联网链接