串口通信
设计概要如下:
在前述“简易8位通用CPU设计”的基础上扩充UART异步串行通信收发器硬件,实现以特定波特率与PC完成串口通信的功能,并以通用CPU嵌入式系统
程序(机器语言或者汇编语言)完成与实际PC串口的通信。
你说你不知道简易8位通用CPU设计?自觉跳转:8位通用CPU设计
硬件上的修改
首先要说明的是,本题使用的是波特率为9600的 uart 通信,因此我们需要每隔 1s /9600 执行一次数据读取或者发送,而我们的开发板频率是50MHz,故我们
需要计算执行一次数据发送或者数据接收:
1分频时执行一次数据处理所需指令周期 = 50,000,000 / 9600 / 6 = 868
2分频时执行一次数据处理所需指令周期 = 50,000,000 / 9600 / 6 / 2 = 434
4分频时执行一次数据处理所需指令周期 = 50,000,000 / 9600 / 6 / 4 = 217
说明一下,只有四分频才能保证 CPU 在处理数据的时候不会出现数据访问冲突,但这并不意味着我们需要采用4分频,因为指令处理频率越高数据帧读取出差错可能性越小
具体采用几分频,得测试哪种方式下数据帧接收错误率最低。因此我们需要一个计数器,用于计数,868和434显然是不能用8为存储下去的,但不管我们采用几分频,都可以采用
217为一个周期计数,比如说我们采用1分频,,那么我们可以让计数每隔4个指令周期才加一,示例如下:
if (~x) begin
if (~xx)
cnt <= next_cnt;
xx = ~xx;
end
x = ~x;
end
在上一题的基础上,需要添加两个新指令 in 和 out,用于串口通信的输入和输出:
- 指令编码表:
| 编号 | 编码(二进制) | 汇编指令 | 操作 | 备注 |
|---|---|---|---|---|
| 0 | 0000xxyy |
mov rx, ry | ry <= rx | |
| 1 | 0001xxyy |
mov rx, (ry) | (ry) <= rx | |
| 2 | 001000yy iiiiiiii |
mov i, ry | ry <= i | |
| 3 | 010000rr |
jmp r | PC <= r | 无条件跳转 |
| 4 | 010001rr |
jc r | PC <= r | 进位时跳转 |
| 5 | 010010rr |
jz r | PC <= r | 结果为0时跳转 |
| 6 | 010100yy |
in ry | ry <= uart receive | 将uart的rx线的值输入到指定寄存器的最高位 |
| 7 | 010101yy |
out ry | uart send <= ry | 将指定寄存器的最低位输出到uart的tx线 |
| 8 | 0110xxyy |
mov (rx), ry | ry <= (rx) | |
| 9 | 01110000 |
rtn | PC <= 0 | |
| 10 | 1000xxyy |
and rx, ry | ry <= rx & ry | |
| 11 | 1001xxyy |
or rx, ry | ry <= rx | ry | |
| 12 | 101000xx |
not rx | rx <= ~rx | |
| 13 | 1011xxyy |
xor rx, ry | ry <= rx ^ ry | |
| 14 | 1100xxyy |
shl rx, ry | ry <= ry << rx | |
| 15 | 1101xxyy |
add rx, ry | ry <= ry + rx | |
| 16 | 1110xxyy |
shr rx, ry | ry <= ry >> rx | |
| 17 | 1111xxyy |
sub rx, ry | ry <= ry - rx |
010100yy 对应的是 in 指令,用于将接受到的数据存到指定寄存器的最高位
010101yy 对应的是 out 指令,用于发送指定寄存器的最低位(将寄存器最低位传到 tx)
首先说一说怎么实现串口通信吧,基本原理如下:

呐,这张图上面的61引脚和62引脚分别对应的是 rxd32 和 txd32,也就是发送引脚和输出引脚,假设我们顶层模块中设置的发送端口为rx,接收端口
位tx,那么就需要将这两个端口分别连接到61号引脚以及62号引脚(在.qsf配置文件中加上如下配置):
set_location_assignment PIN_61 -to rx // 将61号引脚与 rx 端口连接作为接收端口
set_location_assignment PIN_62 -to tx // 将62号引脚与 rx 端口连接作为发送端口
添加 in 指令和 out 指令
其中 in 指令和 out 指令的实现如下:
首先,需要添加几个新的信号:
output reg reg_bus_in, // 寄存器组输入,将需要存入指定寄存器最高位的数据写进去,若 reg_en_in 为高电平,则会将该数据写入指定寄存器的最高位(人话:ry[7] <= reg_bus_in)
output reg reg_en_in, // 寄存器组输入使能,配合上面那玩意使用的
input reg_bus_out, // 寄存器组输出,将需要发送的指定寄存器最低位的数据(reg_bus_out)写出去,若 reg_en_out 为高电平,则会将该数据写入 tx 等待发送(人话
:reg_bus_out <= ry[0])
output reg reg_en_out // 寄存器组输出使能,配合上面那玩意使用的
因此,寄存器除了判断总线仲裁是否选择了它,更需要判断是否需要它配合实现 uart 通信,因此,寄存器组需要被改装:
module reg_file(
input clk,
input rst,
// 双地址总线接口
input [2:0] t_addrbus, // 目标地址总线
input [2:0] s_addrbus,
output reg [7:0] databus0, // 双向数据总线(dMDR与寄存器组之间交互线)
output reg [7:0] databus1, // 双向数据总线(dMDR与寄存器组之间交互线)
output reg [7:0] databus2, // 双向数据总线(dMDR与寄存器组之间交互线)
output reg [7:0] databus3, // 双向数据总线(dMDR与寄存器组之间交互线)
input [7:0] inbus, // 输入数据总线(来自ALU或其他模块)
// 控制信号
input cur_reg_bus_in, // 控制单元寄存器组输出
input cur_reg_en_in, // 控制单元寄存器组使能信号
output reg cur_reg_bus_out, // 当前寄存器组输出
input cur_reg_en_out, // 当前寄存器组使能信号
output [7:0] r3dbg
);
assign r3dbg = databus3;
//------------------------------------------
// 同步写逻辑
//------------------------------------------
always @(negedge clk) begin
// 目标寄存器写入(地址有效时)
if (cur_reg_en_in) begin
case (t_addrbus[1:0]) // 低2位选择寄存器
2'd0: databus0[7] <= cur_reg_bus_in; // R0
2'd1: databus1[7] <= cur_reg_bus_in; // R1
2'd2: databus2[7] <= cur_reg_bus_in; // R2
2'd3: databus3[7] <= cur_reg_bus_in; // R3
endcase
end
else if (cur_reg_en_out) begin
case(s_addrbus[1:0]) // 低2位选择寄存器
2'd0: cur_reg_bus_out <= databus0[0]; // R0
2'd1: cur_reg_bus_out <= databus1[0]; // R1
2'd2: cur_reg_bus_out <= databus2[0]; // R2
2'd3: cur_reg_bus_out <= databus3[0]; // R3
endcase
end
else if (t_addrbus[2] == 1'b0) begin // 最高位=0时有效
case (t_addrbus[1:0]) // 低2位选择寄存器
2'd0: databus0 <= inbus; // R0
2'd1: databus1 <= inbus; // R1
2'd2: databus2 <= inbus; // R2
2'd3: databus3 <= inbus; // R3
endcase
end
if (rst) begin
// 全部寄存器复位为0
databus0 <= 8'b0;
databus1 <= 8'b0;
databus2 <= 8'b0;
databus3 <= 8'b0;
end
end
endmodule
在 always 控制块中,添加了两个使能信号的判断(cur_reg_en_in 和 cur_reg_en_out),当信号有效时,就会将指定寄存器最低位的数据写到 cur_reg_bus_out,或者
将 cur_reg_bus_in 写到指定寄存器的最高位,而 这两个使能信号是由 CU 控制单元去控制生效的,当 CU 将这两个信号任意一个使能后,寄存器组就会将对于的工作处理完,
而后 CU 就可以利用 cur_reg_bus_in 和 cur_reg_bus_out 来处理接受和发送了,下面是 CU 中 in 指令和 out 指令的处理:
执行阶段1:operand高两位用于判断指令为 in 指令还是 out 指令,00 则为 in 指令,01 则为 out 指令
- in指令逻辑:
让指定寄存器最高位接受数据,并将 rx 中的数据接收到 reg_bus_in 中,寄存器组将会利用 reg_bus_in 存储数据,当然最重要的一步,让 reg_en_in 为1
- out指令逻辑:
让指定寄存器最低位发送数据,让 reg_en_in 为1,这一步后指定寄存器会将最低为存储到 reg_bus_out 当中,下一步将会利用到该数据
// EXEC1
4'b0101: begin
case(operand[3:2])
// in ry // ry <= uart receive
2'b00: begin
t_addrbus <= {1'b0, operand[1:0]};
reg_bus_in <= rx; // 寄存器组输入
reg_en_in <= 1; // 寄存器组输入使能信号
end
// out ry // uart send
2'b01: begin
s_addrbus <= {1'b0, operand[1:0]};
reg_en_out <= 1;
end
endcase
end
执行阶段2
- in指令逻辑:
寄存器组停止输入,且停止使能信号输出
- out指令逻辑:
寄存器组停止输出,且停止使能信号输出
// EXEC2
4'b0101: begin
case(operand[3:2])
// in ry step2(in)
2'b00: begin
t_addrbus <= 3'b111; // 停止寄存器组输入
reg_en_in <= 0; // 寄存器组输入使能信
end
2'b01:begin
s_addrbus <= 3'b111; // 停止寄存器组输出
reg_en_out <= 0; // 寄存器组输出使能信
end
endcase
end
可能会有人问了,out 指令不是还要将数据写入到 tx 当中吗,这咋感觉像是两个指令都完成了,要做收尾工作了呢?你别急,你考虑过的我肯定也考虑过了,
若此时 out 指令直接将数据输出到 tx,很有可能寄存器还没有将数据写入到 reg_bus_out 当中,此时就有可能将错误数据写入到 tx 当中
执行阶段3(独属于 out 指令的阶段)
4'b0101: begin
case(operand[3:2])
2'b01:begin
tx <= reg_bus_out; // 寄存器组输出使能
end
endcase
end
在第三阶段再将数据写入 tx, 就肯定不会出问题了
相信细心的观众肯定发现了,我的立即数指令变化了,这是因为串口通信的确需要8位立即数的计算:
CU 立即数 mov 指令逻辑修改
当然,立即数只能存下8位数据,那么我们要怎么做才能保证传送8位数据呢,再怎么样操作数只有4位,一个指令码应该很难实现吧?
对了,那就使用两个指令码来处理立即数 mov 操作,这意味着该指令会让 pc 自增两次,因为需要读取两个指令码,下面是指令的实现:
之前 IR 发送到总线的数据是指令码的低五位,通过这种方式就需要 IR 直接将指令码八位全部发送到总线上去:
always @(posedge clk) begin
// 指令加载阶段
if (ir_in) begin
databus <= inbus; // 锁存总线数据(被修改部分)
end
end
执行阶段1
虽然我们要读取两个指令码,但是实际上我们执行的逻辑是第一个指令码的逻辑,第二个指令码是一个完全的操作数,因此在 pc 自增后,我们要记住上一条是 mov 命令:
reg imm_get_en; // 立即数获取使能(若为1,则继续执行 mov i ry指令)
reg [1:0] imm_reg; // 立即数寄存器(因为在 pc 自增后会丢失 mov 指令数据,因此需要提前将指定寄存器的编号锁存下来)
// EXEC1
4'b0010: begin
imm_reg <= operand[1:0]; // 锁存立即数读取的寄存器编号
pc_out <= 1; // PC输出至数据总线
s_addrbus <= 3'b110; // 指令存储器读取指令
imm_get_en <= 1; // 立即数获取使能
end
首先让 imm_reg 锁存指定寄存器的编号,然后让 PC 输出下一条指令的编号,将 imm_get_en 信号使能设置为1,后面的执行阶段中会先
特判 imm_get_en 是否为1,若为1则执行 mov i ry 指令的逻辑
执行阶段2
if (imm_get_en) begin
pc_out <= 0; // PC停止输出
pc_inc <= 1; // PC自增
s_addrbus <= 3'b111; // 停止源地址发送数据
iMDRout <= 1; // 指令数据存储器输出数据至总线
ir_in <= 1; // 指令寄存器从总线中读取指令
end
执行阶段1已经将 imm_get_en 设置为1了,进入执行阶段2后就直接进入该逻辑部分了,此时让 pc 自增,为后面的阶段做准备,让指令存储器输出数据到总线,
同时让指令寄存器从总线读取指令(准确来说是立即数)
执行阶段3
if (imm_get_en) begin
pc_inc <= 0; // PC停止自增
iMDRout <= 0; // 指令数据存储器停止输出数据至总线
ir_in <= 0; // 指令寄存器停止读取指令
imm_out <= 1; // 立即数输出使能
t_addrbus <= {1'b0, imm_reg}; // 立即数写入寄存器
end
接下来就是立即数输出使能了,让指定寄存器读取数据,需要被写入的寄存器之前已经被锁存了,当时若不锁存,这个时候就无法获取到寄存器编号了,因为 pc 已经自增了
执行阶段4
if (imm_get_en) begin
imm_out <= 0; // 立即数输出停止
imm_get_en <= 0; // 立即数获取使能复位
t_addrbus <= 3'b111; // 停止目标地址接受数据
end
这一阶段没什么好说的,将之前被激活的信号关掉
CU单元算是一个大改,最终代码如下:
module CU (
// 时钟与复位
input clk,
input rst, // 全局复位信号
// 指令接口
input [3:0] opcode,
input [3:0] operand,
// 程序地址总线控制
output reg pc_reset, // PC清零
output reg pc_out, // PC地址输出使能
output reg pc_inc, // PC自增
output reg pc_jump, // PC跳转
// 指令寄存器总线控制
output reg iMDRout, // iMDR写入总线
// 寄存器组总线控制
// ALU总线控制
output reg ALUAin, // ALU数据A输入
output reg ALUBin, // ALU数据B输入
output reg ALUout, // ALU数据输出
output reg [2:0] ALUop, // ALU操作选择
input cf, // 进位标志(CF)
input zf, // 零标志(ZF)
// 数据存储器总线控制
output reg dmdr_iin, // dMDR和总线输入使能
output reg dmdr_iout, // dMDR和总线输出使能(数据总线输出)
output reg mem_as_target, // 数据存储器作为目标地址总线
output reg mem_as_source,
output reg ir_in, // 从总线加载指令(上升沿有效)
output reg imm_out, // 立即数输出(低五位)
output reg [2:0] s_addrbus, // 源地址选择
output reg [2:0] t_addrbus, // 目标地址选择
output reg [7:0] cnt,
output [7:0] next_cnt,
input rx, // UART接收线(可选)
output reg tx, // UART发送线(可选)
output reg reg_bus_in, // 寄存器组输入(R0-R3)
output reg reg_en_in, // 寄存器组输入使能(R0-R3)
input reg_bus_out, // 寄存器组输出(R0-R3)
output reg reg_en_out // 寄存器组输出使能(R0-R3)
);
reg imm_get_en; // 立即数获取使能
reg [2:0] cur_sta;
reg fetch1_wait; // 等待信号
reg [1:0] imm_reg; // 立即数寄存器
parameter
FETCH1 = 3'b000,
FETCH2 = 3'b001,
EXEC1 = 3'b010,
EXEC2 = 3'b011,
EXEC3 = 3'b100,
EXEC4 = 3'b101;
Incrementer cnt_inc(
.op(cnt),
.next_op(next_cnt)
);
always @(negedge clk) begin
case (cur_sta)
// 取指阶段
FETCH1: begin
pc_out <= 1; // PC输出至数据总线
s_addrbus <= 3'b110; // 指令存储器读取指令
t_addrbus <= 3'b111; // 无目标地址
if (fetch1_wait) begin
fetch1_wait <= 1'b0; // 取指等待信号复位
cur_sta <= FETCH1; // 保持在FETCH1状态
end else begin
cur_sta <= FETCH2; // 状态转移到FETCH2
end
end
FETCH2: begin
pc_out <= 0; // PC停止总线输出
s_addrbus <= 3'b111; // 停止指令存储器读取指令
iMDRout <= 1; // 指令数据存储器输出数据至总线
ir_in <= 1; // 指令寄存器从总线中读取指令
pc_inc <= 1; // PC自增
cur_sta <= EXEC1; // 状态转移到EXEC1
end
EXEC1: begin
pc_inc <= 0; // PC停止自增
iMDRout <= 0; // 指令数据存储器停止输出数据至总线
ir_in <= 0; // 指令寄存器停止读取指令
// Start
case (opcode)
// move rx, ry step1(move)
4'b0000: begin
s_addrbus <= {1'b0, operand[3:2]}; // rx发送数据信息至数据总线
t_addrbus <= {1'b0, operand[1:0]}; // ry接受数据总线上的数据信息
end
// move rx, (ry) step1(间接寻址)
4'b0001: begin
s_addrbus <= {1'b0, operand[3:2]}; // rx发送数据信息至数据总线
dmdr_iin <= 1; // MDR接收总线数据
end
// move i ry
4'b0010: begin
imm_reg <= operand[1:0]; // 锁存立即数读取的寄存器编号
pc_out <= 1; // PC输出至数据总线
s_addrbus <= 3'b110; // 指令存储器读取指令
imm_get_en <= 1; // 立即数获取使能
end
// jump
4'b0100: begin
case (operand[3:2])
// ry
2'b00: begin
pc_jump <= 1; // PC跳转
s_addrbus <= {1'b0, operand[1:0]}; // 直接跳转到地址
end
// jc ry
2'b01: begin
if (cf) begin
pc_jump <= 1; // PC跳转
s_addrbus <= {1'b0, operand[1:0]}; // 直接跳转到地址
end else begin
pc_jump <= 0; // PC不跳转
end
end
// jz ry
2'b10: begin
if (zf) begin
pc_jump <= 1; // PC跳转
s_addrbus <= {1'b0, operand[1:0]}; // 直接跳转到地址
end else begin
pc_jump <= 0; // PC不跳转
end
end
endcase
end
4'b0101: begin
case(operand[3:2])
// in ry // ry <= uart receive
2'b00: begin
t_addrbus <= {1'b0, operand[1:0]};
reg_bus_in <= rx; // 寄存器组输入
reg_en_in <= 1; // 寄存器组输入使能信号
end
// out ry // uart send
2'b01: begin
s_addrbus <= {1'b0, operand[1:0]};
reg_en_out <= 1;
end
endcase
end
// move (rx), ry step1(间接寻址)
4'b0110: begin
s_addrbus <= {1'b0, operand[3:2]}; // rx输出地址数据至数据总线
mem_as_source <= 1;
end
// rtn step1(rtn)
4'b0111: begin
pc_reset <= 1; // PC清零
end
// and/or/xor/add/sub/shl/shr rx ry step1(alua <= rx)
4'b1000, 4'b1001, 4'b1011, 4'b1101, 4'b1111, 4'b1100, 4'b1110: begin
s_addrbus <= {1'b0, operand[3:2]};
ALUAin <= 1;
end
default: cur_sta <= cur_sta;
endcase
cur_sta <= EXEC2; // 状态转移到EXEC2
end
EXEC2: begin
if (imm_get_en) begin
pc_out <= 0; // PC停止输出
pc_inc <= 1; // PC自增
s_addrbus <= 3'b111; // 停止源地址发送数据
iMDRout <= 1; // 指令数据存储器输出数据至总线
ir_in <= 1; // 指令寄存器从总线中读取指令
end else begin
case (opcode)
// move rx, ry endstep
4'b0000: begin
s_addrbus <= 3'b111;
t_addrbus <= 3'b111;
end
// move rx, (ry) step2(move)
4'b0001: begin
dmdr_iin <= 0; // MDR停止接收总线数据
s_addrbus <= {1'b0, operand[1:0]}; // ry发送地址信息至数据总线
// t_addrbus <= 3'b100; // 通过总线数据地址信息将对应内存数据存至dMDR
mem_as_target <= 1; // 数据存储器作为目标地址总线
end
// jump step2(jump)
4'b0100: begin
pc_jump <= 0; // PC停止跳转
s_addrbus <= 3'b111; // 停止源地址发送数据
t_addrbus <= 3'b111; // 停止目标地址接受数据
end
4'b0101: begin
case(operand[3:2])
// in ry step2(in)
2'b00: begin
t_addrbus <= 3'b111; // 停止寄存器组输入
reg_en_in <= 0; // 寄存器组输入使能信
end
2'b01:begin
s_addrbus <= 3'b111; // 停止寄存器组输出
reg_en_out <= 0; // 寄存器组输出使能信
end
endcase
end
// move (rx), ry step2(move)
4'b0110: begin
mem_as_source <= 0;
dmdr_iout <= 1; // dMDR输出数据至数据总线
// s_addrbus <= 3'b100; // 主存根据地址数据总线访存
t_addrbus <= {1'b0, operand[1:0]}; // ry接受数据总线上的数据
end
// rtn endstep
4'b0111: begin
pc_reset <= 0; // PC停止清零
end
// and rx ry step2(alub <= ry)
4'b1000: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b100;
end
// or rx ry step2(alub <= ry)
4'b1001: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b101;
end
// xor rx ry step2(alub <= ry)
4'b1011: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b111;
end
// not ry step2(alub <= ry)
4'b1010: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b110;
end
// shl rx, ry (alub <= ry)
4'b1100: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b010;
end
// and rx ry step2(alub <= ry)
4'b1101: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b000;
end
// shr rx ry(alub <= ry)
4'b1110: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b011;
end
// sub rx ry step2(alub <= ry)
4'b1111: begin
s_addrbus <= {1'b0, operand[1:0]};
ALUAin <= 0;
ALUBin <= 1;
ALUop <= 3'b001;
end
default: cur_sta <= cur_sta;
endcase
end
// 状态转移到EXEC3
cur_sta <= EXEC3;
end
EXEC3: begin
if (imm_get_en) begin
pc_inc <= 0; // PC停止自增
iMDRout <= 0; // 指令数据存储器停止输出数据至总线
ir_in <= 0; // 指令寄存器停止读取指令
imm_out <= 1; // 立即数输出使能
t_addrbus <= {1'b0, imm_reg}; // 立即数写入寄存器
end else begin
case (opcode)
// move rx, (ry) endstep
4'b0001: begin
s_addrbus <= 3'b111; // 停止源地址发送数据
t_addrbus <= 3'b111; // 停止目标地址接受数据
mem_as_target <= 0;
end
4'b0101: begin
case(operand[3:2])
2'b01:begin
tx <= reg_bus_out; // 寄存器组输出使能
end
endcase
end
// move (rx), ry endstep
4'b0110: begin
dmdr_iout <= 0; // dMDR停止输出数据至数据总线
t_addrbus <= 3'b111; // ry停止接受数据总线上的数据
s_addrbus <= 3'b111; // 停止源地址发送数据
end
// and/or/xor/add/sub/shl/shr rx ry step3 (result <= databus)
4'b1000, 4'b1001, 4'b1010, 4'b1011, 4'b1101, 4'b1111, 4'b1100, 4'b1110: begin
//clear
s_addrbus <= 3'b111;
ALUBin <= 0;
//operate
ALUout <= 1;
t_addrbus <= {1'b0, operand[1:0]}; // 计算结果写入ry
end
default: cur_sta <= cur_sta;
endcase
// 状态转移到EXEC4
end
cur_sta <= EXEC4;
end
EXEC4: begin
if (imm_get_en) begin
imm_out <= 0; // 立即数输出停止
imm_get_en <= 0; // 立即数获取使能复位
t_addrbus <= 3'b111; // 停止目标地址接受数据
end else begin
case (opcode)
// (and/or/not/xor/add/sub/shl/shr rx ry) / add/sub i endstep
4'b1000, 4'b1001, 4'b1010, 4'b1011, 4'b1100, 4'b1101, 4'b1110, 4'b1111, 4'b1100, 4'b1110: begin
ALUout <= 0;
t_addrbus <= 3'b111;
end
default: cur_sta <= cur_sta;
endcase
end
// 状态转移到FETCH1
cur_sta <= FETCH1;
if(cnt[3] & cnt[4] & cnt[6] & cnt[7]) cnt <= 0;
// if(cnt == 19) cnt <= 0;
// if (cnt[0]) cnt <= 0;
// 这一部分后面会讲到,这里先不用关心
else begin
if (~x) begin
if (~xx)
cnt <= next_cnt;
xx = ~xx;
end
x = ~x;
end
end
default: cur_sta <= FETCH1;
endcase
if (rst) begin
cur_sta <= FETCH1; // 复位时状态机回到FETCH1
pc_reset <= 1'b0;
pc_out <= 1'b0; // PC输出停止
pc_inc <= 1'b0; // PC自增停止
iMDRout <= 1'b0; // 指令存储器输出停止
ALUAin <= 1'b0; // ALU A输入停止
ALUBin <= 1'b0; // ALU B输入停止
ALUout <= 1'b0; // ALU输出停止
ALUop <= 3'b000; // ALU操作码清零
dmdr_iin <= 1'b0; // 数据存储器输入停止
dmdr_iout <= 1'b0; // 数据存储器输出停止
ir_in <= 1'b0; // 指令寄存器输入停止
imm_out <= 1'b0; // 立即数输出停止
s_addrbus <= 3'b111; // 源地址总线置空
t_addrbus <= 3'b111; // 目标地址总线置空
fetch1_wait <= 1'b1; // 清除等待状态
cnt <= 0;
tx <= 1;
end
end
reg x = 0;
reg xx = 0;
endmodule
主存配合 uart 通信所必需的要求
always @(negedge clk) begin
// 总线输入到MDR
if (dmdr_iin)
databus <= inbus;
// MDR写到内存
else if (mem_as_target)
memory[mem_addr[2:0]] <= databus;
// 内存读到MDR
else if (mem_as_source)
databus <= memory[mem_addr[2:0]];
// new part
if (memory[6] == memory[7]) begin // 14和15寄存器相等时,输出PWM调光信号
memory[5][0] <= 1'b1; // 13寄存器最低位为1
end
memory[7] <= cnt;
// new part end
end
assign mem_addr = inbus;
其中 memory[7] 会一直存储当前所处的周期计数,而 memory[6] 用于存放 uart 通信中期望接收数据或发送数据所达到的周期,因此,memory[5][0] 就用来存放
两个内存中数据是否相同的判断,当判断为1,uart 就会接受1 bit 数据。
软件上的实现
下面开始详解汇编代码的编写:
- Part 1:检测起始信号
L0:
xor r3, r3 // 00 | 10111111
not r3 // 01 | 10100011
in r0 // 02 | 01010000
// 下降沿后, and 指令会设置一个零标志
and r0, r3 // 03 | 10000011
// 零标志时跳转至 recv_pre
mov recv_pre, r0 // 04 | 00100000
// 05 | 00001000 : recv_pre
jz r0 // 06 | 01001000
// 不跳转回到开头继续等待
rtn // 07 | 01110000
刚开始,我们需要循环检测低电平,在 uart 协议当中,起始位为低电平,我们不断地通过 in 指令读取数据至 r0 寄存器的最高位,而 r3 在刚开始时被清零并取反,
r3 变成全1,当收到信号为高电平时零标志位为0,就会触发 rtn 指令,回到开头继续循环,信号变成低电平后零标志位为1,将 recv_pre 指令地址起始地址存到 r0,并跳转到
该地址
- Part 2:计时器取中值
我们要做到在发送信号后的中间时刻取数,这样才能保证数据不会取偏。我将解释全部放到代码当中,方便理解
// 第一个下降沿后,计算采样点的计数(当前计数+108)
// 可直接存储立即数(自己实现,我不敢改了,反正能跑)
recv_pre: // 08
// r3=108 (以下的步骤全都是在给r3 赋值108,其实我上面已经改成了8位立即数存储,但当时写这个汇编代码的时候并不支持,后期修改后反而还跑不了了,所以就索性不改了)
mov 5, r0 // 08 | 00100000
// 09 | 00000101
mov 3, r3 // 0a | 00100011
// 0b | 00000011
shl r0, r3 // 0c | 11000011
mov 0xc, r0 // 0d | 00100000
// 0e | 00001100
add r0, r3 // 0f | 11010011
// 计数器 上面说过,主存的最后一块地址用于存储当前的周期计数,因此这里的操作是将数据拿出来,以此为基准,在相对于这个时间点后的108个指令周期接受一次数据
// r1 = ucnt - 108
mov ucnt, r0 // 10 | 00100000
// 11 | 00000111 : ucnt
mov (r0), r1 // 12 | 01100001
sub r3, r1 // 13 | 11111101
// if (r1 < 0) r1 = r1 + 216
// 可直接使用 jc 判断(你知道我想说什么的)
// 这里是通过判断减后最高位是不是1来判断 r1 是否小于0,若是1,说明建成了一个负数,需要加上216补全
mov 7, r2 // 14 | 00100010
// 15 | 00000111
mov 1, r0 // 16 | 00100000
// 17 | 00000001
shl r2, r0 // 18 | 11001000
and r0, r1 // 19 | 10000001
mov 1f, r0 // 1a | 00100000
// 1b | 00100001 : 1f
jz r0 // 1c | 01001000
mov 1, r0 // 1d | 00100000
// 1e | 00000001
// r3 << 1 (r3 = 216)
// 跑这里来说明就是 r1 < 0 了,得加216补全
shl r0, r3 // 1f | 11000011
add r3, r1 // 20 | 11011101
1: // 21
// (testpoint) = r1
// 将期望接受数据的周期数存储到测试点(内存地址6)当中,在硬件逻辑中当检测到内存6和内存7单元相同时会把地址7变为1
mov testpoint, r0 // 21 | 00100000
// 22 | 00000110 : testpoint
mov r1, (r0) // 23 | 00010100
// (attp) = 0,先将 r1 清零,将 attp 内存地址存放的数据变为0(因为这里是起始位,需要等过去,不能执行数据接收)(起始位不是真正的数据,下一个比特才是)
xor r1, r1 // 24 | 10110101
mov attp, r0 // 25 | 00100000
// 26 | 00000101 : attp
mov r1, (r0) // 27 | 00010100
// 8个采样周期,作为循环计数
// (recv_bcnt) = 8
// 内存地址0用于存放数据接收剩余周期数(一共8个周期,因为总共需要接收8个数据位)
mov 8, r1 // 28 | 00100001
// 29 | 00001000
mov recv_bcnt, r0 // 2a | 00100000
// 2b | 00000000 : recv_bcnt
mov r1, (r0) // 2c | 00010100
- Part 3:数据接收决策点
// 1.5个周期后是第一个采样点,所以先跳过一个采样点
// 因为指令读取 attp 的行为对于时钟计数有延迟,这也是为什么需要让指令执行频率尽量高,因为我们需要让所谓的1.5个周期足够的小
// 实际上有很小的概率把第一个采样点跳过,导致数据错误
L2: // 2d
// 检测内存5是不是1,是的话就说明可以接收数据了
mov attp, r0 // 2d | 00100000
// 2e | 00000101 : attp
mov (r0), r1 // 2f | 01100001
mov 1, r0 // 30 | 00100000
// 31 | 00000001
and r0, r1 // 32 | 10000001
mov L2, r0 // 33 | 00100000
// 34 | 00101101 : L2
// 不是1,继续等待
jz r0 // 35 | 01001000
// 是1,进入数据接收Part,顺手把r0清零(养成好习惯,后面每一bit数据都要检测attp,因为硬件上内存只有给attp赋值位1的权限,所以attp清零必须依靠汇编代码)
xor r1, r1 // 36 | 10110101
mov attp, r0 // 37 | 00100000
// 38 | 00000101
mov r1, (r0) // 39 | 00010100
- Part 4:数据接收
recv: // 3a
// attp 在 ucnt 到达 testpoint 时会自动设为1
// 检测 attp 即可得知是否到达当前采样点
mov attp, r0 // 3a | 00100000
// 3b | 00000101 : attp
mov (r0), r1 // 3c | 01100001
mov 1, r0 // 3d | 00100000
// 3e | 00000001
and r0, r1 // 3f | 10000001
// 没到采样点时跳转回recv
mov recv, r0 // 40 | 00100000
// 41 | 00111010 : recv
jz r0 // 42 | 01001000
// 否则开始采样,先右移将上次采样结果保留
// 再使用 in 指令将这次的接收线的值存下来
mov 1, r0 // 43 | 00100000
// 44 | 00000001
shr r0, r3 // 45 | 11100011
in r3 // 46 | 01010011
// 将 attp 置 0,以等待下次的采样点到达
xor r1, r1 // 47 | 10110101
mov attp, r0 // 48 | 00100000
// 49 | 00000101 : attp
mov r1, (r0) // 4a | 00010100
// 循环计数减1
mov recv_bcnt, r0 // 4b | 00100000
// 4c | 00000000 : recv_bcnt
mov (r0), r1 // 4d | 01100001
mov 1, r0 // 4e | 00100000
// 4f | 00000001
sub r0, r1 // 50 | 11110001
mov recv_bcnt, r0 // 51 | 00100000
// 52 | 00000000 : recv_bcnt
mov r1, (r0) // 53 | 00010100
mov 0xff, r0 // 54 | 00100000
// 55 | 11111111
and r0, r1 // 56 | 10000001
// 循环计数为0时进入发送阶段
mov send_pre, r0 // 57 | 00100000
// 58 | 01011101 : send_pre
jz r0 // 59 | 01001000
// 否则跳回recv
mov recv, r0 // 5a | 00100000
// 5b | 00111010 : recv
jmp r0 // 5c | 01000000
此时 r3 寄存器的值为接收到的数据帧的8个数据位,结束位直接忽略
- Part5 数据发送准备
// 发送准备阶段
send_pre: // 5d
xor r1, r1 // 5d | 10110101
// 因为是发送,采样点简单设为0就行,不需要特殊计算
mov testpoint, r0 // 5e | 00100000
// 5f | 00000110 : testpoint
mov r1, (r0) // 60 | 00010100
mov attp, r0 // 61 | 00100000
// 62 | 00000101 : attp
mov r1, (r0) // 63 | 00010100
// 8个采样周期,作为循环计数,这里懒得改符号名了,所以还是 recv_bcnt
// (recv_bcnt) = 8
mov 8, r1 // 64 | 00100001
// 65 | 00001000
mov recv_bcnt, r0 // 66 | 00100000
// 67 | 00000000 : recv_bcnt
mov r1, (r0) // 68 | 00010100
- Part6 数据发送
// 加载起始位
L1: // 69
mov attp, r0 // 69 | 00100000
// 6a | 00000101 : attp
mov (r0), r1 // 6b | 01100001
mov 1, r0 // 6c | 00100000
// 6d | 00000001
and r0, r1 // 6e | 10000001
mov L1, r0 // 6f | 00100000
// 70 | 01101001 : L1
jz r0 // 71 | 01001000
// r0 归零,并输出 r0
xor r0, r0 // 72 | 10110000
out r0 // 73 | 01010100
// 将 attp 置 0,以等待下次的采样点到达
xor r1, r1 // 74 | 10110101
mov attp, r0 // 75 | 00100000
// 76 | 00000101 : attp
mov r1, (r0) // 77 | 00010100
// 发送8个数据位
send: // 78
mov attp, r0 // 78 | 00100000
// 79 | 00000101 : attp
mov (r0), r1 // 7a | 01100001
mov 1, r0 // 7b | 00100000
// 7c | 00000001
and r0, r1 // 7d | 10000001
// 没到采样点时跳回 send
mov send, r0 // 7e | 00100000
// 7f | 01111000 : send
jz r0 // 80 | 01001000
// 先输出 r3 ,再将 r3 右移,这样下次再执行到这里时,输出的最低位依然是要发送的位
out r3 // 81 | 01010111
mov 1, r0 // 82 | 00100000
// 83 | 00000001
shr r0, r3 // 84 | 11100011
// attp 置 0,不再重复说明
xor r1, r1 // 85 | 10110101
mov attp, r0 // 86 | 00100000
// 87 | 00000101 : attp
mov r1, (r0) // 88 | 00010100
// 循环计数减1
mov recv_bcnt, r0 // 89 | 00100000
// 8a | 00000000 : recv_bcnt
mov (r0), r1 // 8b | 01100001
mov 1, r0 // 8c | 00100000
// 8d | 00000001
sub r0, r1 // 8e | 11110001
mov recv_bcnt, r0 // 8f | 00100000
// 90 | 00000000 : recv_bcnt
mov r1, (r0) // 91 | 00010100
mov 0xf, r0 // 92 | 00100000
// 93 | 00001111
and r0, r1 // 94 | 10000001
// 循环计数为0时发送完成,即将发送结束位
mov tr_end, r0 // 95 | 00100000
// 96 | 10011011 : tr_end
jz r0 // 97 | 01001000
// 否则回到 send,发送下一位
mov send, r0 // 98 | 00100000
// 99 | 01111000 : send
jmp r0 // 9a | 01000000
发送逻辑和接收逻辑相似,因此解释会少很多
- Part7 结束位发送
// 传输完成,发送结束位
tr_end: // 9b
mov attp, r0 // 9b | 00100000
// 9c | 00000101 : attp
mov (r0), r1 // 9d | 01100001
mov 1, r0 // 9e | 00100000
// 9f | 00000001
and r0, r1 // a0 | 10000001
// 同样读取 attp ,没到采样点时跳回 tr_end
mov tr_end, r0 // a1 | 00100000
// a2 | 10011011 : tr_end
jz r0 // a3 | 01001000
// 加载结束位
mov 1, r0 // a4 | 00100000
// a5 | 00000001
out r0 // a6 | 01010100
// 回到开头接收下一个字节
rtn // a7 | 01110000
汇编代码完整版:
section text
// 等待第一个下降沿
L0:
xor r3, r3 // 00 | 10111111
not r3 // 01 | 10100011
in r0 // 02 | 01010000
// 下降沿后, and 指令会设置一个零标志
and r0, r3 // 03 | 10000011
// 零标志时跳转至 recv_pre
mov recv_pre, r0 // 04 | 00100000
// 05 | 00001000 : recv_pre
jz r0 // 06 | 01001000
// 不跳转回到开头继续等待
rtn // 07 | 01110000
// 第一个下降沿后,计算采样点的计数(当前计数+108) // 可直接存储立即数
recv_pre: // 08
// r3=108
mov 5, r0 // 08 | 00100000
// 09 | 00000101
mov 3, r3 // 0a | 00100011
// 0b | 00000011
shl r0, r3 // 0c | 11000011
mov 0xc, r0 // 0d | 00100000
// 0e | 00001100
add r0, r3 // 0f | 11010011
// r1 = ucnt - 108
mov ucnt, r0 // 10 | 00100000
// 11 | 00000111 : ucnt
mov (r0), r1 // 12 | 01100001
sub r3, r1 // 13 | 11111101
// if (r1 < 0) r1 = r1 + 216 // 可直接使用 jc 判断
mov 7, r2 // 14 | 00100010
// 15 | 00000111
mov 1, r0 // 16 | 00100000
// 17 | 00000001
shl r2, r0 // 18 | 11001000
and r0, r1 // 19 | 10000001
mov 1f, r0 // 1a | 00100000
// 1b | 00100001 : 1f
jz r0 // 1c | 01001000
mov 1, r0 // 1d | 00100000
// 1e | 00000001
// r3 << 1 (r3 = 216)
shl r0, r3 // 1f | 11000011
add r3, r1 // 20 | 11011101
1: // 21
// (testpoint) = r1
mov testpoint, r0 // 21 | 00100000
// 22 | 00000110 : testpoint
mov r1, (r0) // 23 | 00010100
// (attp) = 0
xor r1, r1 // 24 | 10110101
mov attp, r0 // 25 | 00100000
// 26 | 00000101 : attp
mov r1, (r0) // 27 | 00010100
// 8个采样周期,作为循环计数
// (recv_bcnt) = 8
mov 8, r1 // 28 | 00100001
// 29 | 00001000
mov recv_bcnt, r0 // 2a | 00100000
// 2b | 00000000 : recv_bcnt
mov r1, (r0) // 2c | 00010100
// 1.5个周期后是第一个采样点,所以先跳过一个采样点
// 因为指令读取 attp 的行为对于时钟计数有延迟,
// 实际上有很小的概率把第一个采样点跳过,导致数据错误
L2: // 2d
mov attp, r0 // 2d | 00100000
// 2e | 00000101 : attp
mov (r0), r1 // 2f | 01100001
mov 1, r0 // 30 | 00100000
// 31 | 00000001
and r0, r1 // 32 | 10000001
mov L2, r0 // 33 | 00100000
// 34 | 00101101 : L2
jz r0 // 35 | 01001000
xor r1, r1 // 36 | 10110101
mov attp, r0 // 37 | 00100000
// 38 | 00000101
mov r1, (r0) // 39 | 00010100
recv: // 3a
// attp 在 ucnt 到达 testpoint 时会自动设为1
// 检测 attp 即可得知是否到达当前采样点
mov attp, r0 // 3a | 00100000
// 3b | 00000101 : attp
mov (r0), r1 // 3c | 01100001
mov 1, r0 // 3d | 00100000
// 3e | 00000001
and r0, r1 // 3f | 10000001
// 没到采样点时跳转回recv
mov recv, r0 // 40 | 00100000
// 41 | 00111010 : recv
jz r0 // 42 | 01001000
// 否则开始采样,先右移将上次采样结果保留
// 再使用 in 指令将这次的接收线的值存下来
mov 1, r0 // 43 | 00100000
// 44 | 00000001
shr r0, r3 // 45 | 11100011
in r3 // 46 | 01010011
// 将 attp 置 0,以等待下次的采样点到达
xor r1, r1 // 47 | 10110101
mov attp, r0 // 48 | 00100000
// 49 | 00000101 : attp
mov r1, (r0) // 4a | 00010100
// 循环计数减1
mov recv_bcnt, r0 // 4b | 00100000
// 4c | 00000000 : recv_bcnt
mov (r0), r1 // 4d | 01100001
mov 1, r0 // 4e | 00100000
// 4f | 00000001
sub r0, r1 // 50 | 11110001
mov recv_bcnt, r0 // 51 | 00100000
// 52 | 00000000 : recv_bcnt
mov r1, (r0) // 53 | 00010100
mov 0xff, r0 // 54 | 00100000
// 55 | 11111111
and r0, r1 // 56 | 10000001
// 循环计数为0时进入发送阶段
mov send_pre, r0 // 57 | 00100000
// 58 | 01011101 : send_pre
jz r0 // 59 | 01001000
// 否则跳回recv
mov recv, r0 // 5a | 00100000
// 5b | 00111010 : recv
jmp r0 // 5c | 01000000
// 发送准备阶段
send_pre: // 5d
xor r1, r1 // 5d | 10110101
// 因为是发送,采样点简单设为0就行,不需要特殊计算
mov testpoint, r0 // 5e | 00100000
// 5f | 00000110 : testpoint
mov r1, (r0) // 60 | 00010100
mov attp, r0 // 61 | 00100000
// 62 | 00000101 : attp
mov r1, (r0) // 63 | 00010100
// 8个采样周期,作为循环计数,这里懒得改符号名了,所以还是 recv_bcnt
// (recv_bcnt) = 8
mov 8, r1 // 64 | 00100001
// 65 | 00001000
mov recv_bcnt, r0 // 66 | 00100000
// 67 | 00000000 : recv_bcnt
mov r1, (r0) // 68 | 00010100
// 加载起始位
L1: // 69
mov attp, r0 // 69 | 00100000
// 6a | 00000101 : attp
mov (r0), r1 // 6b | 01100001
mov 1, r0 // 6c | 00100000
// 6d | 00000001
and r0, r1 // 6e | 10000001
mov L1, r0 // 6f | 00100000
// 70 | 01101001 : L1
jz r0 // 71 | 01001000
// r0 归零,并输出 r0
xor r0, r0 // 72 | 10110000
out r0 // 73 | 01010100
// 将 attp 置 0,以等待下次的采样点到达
xor r1, r1 // 74 | 10110101
mov attp, r0 // 75 | 00100000
// 76 | 00000101 : attp
mov r1, (r0) // 77 | 00010100
// 发送8个数据位
send: // 78
mov attp, r0 // 78 | 00100000
// 79 | 00000101 : attp
mov (r0), r1 // 7a | 01100001
mov 1, r0 // 7b | 00100000
// 7c | 00000001
and r0, r1 // 7d | 10000001
// 没到采样点时跳回 send
mov send, r0 // 7e | 00100000
// 7f | 01111000 : send
jz r0 // 80 | 01001000
// 先输出 r3 ,再将 r3 右移,这样下次再执行到这里时,输出的最低位依然是要发送的位
out r3 // 81 | 01010111
mov 1, r0 // 82 | 00100000
// 83 | 00000001
shr r0, r3 // 84 | 11100011
// attp 置 0,不再重复说明
xor r1, r1 // 85 | 10110101
mov attp, r0 // 86 | 00100000
// 87 | 00000101 : attp
mov r1, (r0) // 88 | 00010100
// 循环计数减1
mov recv_bcnt, r0 // 89 | 00100000
// 8a | 00000000 : recv_bcnt
mov (r0), r1 // 8b | 01100001
mov 1, r0 // 8c | 00100000
// 8d | 00000001
sub r0, r1 // 8e | 11110001
mov recv_bcnt, r0 // 8f | 00100000
// 90 | 00000000 : recv_bcnt
mov r1, (r0) // 91 | 00010100
mov 0xf, r0 // 92 | 00100000
// 93 | 00001111
and r0, r1 // 94 | 10000001
// 循环计数为0时发送完成,即将发送结束位
mov tr_end, r0 // 95 | 00100000
// 96 | 10011011 : tr_end
jz r0 // 97 | 01001000
// 否则回到 send,发送下一位
mov send, r0 // 98 | 00100000
// 99 | 01111000 : send
jmp r0 // 9a | 01000000
// 传输完成,发送结束位
tr_end: // 9b
mov attp, r0 // 9b | 00100000
// 9c | 00000101 : attp
mov (r0), r1 // 9d | 01100001
mov 1, r0 // 9e | 00100000
// 9f | 00000001
and r0, r1 // a0 | 10000001
// 同样读取 attp ,没到采样点时跳回 tr_end
mov tr_end, r0 // a1 | 00100000
// a2 | 10011011 : tr_end
jz r0 // a3 | 01001000
// 加载结束位
mov 1, r0 // a4 | 00100000
// a5 | 00000001
out r0 // a6 | 01010100
// 回到开头接收下一个字节
rtn // a7 | 01110000
section data
recv_bcnt: 8
preserved: resb 5 - .
attp: 0
testpoint: 0
ucnt:
汇编代码完成,没写汇编器,靠人编的(见汇编代码右侧注释),然后利用一个简单的程序将该汇编代码中的所有8位指令码提取出来,相信各位的软件能力,简单demo呈现:
#include <iostream>
#include <fstream>
#include <string>
int cnt = 0; // 全局计数器
std::string extractor(const std::string& input) {
std::string result;
if(input.find("(replaced)") != std::string::npos) {
return result; // 如果没有 "(replaced)",直接返回原始字符串
}
int sz = input.size();
for(int i = 0; i <= sz - 8; ++i)
{
bool flag = false;
for(int j = 0; j < 8; ++j)
{
if(input[i + j] >= '0' && input[i + j] <= '9') continue;
else {
flag = true; // 如果有非数字字符,标记为true
break;
}
}
if(!flag) { // 如果没有非数字字符
result += input.substr(i, 8); // 提取8个字符
std::cout << "Extracted: " << result << std::endl; // 输出提取的字符串
++cnt; // 增加计数器
return result;
}
}
return ""; // 如果没有找到符合条件的字符串,返回空字符串
}
void processFile(const std::string& inputFilename) {
// 构造输出文件名
size_t dotPos = inputFilename.find_last_of('.');
std::string outputFilename = inputFilename.substr(0, dotPos) + "_ic.hex";
// 打开输入文件
std::ifstream inputFile(inputFilename);
if (!inputFile.is_open()) {
std::cerr << "Error: Could not open input file " << inputFilename << std::endl;
return;
}
// 打开输出文件
std::ofstream outputFile(outputFilename);
if (!outputFile.is_open()) {
std::cerr << "Error: Could not create output file " << outputFilename << std::endl;
inputFile.close();
return;
}
// 逐行处理文件
std::string line;
while (std::getline(inputFile, line)) {
std::string result = extractor(line);
if (!result.empty()) { // 只有结果非空时才写入
outputFile << result << '\n';
}
}
// 关闭文件
inputFile.close();
outputFile.close();
std::cout << "Processing completed. Results written to " << outputFilename << std::endl;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <input_file.xxx>" << std::endl;
return 1;
}
std::string inputFilename = argv[1];
processFile(inputFilename);
std::ofstream outputFile(inputFilename.substr(0, inputFilename.find_last_of('.')) + "_ic.hex", std::ios::app);
for(int i = cnt; i < 256; ++i){
outputFile << "00000000\n"; // 填充空行
}
return 0;
}
用法如下:
./[可执行程序名称] [文件名]
比如说我的可执行程序时 icex.exe,需要提取8位指令码的文件为 uart.asm,那么我就输入如下命令:
./icex.exe uart.asm
超频
感兴趣的可以看看,结论先放出来,不分配是最好的
接下来我们需要决策一下使用几分频:
首先试一试四分频(数据传输安全,但帧率低):

然后试试二分频(数据传输不太安全,但帧率变高):

最后试一试不分频(数据传输没有保障,但帧率极高):

那么,我们该如何计算正确率呢,没错,再写一个小demo:
#include <iostream>
#include <fstream>
#include <string>
double calculater(const std::string &inputFilename, const std::string &compare_num)
{
std::ifstream inputFile(inputFilename);
if (!inputFile.is_open()) {
std::cerr << "Error: Could not open input file " << inputFilename << std::endl;
return -1;
}
int total_num = 0, true_num = 0;
std::string word;
while(inputFile >> word)
{
++total_num;
if(word == compare_num) ++true_num;
}
return (double)(true_num) / total_num;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <input_file.xxx>" << "compare_str" << std::endl;
return 1;
}
std::string inputFilename = argv[1];
std::cout << calculater(inputFilename, argv[2]);
return 0;
}
检测正确率如下,从上至下分别是四分频,二分频以及不分频:
PS C:\altera\90\quartus\project\core8> ./acc.exe check.txt 8A
0.807823
PS C:\altera\90\quartus\project\core8> ./acc.exe check.txt 8A
0.880952
PS C:\altera\90\quartus\project\core8> ./acc.exe check.txt 8A
0.955782
最后把所有的代码(适配uart版本)呈现给大家:
适配 uart 的 CPU 代码
core8.v
uart 汇编代码
uart.asm
辅助\测试工具 demo
icex.cpp
accurancy.cpp