MIPS微系统设计文档
如果你能看到并用到这篇文章,证明你已经是一个成熟的6系计组人了(笑
但我还是要说最后一次实验难度较大且考期紧张,适当的取舍也并非不可;如果你下决心要做,就不要被最后一次实验的困难打倒。
模块概览
整体架构
mips
TC0
Bridge
Key
Switch
LED
DTube
IM
DM
mips_uart
uart_rx
uart_rd
CPU
D_Controller
E_Controller
M_Controller
W_Controller
Blocker
D_Controller
E_Controller
M_Controller
E_MDU
MulDivUnit
MulUnit
DivUnit
F_PC
FD_REG
D_GRF
D_ext
D_cmp
D_NPC
DE_REG
E_ALU
EM_REG
M_DM_IN
M_DM_OUT
CP0
MW_REG
部分新增模块实现
TC0
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
Addr | 30 | in | 地址输入 |
WE | 1 | in | 写使能信号 |
Din | 32 | in | 数据输入 |
Dout | 32 | out | 数据输出 |
IRQ | 1 | out | 中断请求信号 |
TC0即系统计时器,可使用写入指令控制计时模式和计时起点,在到达特定时间时,将置高 IRQ 向系统发出中断信号。
Bridge
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
m_data_addr | 32 | out | DM或外设的读写地址 |
m_data_rdata | 32 | in | 从外置DM直接读取的待选用的数据 |
m_data_wdata | 32 | out | 写入DM或外设的数据 |
m_data_byteen | 4 | out | 经处理的字节使能信号 |
temp_m_data_addr | 32 | in | DM或TC的读写地址 |
temp_m_data_rdata | 32 | out | 经过选择从DM或外设中读出的数据 |
temp_m_data_wdata | 32 | in | 写入DM或外设的数据 |
temp_m_data_byteen | 4 | in | 未经处理的字节使能信号 |
TC0_WE | 1 | out | TC0写使能信号 |
TC0_Addr | 32 | out | TC0读写地址 |
TC0_Din | 32 | out | 可能写入TC0的数据 |
TC0_Dout | 32 | in | 根据地址从TC0中直接读出的数据 |
UART_WE | 1 | out | UART写使能信号 |
UART_Addr | 32 | out | UART读写地址 |
UART_Din | 32 | out | 可能写入UART的数据 |
UART_Dout | 32 | in | 根据地址从UART中直接读出的数据 |
DTube_data_byteen | 4 | out | 数码管写字节使能信号 |
DTube_Addr | 32 | out | 数码管读写地址 |
DTube_Din | 32 | out | 可能写入数码管的数据 |
DTube_Dout | 32 | in | 根据地址从数码管中直接读出的数据 |
Switch_Addr | 1 | out | 两组拨码开关的选择信号 |
Switch_Dout | 32 | in | 从拨码开关读出的数据 |
Key_Dout | 32 | in | 从按键开关读出的数据 |
LED_data_byteen | 4 | out | LED输出字节使能信号 |
LED_Din | 32 | out | 可能写入LED的数据 |
Bridge 即系统桥,其功能是根据读写地址在 CPU 和各项外设之间选择性开关数据通路。
Key
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
KeyInput | 8 | in | 按键的输入数据(外设按键信号) |
KeyResult | 32 | out | 按键输出数据 |
Key 即按键开关,输出数据仅有低8位有效。
Switch
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
Addr | 1 | in | 两组拨码开关的选择信号 |
swift1 | 8 | in | 拨码开关输入数据 |
swift2 | 8 | in | 拨码开关输入数据 |
swift3 | 8 | in | 拨码开关输入数据 |
swift4 | 8 | in | 拨码开关输入数据 |
swift5 | 8 | in | 拨码开关输入数据 |
swift6 | 8 | in | 拨码开关输入数据 |
swift7 | 8 | in | 拨码开关输入数据 |
swift8 | 8 | in | 拨码开关输入数据 |
SwitchResult | 32 | out | 拨码开关输出数据 |
Switch 即拨码开关,共64个,分为2个大组,8个小组,通过Addr 来选择从哪个大组中读出数据。
LED
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
LED_data_byteen | 4 | in | LED字节使能信号 |
LEDInput | 32 | in | 输入LED的数据 |
LEDResult | 32 | out | LED输出数据 |
LED 即控制开发板上 LED 灯的模块。
DTube
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
Addr | 1 | in | 数码管组选择信号 |
DTube_data_byteen | 4 | in | 数码管字节使能信号 |
DTubeInput | 32 | in | 待输入数码管的数据 |
DTubeResult | 32 | out | 经Addr选择输入外置数码管的数据 |
DTube0 | 8 | out | 可能输入1-4号外置数码管的数据 |
DTube_sel0 | 4 | out | 1-4号外置数码管选择信号 |
DTube1 | 8 | out | 可能输入5-8号外置数码管的数据 |
DTube_sel1 | 4 | out | 5-8号外置数码管选择信号 |
DTube2 | 8 | out | 可能输入最高位外置数码管的数据 |
DTube_sel2 | 1 | out | 最高位外置数码管选择信号 |
DTube 即外置数码管的控制模块,主要通过写入的值更改数码管的输出,并以固定的时间间隔轮询刷新各个数码管。
IM/DM
按照教程的说法这两个模块是使用 ip_core 生成的,由于其同步读的特性,应该对流水线中的线路连通进行修改,大致工作如下:
- 对于 DM 只需在 MW_REG 中将 W_dataOut 和 M_dataOut 短接并辅助特判即可:
assign W_dataOut = (reset || flush || Req) ? 0 : M_dataOut;
- 对于 IM 如法炮制,需要注意阻塞时的行为。
reg [31:0] last_instr; assign D_instr = (reset || flush || Req) ? 0 : (!en) ? last_instr : F_instr; always @(posedge clk) begin if(reset | flush | Req)begin last_instr <= 0; D_pc <= Req ? 32'h00004180 : 32'h00003000; D_ExcCode <= 0; D_BD <= 0; end else if(en)begin last_instr <= F_instr; D_pc <= F_pc; D_ExcCode <= F_ExcCode; D_BD <= F_BD; end end
mips_uart
管脚名 | 位数 | in/out | 功能解释 |
---|---|---|---|
clk | 1 | in | 系统时钟信号 |
reset | 1 | in | 复位信号 |
dataIn | 32 | in | CPU 发往 UART 的数据 |
dataOut | 32 | out | UART 发往 CPU 的数据 |
Addr | 2 | in | 读写数据地址 |
WE | 1 | in | UART写使能信号 |
rxd | 1 | in | 外界发往UART的一字节数据 |
txd | 1 | out | UART发往外界的一字节数据 |
UART_Int | 1 | out | 外界发送数据待接收的中断信号 |
mips_uart 即 uart_rx, uart_rd 的封装模块,主要维护 UART 的寄存器读写和rx, rd间的数据流和控制逻辑。重要控制信号和数据处理如下:
assign UART_Int = rx_ready;
assign LSR = {26'b0, tx_avai, 4'b0, rx_ready};
assign rx_clear = WE;
always @(posedge clk or posedge reset) begin
if(reset)
tx_start <= 0;
else
tx_start <= !tx_start && WE && (Addr == 0);
end
可综合性的实现
由于本次实验需要生成可烧录的 .bit 文件,故 verilog 代码必须是可综合的;主要有两方面的工作,一时处理 IM、DM 的可综合性,二是优化乘除模块的可综合性。前者在上节已经介绍完毕,现介绍 MDU 模块的改进。
笔者将课程组提供的乘除模块封装入 P6 中自己构造的乘除模块中以减少主电路的改动。
always @(*) begin
if(Req)
in_valid = 0;
else begin //译码阶段,根据不同的运算赋值控制信号。
case (MDUCtrl)
`MDUCtrl_mult:begin
in_op = 1;
in_sign = 1;
in_valid = 1;
end
`MDUCtrl_multu:begin
in_op = 1;
in_sign = 0;
in_valid = 1;
end
`MDUCtrl_div:begin
in_op = 2;
in_sign = 1;
in_valid = 1;
end
`MDUCtrl_divu:begin
in_op = 2;
in_sign = 0;
in_valid = 1;
end
default:begin
in_op = 0;
in_sign = 0;
in_valid = 0;
end
endcase
end
end
MulDivUnit m_MulDivUnit(
.clk(clk),
.reset(reset),
.in_src0(A),
.in_src1(B),
.in_op(in_op),
.in_sign(in_sign),
.in_ready(in_ready),
.in_valid(in_valid),
.out_ready(1),
.out_valid(out_valid),
.out_res0(lo),
.out_res1(hi)
);
always @(posedge clk ) begin
if(reset)begin
HI <= 0;
LO <= 0;
Busy <= 0;
end
else if(!Req) begin
if(MDUCtrl == `MDUCtrl_mthi)
HI <= A;
else if(MDUCtrl == `MDUCtrl_mtlo)
LO <= A;
if(out_valid)begin //计算完毕将子模块的结果返回主乘除模块
LO <= lo;
HI <= HI;
end
Busy <= ~in_ready;
end
end
汇编程序的编写及思路
根据教程的提交要求,需要在我们编写的 CPU 上运行 mips 汇编程序以完成相应的任务;笔者把任务划分为三步:
- 实现最基本的选择操作、计算、数码管及LED显示的控制。
- 实现计时器功能。
- 实现串口显示计算结果或计时时间,完善回写功能。
代码如下:
code_main
# Data Memory 0x0000_0000 - 0x0000_2fff
# Instr Memory 0x0000_3000 - 0x0000_6fff
# Timer0 0x0000_7f00 - 0x0000_7f0b
# UART 0x0000_7f30 - 0x0000_7f3f
# Digital Tube 0x0000_7f50 - 0x0000_7f57
# Dip Switch 0x0000_7f60 - 0x0000_7f67
# Button Key 0x0000_7f68 - 0x0000_7f6b
# LED 0x0000_7f70 - 0x0000_7f73
.text 0x3000
ori $t0, $0, 0xfc01
mtc0 $t0, $12
loop:
lbu $t0, 0x7f68($0)
lw $s0, 0x7f60($0) #B / preset
lw $s1, 0x7f64($0) #A
# 操作识别阶段
beq $t0, 1, TimerLEDPlus
nop
beq $t0, 3, TimerUARTPlus
nop
beq $t0, 5, TimerLEDMinus
nop
beq $t0, 7, TimerUARTMinus
nop
beq $t0, 4, AddLED
nop
beq $t0, 6, AddUART
nop
beq $t0, 8, SubLED
nop
beq $t0, 10, SubUART
nop
beq $t0, 16, MultLED
nop
beq $t0, 18, MultUART
nop
beq $t0, 32, DivLED
nop
beq $t0, 34, DivUART
nop
beq $t0, 64, AndLED
nop
beq $t0, 66, AndUART
nop
beq $t0, 128, OrLED
nop
beq $t0, 130, OrUART
nop
j End
#计时器指令
TimerLEDPlus:
ori $t6, $0, 1
li $t2, 25000000
sw $t2, 0x7f04($0)
li $t1, 11
sw $t1, 0x7f00($0)
li $t3, 0 #count from 0
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
wait1:
lbu $s3, 0x7f68($0)
lw $s4, 0x7f60($0) #B / preset
bne $s3, $t0, end1
nop
bne $s4, $s0, end1
nop
blt $t3, $s0, wait1
nop
end1:
sw $0, 0x7f00($0)
or $t6, $0, $0
j End
nop
TimerUARTPlus:
ori $t6, $0, 1
li $t2, 25000000
sw $t2, 0x7f04($0)
li $t1, 11
sw $t1, 0x7f00($0)
or $t3, $0, $0 #count from 0
or $t9, $0, $0
wait3:
lbu $s3, 0x7f68($0)
lw $s4, 0x7f60($0) #B / preset
bne $t3, $t9, Timer_UART
nop
return_TimerUARTPlus:
add $t9, $t3, $0
bne $s3, $t0, end3
nop
bne $s4, $s0, end3
nop
blt $t3, $s0, wait3
nop
end3:
sw $0, 0x7f00($0)
or $t6, $0, $0
j End
nop
TimerLEDMinus:
ori $t6, $0, 1
li $t2, 25000000
sw $t2, 0x7f04($0)
li $t1, 11
sw $t1, 0x7f00($0)
or $t3, $s0, $zero #count from preset
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
wait2:
lbu $s3, 0x7f68($0)
lw $s4, 0x7f60($0) #B / preset
bne $s3, $t0, end2
nop
bne $s4, $s0, end2
nop
bnez $t3, wait2
nop
end2:
sw $0, 0x7f00($0)
or $t6, $0, $0
j End
nop
TimerUARTMinus:
ori $t6, $0, 1
li $t2, 25000000
sw $t2, 0x7f04($0)
li $t1, 11
sw $t1, 0x7f00($0)
or $t3, $s0, $0 #count from preset
or $t9, $s0, $0
wait4:
lbu $s3, 0x7f68($0)
lw $s4, 0x7f60($0) #B / preset
bne $t3, $t9, Timer_UART
nop
return_TimerUARTMinus:
add $t9, $t3, $0
bne $s3, $t0, end4
nop
bne $s4, $s0, end4
nop
bnez $t3, wait4
nop
end4:
sw $0, 0x7f00($0)
or $t6, $0, $0
j End
nop
#计算指令
AddLED:
addu $t3, $s1, $s0
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j End
nop
AddUART:
addu $t3, $s1, $s0
j Cal_UART
nop
SubLED:
subu $t3, $s1, $s0
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j End
nop
SubUART:
subu $t3, $s1, $s0
j Cal_UART
nop
MultLED:
mult $s1, $s0
mflo $t3
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j End
nop
MultUART:
mult $s1, $s0
mflo $t3
j Cal_UART
nop
DivLED:
div $s1, $s0
mflo $t3
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j End
nop
DivUART:
div $s1, $s0
mflo $t3
j Cal_UART
nop
AndLED:
and $t3, $s1, $s0
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j End
nop
AndUART:
and $t3, $s1, $s0
j Cal_UART
nop
OrLED:
or $t3, $s1, $s0
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j End
nop
OrUART:
or $t3, $s1, $s0
j Cal_UART
nop
#针对计算指令的串口显示处理
Cal_UART:
num3:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num3
nop
srl $t5, $t3, 24
sw $t5, 0x7f30($0)
num32:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num32
nop
srl $t5, $t3, 16
sw $t5, 0x7f30($0)
num2:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num2
nop
srl $t5, $t3, 16
sw $t5, 0x7f30($0)
num21:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num21
nop
srl $t5, $t3, 8
sw $t5, 0x7f30($0)
num1:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num1
nop
srl $t5, $t3, 8
sw $t5, 0x7f30($0)
num10:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num10
nop
srl $t5, $t3, 0
sw $t5, 0x7f30($0)
num0:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, num0
nop
srl $t5, $t3, 0
sw $t5, 0x7f30($0)
num_loop:
lbu $s3, 0x7f68($0)
lw $s4, 0x7f60($0) #B / preset
lw $s5, 0x7f64($0)
bne $s3, $t0, End
nop
bne $s4, $s0, End
nop
bne $s5, $s1, End
nop
j num_loop
nop
#针对计时指令的串口显示处理
Timer_UART:
time3:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time3
nop
srl $t5, $t9, 24
sw $t5, 0x7f30($0)
time32:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time32
nop
srl $t5, $t9, 16
sw $t5, 0x7f30($0)
time2:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time2
nop
srl $t5, $t9, 16
sw $t5, 0x7f30($0)
time21:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time21
nop
srl $t5, $t9, 8
sw $t5, 0x7f30($0)
time1:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time1
nop
srl $t5, $t9, 8
sw $t5, 0x7f30($0)
time10:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time10
nop
srl $t5, $t9, 0
sw $t5, 0x7f30($0)
time0:
lw $t4, 0x7f34($0) #LSR
andi $t4, $t4, 32
beqz $t4, time0
nop
srl $t5, $t9, 0
sw $t5, 0x7f30($0)
beq $t0, 3, return_TimerUARTPlus
nop
beq $t0, 7, return_TimerUARTMinus
nop
End:
j loop
nop
code_handle
.ktext 0x4180
nop
nop
nop
mfc0 $t7, $13
andi $t7, $t7, 4096 #判断是否为UART中断
check_tc0:
beqz $t6, check_uart #判断是否为计时指令引起的中断
nop
beq $t0, 1, plus
nop
beq $t0, 3, plus
nop
#更新时间并显示
minus:
subi $t3, $t3, 1
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
j check_uart
nop
plus:
addi $t3, $t3, 1
sw $t3, 0x7f50($0)
sw $t3, 0x7f70($0)
check_uart:
beqz $t7, back_to_main
nop
#回显功能的实现
lw $a0, 0x7f30($0)
sw $a0, 0x7f30($0)
back_to_main:
eret
问题与解决
就我本人来看,本次实验是我最为吃力的一次,遇到的主要问题和解决如下。
1.如何改动使ip核生成的 IM/DM 融入CPU?
一开始采用了简单的倍频方式,其后发现该方式存在一些难以理解的行为,且工程设计中并不会采用,故转用更加合理的修改数据通路的方式。
2.如何封装教程提供的UART子模块代码?
其实弄清楚这个草图可以应对本实验的要求:
3.如何实现串口发生一个字的数据?
串口以一个字节为一个单位发送数据,故可以在 mips 汇编代码中编写程序,对于一个字共发送4次,每次使用位移指令选择相应的位进行发送,在这期间要不断从 UART 的 LSR 中获取 UART 的工作状态,在上一个字节已经发送完毕后再进行发送。
4.如何实现回显功能?
为了不影响其他功能的实现,我采用的中断的方式进行处理,当UART接收到一个字节的数据后产生中断,进入 handle 程序执行回显指令。
5.如何实现计时器?
同样的,也采用 timer 提供中断信号的方式,用软件控制将 timer 设置成约1秒产生一次中断的工作模式,在中断中更新计时器的值即可。
最后的最后
一点感受:计组给我的锻炼到目前为止都是北航其他课程无法比拟的。
虽然每次课上实验都像一次“冒险”,但最终贯通整个架构,在 FPGA 上实现自己的 CPU 是一次十分难得的经历。
希望你也能体会到其中的乐趣 😃
彩蛋: