WizardLQ’s | 魔法师の小茶馆

Keep moving, never give up. | 锲而不舍,金石可镂.

简单线性回归

简单线性回归

海拔与温度数据

1
2
3
4
5
6
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')
1
2
data = pd.read_csv('exercise/height.vs.temperature.csv')
data.head()
height temperature
0 0.0 12.834044
1 500.0 10.190649
2 1000.0 5.500229
3 1500.0 2.854665
4 2000.0 -0.706488
1
data.dtypes
height         float64
temperature    float64
dtype: object
1
data.columns
Index(['height', 'temperature'], dtype='object')
1
2
3
4
plt.figure(figsize=(16, 8))
plt.scatter(data['height'], data['temperature'], c= 'black')
plt.xlabel('Height')
plt.ylabel('Temperature')
Text(0, 0.5, 'Temperature')

png

1
2
X = data['height'].values.reshape(-1, 1)
y = data['temperature'].values.reshape(-1, 1)
1
2
linear_reg = LinearRegression()
linear_reg.fit(X, y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
1
2
3
4
5
6
a = linear_reg.coef_[0][0]
b = linear_reg.intercept_[0]

print('a = {:.5f}'.format(a))
print('b = {:.5f}'.format(b))
print('回归方程为: y={0:.5f}x+{1:.5f}'.format(a, b))
a = -0.00657
b = 12.71851
回归方程为: y=-0.00657x+12.71851

可视化回归方程

1
2
3
4
5
6
7
plt.figure(figsize=(16, 8))
plt.scatter(data['height'], data['temperature'], c= 'black')
plt.xlabel('Height')
plt.ylabel('Temperature')

predictions = linear_reg.predict(X)
plt.plot(X, predictions, c='blue', linewidth=2)
[<matplotlib.lines.Line2D at 0x7f6666899588>]

png

预测

1
2
3
x_0 = int(8000)
y_0 = linear_reg.predict([[x_0]])
print('预测海拔{0}的气温为{1:.5f}摄氏度'.format(x_0, y_0[0][0]))
预测海拔8000的气温为-39.83777摄氏度

基于有限状态机的8位RISC CPU的Verilog实现

一. 设计需求

当今绝大多数计算机,无论是大型机还是微型机其基本结构都是冯诺依曼体系结构,即计算机由控制器,运算器,存储器,和输入/输出设备五部分组成。指令和程序都是存储在存储器中。

中央处理器,也称微处理器(microprocessor),是计算机系统的核心。主要完成以下任务:(1)从存储器中取指令,指令译码;(2)执行简单的算数逻辑运算;(3)在处理器和存储器或I/O之间传送数据;(4)程序流向控制等[1]。

RISC,reduced instruction set computer,精简指令集计算机相较于CISC,complex instruction set computer,复杂指令集计算机能够在一个时钟周期执行更多的指令,这些指令比CISC的指令集具有更短,功能更简单,指令格式更统一等特点,因此适合流水线处理,加快处理速度。

8位CPU是上世纪70年代开始采用的典型CPU结构,代表产品有因特尔的8080系列[1]。是现代普遍采用的64位,32位总线结构CPU的起始。

本文将基于有限状态机(Finite State Machine, FSM)采用Verilog硬件描述语言对8位RISC架构CPU进行实现。

二. 硬件组成

如图是微型计算机系统中关键组成部分,包含CPU,存储器,数据和地址总线。CPU主要由算数逻辑单元(ALU,Arithmetic Logic Unit),累加器(accumulator),通用寄存器(registers),程序计数器(PC,Program Counter),指令寄存器(IR,Instruction Register),地址选择器(address multiplexer)组成。存储器这里指主存储器,分为随机存取存储器RAM(Radom Access Memory)和只读存储器ROM(Read Only Memory)。主存和CPU之间通过总线访问,总线有地址总线(Address Bus)和数据总线(Data Address)两种。

2.1 存储器

2.1.1 ROM

ROM用于存储要执行的指令,关于指令的介绍见第三章。

Verilog实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module rom(data, addr, read, ena);
input read, ena;
input [7:0] addr;
output [7:0] data;

reg [7:0] memory[255:0];


// note: Decimal number in the bracket
initial begin
memory[0] = 8'b000_00000; //NOP
... // some instructions
end

assign data = (read&&ena)? memory[addr]:8'hzz;

endmodule

ROM,只读指令。接受输入地址,当读信号和使能信号高电平时输出对应地址存储的指令,否则输出保持高阻态。地址和数据都是8位,可寻址以及内部存储的大小为256Bytes。

RTL(register-transfer level,寄存器传输级)综合如上图所示。

2.2.2 RAM

存储数据,可读可写。

Verilog实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
module ram(data, addr, ena, read, write);
input ena, read, write;
input [7:0] addr;
inout [7:0] data;

reg [7:0] ram[255:0];

assign data = (read&&ena)? ram[addr]:8'hzz; // read data from RAM

always @(posedge write) begin // write data to RAM
ram[addr] <= data;
end
endmodule

可读可写,接收8位地址,当读信号和使能信号有效时,输出对应地址存储的数据,否则输出保持高阻态。当写信号上升沿是触发,将输入输出写入地址对应位置。内部存储以及可循址大小也为256Byters。

RTL视图如上。

2.2 CPU

2.2.1 PC

程序计数器,有时也叫做指令地址寄存器(Instruction Address Register, IAR),对应于Intel
X86体系CPU中的指令指针(Instruction pointer)寄存器。其功能是用来存放要执行的下一条指令在现行代码段中的偏移地址。本文中PC由Controller自动修改,使得其中始终存放着下一条将要执行指令的地址。因此,PC是用来控制指令序列执行流程的寄存器[2]。

Verilog实现:

1
2
3
4
5
6
7
8
9
10
11
12
//PC, program counter
module counter(pc_addr, clock, rst, en);
input clock, rst, en;
output reg [7:0] pc_addr;
always @(posedge clock or negedge rst) begin
if(!rst) pc_addr <= 8'd0;
else begin
if(en) pc_addr <= pc_addr+1;
else pc_addr <= pc_addr;
end
end
endmodule

异步清零。时钟上升沿触发,高电平使能时程序计数器计数,指向下一条要执行指令的地址。指令存储在ROM中,故每次pc_addr加1。

RTL视图如上。

2.2.2 累加器

累加器,用于储存计算的中间结果。

Verilog实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Accumulator
module accum( in, out, ena, clk, rst);
// a register, to storage result after computing
input clk,rst,ena;
input [7:0] in;
output reg [7:0] out;

always @(posedge clk or negedge rst) begin
if(!rst) out <= 8'd0;
else begin
if(ena) out <= in;
else out <= out;
end
end
endmodule

异步清零。时钟上升沿触发,高电平使能时输出当前输入信号。

RTL视图如上,可以看出其是一个Q触发器来实现。

2.2.3 地址选择器

接受控制使能信号对输入的来自程序计数器和指令寄存器的地址进行选择。

Verilog实现:

1
2
3
4
5
6
7
8
// Address multiplexer
module addr_mux(addr, sel, ir_ad, pc_ad);
// To choose address of instruction register or address of program counter
input [7:0] ir_ad, pc_ad;
input sel;
output [7:0] addr;
assign addr = (sel)? ir_ad:pc_ad;
endmodule

当选择信号为1时,选择来自寄存器输入的地址到数据总线,否则将程序计数器中的地址加载到数据总线。

RTL视图如上。

2.2.4 ALU

算术逻辑运算单元,根据指令类型来决定进行哪种运算,从而将运算结果输出通用寄存器或者累加器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module alu(alu_out, alu_in, accum, op);
// Arithmetic logic unit
// to perform arithmetic and logic operations.
input [2:0] op;
input [7:0] alu_in,accum;
output reg [7:0] alu_out;

parameter NOP=3'b000,
LDO=3'b001,
LDA=3'b010,
STO=3'b011,
PRE=3'b100,
ADD=3'b101,
LDM=3'b110,
HLT=3'b111;

always @(*) begin
casez(op)
NOP: alu_out = accum;
HLT: alu_out = accum;
LDO: alu_out = alu_in;
LDA: alu_out = alu_in;
STO: alu_out = accum;
PRE: alu_out = alu_in;
ADD: alu_out = accum+alu_in;
LDM: alu_out = accum;
default: alu_out = 8'bzzzz_zzzz;
endcase
end
endmodule

RTL视图如上。

2.2.5 通用寄存器

通用寄存器,ALU输出结果,指令寄存器输出的操作数都可以存储到寄存器中的特定的地址。输出寄存器中存储的数据到数据总线。

Verilog实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
module reg_32(in, data, write, read, addr, clk);
input write, read, clk;
input [7:0] in;
input [7:0] addr;
//!Warning: addr should be reduced to 5 bits width, not 8 bits width.
//input [4:0] addr;

output [7:0] data;

reg [7:0] R[31:0]; //32Byte
wire [4:0] r_addr;

assign r_addr = addr[4:0];
assign data = (read)? R[r_addr]:8'hzz; //read enable

always @(posedge clk) begin //write, clk posedge
if(write) R[r_addr] <= in;
end
endmodule

当写信号有效时,将输入数据(来自ALU的输出)存储到寄存器中的特定地址。当读信号有效时,将寄存器中特定位置的数据输出(到数据总线)。寄存器大小为32Bytes。

RTL视图如上。

2.2.6 IR

指令寄存器,从数据总线上获取数据,根据输入控制信号,根据指令类型将特定指令和地址输出到ALU,通用寄存器和地址选择器。

verilog实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// instruction register
module ins_reg(data, fetch, clk, rst, ins, ad1, ad2);
input clk, rst;
input [1:0] fetch;
input [7:0] data;
output [2:0] ins;
output [4:0] ad1;
output [7:0] ad2;

reg [7:0] ins_p1, ins_p2;
reg [2:0] state;

assign ins = ins_p1[7:5]; //hign 3 bits, instructions
assign ad1 = ins_p1[4:0]; //low 5 bits, register address
assign ad2 = ins_p2;

always @(posedge clk or negedge rst) begin
if(!rst) begin
ins_p1 <= 8'd0;
ins_p2 <= 8'd0;
end
else begin
if(fetch==2'b01) begin //fetch==2'b01 operation1, to fetch data from REG
ins_p1 <= data;
ins_p2 <= ins_p2;
end
else if(fetch==2'b10) begin //fetch==2'b10 operation2, to fetch data from RAM/ROM
ins_p1 <= ins_p1;
ins_p2 <= data;
end
else begin
ins_p1 <= ins_p1;
ins_p2 <= ins_p2;
end
end
end
endmodule

异步清零。当输入控制信号为01时表示数据总线当前为指令(形式为指令编码+寄存器地址,见第三章),将其从insad1输出,当控制信号为10时,表示当前数据总线上的为数据(8位地址数据,见第三章),将其从ad2输出到地址选择器。

RTL视图如上。

2.3 内部结构(总)

如图是系统内部结构原理图,显示了各个部件之间的连接关系,数据总线和地址总线是总线系统的核心。其中地址总线连接了地址选择器的输出,ROM以及RAM的输入端。地址总线和ROM/RAM的输出,IR和ALU的输入,以及通用寄存器的输出相连。控制器controller(图左上方)是系统的控制单元,相关细节见第四章。

整个硬件系统使用元件例化语句的Verilog描述如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Core
// Top-level entity(except core-tb)
module core(clk, rst);
input clk, rst;

wire write_r, read_r, PC_en, ac_ena, ram_ena, rom_ena;
wire ram_write, ram_read, rom_read, ad_sel;

wire [1:0] fetch;
wire [7:0] data, addr;
wire [7:0] accum_out, alu_out;
wire [7:0] ir_ad, pc_ad;
wire [4:0] reg_ad;
wire [2:0] ins;

ram RAM1(.data(data),
.addr(addr),
.ena(ram_ena),
.read(ram_read),
.write(ram_write)); //module ram(data, addr, ena, read, write);

rom ROM1(.data(data),
.addr(addr),
.ena(rom_ena),
.read(rom_read)); //module rom(data, addr, read, ena);

addr_mux MUX1(.addr(addr),
.sel(ad_sel),
.ir_ad(ir_ad),
.pc_ad(pc_ad)); //module addr_mux(addr, sel, ir_ad, pc_ad);

counter PC1(.pc_addr(pc_ad),
.clock(clk),
.rst(rst),
.en(PC_en)); //module counter(pc_addr, clock, rst, en);

accum ACCUM1(.out(accum_out),
.in(alu_out),
.ena(ac_ena),
.clk(clk),
.rst(rst)); //module accum( in, out, ena, clk, rst);

alu ALU1(.alu_out(alu_out),
.alu_in(data),
.accum(accum_out),
.op(ins)); // module alu(alu_out, alu_in, accum, op);

reg_32 REG1(.in(alu_out),
.data(data),
.write(write_r),
.read(read_r),
.addr({ins,reg_ad}),
.clk(clk));
//module reg_32(in, data, write, read, addr, clk);
//reg_32 REG1(.in(alu_out), .data(data), .write(write_r), .read(read_r), .addr(reg_ad), .clk(clk));
//module reg_32(in, data, write, read, addr, clk);

ins_reg IR1(.data(data),
.fetch(fetch),
.clk(clk),
.rst(rst),
.ins(ins),
.ad1(reg_ad),
.ad2(ir_ad));
//module ins_reg(data, fetch, clk, rst, ins, ad1, ad2);

//module machine(ins, clk, rst, write_r, read_r, PC_en, fetch, ac_ena, ram_ena, rom_ena,ram_write, ram_read, rom_read, ad_sel);
controller CONTROLLER1(.ins(ins),
.clk(clk),
.rst(rst),
.write_r(write_r),
.read_r(read_r),
.PC_en(PC_en),
.fetch(fetch),
.ac_ena(ac_ena),
.ram_ena(ram_ena),
.rom_ena(rom_ena),
.ram_write(ram_write),
.ram_read(ram_read),
.rom_read(rom_read),
.ad_sel(ad_sel)
);
endmodule

各个模块进行例化后的系统总体RTL视图如上。

三. 指令集

我们定义的RISC指令集长度类型两种,分别为短指令和长指令:

其中指令编码采用三位二进制表示,共定义有8种指令。短指令共8位,高三位为指令编码,低五位为通用寄存器地址。长指令为16位,每个长指令分两次取,每次取8位,首先取高8位,格式和短指令相通,也是高3位为指令编码,低5位为通用寄存器地址;第二次取低8位,表示ROM或者RAM地址,取决于指令编码。

因此有指令集如下表所示,为了方便理解指令的缩写含义,表中用英文进行了描述并将缩写的由来使用加粗来表示:

INS Binary Description Type Comment
NOP 000 No operation Short 空操作
LDO 001 Loads the contents of the ROM address into the REG address Long REG[reg_addr]<-ROM[ROM_addr]
LDA 010 Loads the contents of the RAM address into the REG address Long REG[reg_addr]<-RAM[RAM_addr]
STO 011 Store intermediate results into RAM address Long RAM[RAM_addr]<-REG[reg_addr]
PRE 100 Prefetch Data from REG address Short ACCUM<-REG[reg_addr]
ADD 101 Adds the contents of the REG address or integer to the accumulator Short accumulator<-REG[reg_addr]+ ACCUM
LDM 110 Load Multiple Short REG[reg_addr]<-ACCUM
HLT 111 Halt Short 停机指令

四. 控制器

控制器是系统的核心,具有以下功能:取指令,指令排队,读写操作数,总线控制等。这里采用(Mealy型)有限状态机(FSM)来实现控制器,指令存储在ROM中来执行,控制器接受外界时钟和复位信号,控制器根据当前状态以及输入进行状态的转移。

4.1 状态转移图

根据指令的任务,我们设计了如上图所示的状态转移图,从左至右依次为状态Sidle,S0~S12。各个状态的含义如下:

Source State Description Comment
S0 Load ir1 取指令1(短指令或者长指令的第一个)
S1 PC+1 每执行一条PC+1
S2 HLT 停机
S3 Load ir2 取指令2
S4 PC+1 每执行一条PC+1
S5 ROM/RAM to REG LDA/LDO
S6 Protect 写保护
S7 Read REG STO第1阶段
S8 Write RAM STO第2阶段
S9 Read REG PRE/ADD,第1阶段
S10 Write ACCUM PRE/ADD,第2阶段
S11 Write REG LDM
S12 Protect LDM
Sidle Reset 重启

各个状态之间的转移有:

S0 S1 S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12 Sidle
S0 1 0 0 0 0 0 0 0 0 0 0 0 0 1
S1 0 1 0 0 0 0 0 0 0 0 0 0 0 1
S2 0 0 1 0 0 0 0 0 0 0 0 0 0 1
S3 0 0 0 1 0 0 0 0 0 0 0 0 0 1
S4 0 0 0 0 1 0 0 0 0 0 0 0 0 1
S5 0 0 0 0 0 1 0 0 0 0 0 0 0 1
S6 0 0 0 0 0 0 1 0 0 0 0 0 0 1
S7 0 0 0 0 0 0 0 1 0 0 0 0 0 1
S8 0 0 0 0 0 0 0 0 1 0 0 0 0 1
S9 0 0 0 0 0 0 0 0 0 1 0 0 0 1
S10 0 0 0 0 0 0 0 0 0 0 1 0 0 1
S11 0 0 0 0 0 0 0 0 0 0 0 1 0 1
S12 0 0 0 0 0 0 0 0 0 0 0 0 1 1
Sidle 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Source State Destination State Condition
S0 S1
S1 S0 (!ins[0]).(!ins[1]).(!ins[2])
S1 S3 (!ins[0]).(ins[1]).(!ins[2]) + (ins[0]).(!ins[2])
S1 S11 (!ins[0]).(ins[1]).(ins[2])
S1 S9 (!ins[1]).(ins[2])
S1 S2 (ins[0]).(ins[1]).(ins[2])
S2 S2
S3 S4
S4 S7 (!ins[0]).(!ins[1]) + (!ins[0]).(ins[1]).(ins[2]) + (ins[0]).(!ins[1]).(ins[2]) + (ins[0]).(ins[1])
S4 S5 (!ins[0]).(ins[1]).(!ins[2]) + (ins[0]).(!ins[1]).(!ins[2])
S5 S6
S6 S0
S7 S8
S8 S0
S9 S10
S10 S0
S11 S12
S12 S0
Sidle S0

例如我们可以看到S0,S1的状态转移:

详情请见附件fsm.pdf

关于图示有限状态机的verilog实现,这里采用了经典的3段式结构:状态寄存器(state register),下一个状态组合逻辑电路(Next-state combinational logic),输出组合逻辑电路(Output combinational logic)。

4.2 FSM之状态寄存器

本质是一个D触发器,负责将下一个状态赋给当前状态值(即跳转到下一个状态),异步清零。

1
2
3
4
5
6
7
8
//PART A: D flip latch; State register
always @(posedge clk or negedge rst)
begin
if(!rst) state<=Sidle;
//current_state <= Sidle;
else state<=next_state;
//current_state <= next_state;
end

4.3 FSM之下一个状态组合逻辑

负责控制状态的转移,这里下一个状态跟当前状态state以及输入ins均有关,属于Mealy型状态机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//PART B: Next-state combinational logic
always @*
begin
case(state)
S1: begin
if (ins==NOP) next_state=S0;
else if (ins==HLT) next_state=S2;
else if (ins==PRE | ins==ADD) next_state=S9;
else if (ins==LDM) next_state=S11;
else next_state=S3;
end

S4: begin
if (ins==LDA | ins==LDO) next_state=S5;
//else if (ins==STO) next_state=S7;
else next_state=S7; // ---Note: there are only 3 long instrucions. So, all the cases included. if (counter_A==2*b11)
end
Sidle: next_state=S0;
S0: next_state=S1;
S2: next_state=S2;
S3: next_state=S4;
S5: next_state=S6;
S6: next_state=S0;
S7: next_state=S8;
S8: next_state=S0;
S9: next_state=S10;
S10: next_state=S0;
S11: next_state=S12;
S12: next_state=S0;
default: next_state=Sidle;
endcase
end

4.4 FSM之输出组合逻辑

输出组合逻辑电路根据当前状态以及输入命令,来确定输出值。

由于篇幅较长,见附录。

五. 测试及结果

为了验证RISC CPU功能的正确与否,下面进行芯片进行测试。

5.1 测试指令

ROM中存储的指令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// note: Decimal number in the bracket
initial begin
memory[0] = 8'b000_00000; //NOP

memory[1] = 8'b001_00001; //LDO s1
memory[2] = 8'b010_00001; //rom(65) //end, reg[1]<-rom[65]
memory[3] = 8'b001_00010; //LDO s2
memory[4] = 8'b010_00010; //rom(66) //end, reg[2]<-rom[66]
memory[5] = 8'b001_00011; //LDO s3
memory[6] = 8'b010_00011; //rom(67) //end, reg[3]<-rom[67]

memory[7] = 8'b100_00001; //PRE s1
memory[8] = 8'b101_00010; //ADD s2
memory[9] = 8'b110_00001; //LDM s1 // REG[1] <- REG[1]+REG[2]

memory[10] = 8'b011_00001; //STO s1
memory[11] = 8'b000_00001; //ram(1) // RAM[1] <- REG[1]
memory[12] = 8'b010_00010; //LDA s2
memory[13] = 8'b000_00001; //ram(1) // REG[2] <- RAM[1]

memory[14] = 8'b100_00011; //PRE s3
memory[15] = 8'b101_00010; //ADD s2
memory[16] = 8'b110_00011; //LDM s3 // REG[3] <- REG[2]+REG[3]

memory[17] = 8'b011_00011; //STO s3
memory[18] = 8'b000_00010; //ram(2) //REG[3] -> ram[2]
memory[19] = 8'b111_00000; //HLT

memory[65] = 8'b001_00101; //37
memory[66] = 8'b010_11001; //89
memory[67] = 8'b001_10101; //53
end

指令按照顺序执行,最终的结果是将ROM中的65,66,67位的三个数进行加法,存储到RAM[2]中,即实现三个数的加法,于此同时RAM[1]存放着前两个数加法的和。

5.2 Test-Bench

为了测试系统的功能,这里生成/编写test-bench文件,用于仿真:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
`timescale 1ps / 1ps
module core_tb_00 ;

reg rst ;
reg clk ;
core
DUT (
.rst (rst ) ,
.clk (clk ) );

// "Clock Pattern" : dutyCycle = 50
// Start Time = 0 ps, End Time = 10 ns, Period = 100 ps
initial
begin
clk = 1'b0 ;
# 150 ;
// 50 ps, single loop till start period.
repeat(99)
begin
clk = 1'b1 ;
#50 clk = 1'b0 ;
#50 ;
// 9950 ps, repeat pattern in loop.
end
clk = 1'b1 ;
# 50 ;
// dumped values till 10 ns
end


// "Constant Pattern"
// Start Time = 0 ps, End Time = 10 ns, Period = 0 ps
initial
begin
rst = 1'b0 ;
# 100;
rst=1'b1;
# 9000 ;
// dumped values till 10 ns
end

initial
#20000 $stop;
endmodule

只需要给CPU两个信号,激励时钟clk和异步复位信号rst

5.3 波形

根据ModelSIM仿真结果,如上图所示累加器输出最终结果179,在最后的停机指令前(图中6300ps处),addr地址为2,data为179,ram写,使能信号均为1,将最终结果写入到了RAM[2]中,指令指令结果无误。

从仿真波形中,不仅可以看出每个控制信号在每个时刻的状态,还可以看出每条指令执行的状态机的状态转换信息:

如图所示,从波形可以看出执行一个LDO长指令消耗了6个时钟周期,NOP指令消耗了两个时钟周期,和状态转换图一致,得到的结果和测试指令的输出要求一致。

如图是两个最重要的用来验证功能正确性的两个时刻,从波形可以看出,相应的计算结果126,179分别被写到了RAM的地址第1,2位置,相关控制信号正常。即达到了我们设计的功能。更多关于波形的情况,见附件即仿真源文件。

六. 结论

本文构建了8位的RISC CPU,详细介绍了设计过程和实验测试,包括:硬件组成,指令集系统等。重点在于控制器的设计,基于有限状态机,实现了指令和状态之间的对应和转移,并进行了详实的仿真实验,结果证明CPU功能正常,达到了预期。

参考文献

[1]周荷琴, 吴秀清. 微型计算机原理和接口技术(第三版).中国科学技术大学出版社. 2008.

附录

附录A Controller的Verilog实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
module controller(ins, clk, rst, write_r, read_r, PC_en, fetch, ac_ena, ram_ena, rom_ena,ram_write, ram_read, rom_read, ad_sel);

input clk, rst; // clock, reset
input [2:0] ins; // instructions, 3 bits, 8 types

// Enable signals
output reg write_r, read_r, PC_en, ac_ena, ram_ena, rom_ena;

// ROM: where instructions are storaged. Read only.
// RAM: where data is storaged, readable and writable.
output reg ram_write, ram_read, rom_read, ad_sel;

output reg [1:0] fetch; // 01: to fetch from RAM/ROM; 10: to fetch from REG

// State code(current state)
reg [3:0] state; // current state
reg [3:0] next_state; // next state


// instruction code
parameter NOP=3'b000, // no operation
LDO=3'b001, // load ROM to register
LDA=3'b010, // load RAM to register
STO=3'b011, // Store intermediate results to accumulator
PRE=3'b100, // Prefetch Data from Address
ADD=3'b101, // Adds the contents of the memory address or integer to the accumulator
LDM=3'b110, // Load Multiple
HLT=3'b111; // Halt

// state code
parameter Sidle=4'hf,
S0=4'd0,
S1=4'd1,
S2=4'd2,
S3=4'd3,
S4=4'd4,
S5=4'd5,
S6=4'd6,
S7=4'd7,
S8=4'd8,
S9=4'd9,
S10=4'd10,
S11=4'd11,
S12=4'd12;

//PART A: D flip latch; State register
always @(posedge clk or negedge rst)
begin
if(!rst) state<=Sidle;
//current_state <= Sidle;
else state<=next_state;
//current_state <= next_state;
end

//PART B: Next-state combinational logic
always @*
begin
case(state)
S1: begin
if (ins==NOP) next_state=S0;
else if (ins==HLT) next_state=S2;
else if (ins==PRE | ins==ADD) next_state=S9;
else if (ins==LDM) next_state=S11;
else next_state=S3;
end

S4: begin
if (ins==LDA | ins==LDO) next_state=S5;
//else if (ins==STO) next_state=S7;
else next_state=S7; // ---Note: there are only 3 long instrucions. So, all the cases included. if (counter_A==2*b11)
end
Sidle: next_state=S0;
S0: next_state=S1;
S2: next_state=S2;
S3: next_state=S4;
S5: next_state=S6;
S6: next_state=S0;
S7: next_state=S8;
S8: next_state=S0;
S9: next_state=S10;
S10: next_state=S0;
S11: next_state=S12;
S12: next_state=S0;
default: next_state=Sidle;
endcase
end

// another style
//PART C: Output combinational logic
always@*
begin
case(state)
// --Note: for each statement, we concentrate on the current state, not next_state
// because it is combinational logic.
Sidle: begin
write_r=1'b0;
read_r=1'b0;
PC_en=1'b0;
ac_ena=1'b0;
ram_ena=1'b0;
rom_ena=1'b0;
ram_write=1'b0;
ram_read=1'b0;
rom_read=1'b0;
ad_sel=1'b0;
fetch=2'b00;
end
S0: begin // load IR
write_r=0;
read_r=0;
PC_en=0;
ac_ena=0;
ram_ena=0;
rom_ena=1;
ram_write=0;
ram_read=0;
rom_read=1;
ad_sel=0;
fetch=2'b01;
end
S1: begin
write_r=0;
read_r=0;
PC_en=1;
ac_ena=0;
ram_ena=0;
ram_write=0;
ram_read=0;
rom_ena=1;
rom_read=1;
ad_sel=0;
fetch=2'b00;
end
S2: begin
write_r=0;
read_r=0;
PC_en=0;
ac_ena=0;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;
fetch=2'b00;
end
S3: begin
write_r=0;
read_r=0;
PC_en=0;
ac_ena=1;
ram_ena=0;
rom_ena=1;
ram_write=0;
ram_read=0;
rom_read=1;
ad_sel=0;
fetch=2'b10;
end
S4: begin
write_r=0;
read_r=0;
PC_en=1;
ac_ena=1;
ram_ena=0;
ram_write=0;
ram_read=0;
rom_ena=1;
rom_read=1;
ad_sel=0;
fetch=2'b10;
end
S5: begin
if (ins==LDO)
begin
write_r=1;
read_r=0;
PC_en=0;
ac_ena=1;
ram_ena=0;
ram_write=0;
ram_read=0;
rom_ena=1;
rom_read=1;
ad_sel=1;
fetch=2'b01;
end
else
begin
write_r=1;
read_r=0;
PC_en=0;
ac_ena=1;
ram_ena=1;
ram_write=0;
ram_read=1;
rom_ena=0;
rom_read=0;
ad_sel=1;
fetch=2'b01;
end
end
S6: begin

write_r=1'b0;
read_r=1'b0;
PC_en=1'b0; //** not so sure, log: change 1 to 0
ac_ena=1'b0;
ram_ena=1'b0;
rom_ena=1'b0;
ram_write=1'b0;
ram_read=1'b0;
rom_read=1'b0;
ad_sel=1'b0;
fetch=2'b00;
end

S7: begin // STO, reg->ram. step1. read REG
write_r=0;
read_r=1;
PC_en=0;
ac_ena=0;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;
fetch=2'b00;
end
S8: begin // STO, step2, write RAM
write_r=0;
read_r=1;
PC_en=0;
ac_ena=0;
rom_read=0;
rom_ena=0;

ram_ena=1;
ram_write=1;
ram_read=0;

ad_sel=1;
fetch=2'b00; //fetch=2'b10, ram_ena=1, ram_write=1, ad_sel=1;
end
S9: begin
if (ins==PRE) // REG->ACCUM
begin
write_r=0;
read_r=1;
PC_en=0;
ac_ena=1;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;
fetch=2'b00;
end
else
begin
write_r=0;
read_r=1;
PC_en=0;
ac_ena=1;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;

fetch=2'b00;
end
end
S10: begin
write_r=0;
read_r=1;
PC_en=0;
ac_ena=0;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;
fetch=2'b00;
end
S11: begin // LDM, step1, write reg
write_r=1;
read_r=0;
PC_en=0;
ac_ena=1;
ram_ena=0;

ram_write=0;
ram_read=0;
rom_ena=1;
rom_read=1;
ad_sel=0;
fetch=2'b00;

end
S12: begin
write_r=0;
read_r=0;
PC_en=0;
ac_ena=0;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;
fetch=2'b00;
end
default: begin
write_r=0;
read_r=0;
PC_en=0;
ac_ena=0;
ram_ena=0;
rom_ena=0;
ram_write=0;
ram_read=0;
rom_read=0;
ad_sel=0;
fetch=2'b00;
end
endcase
end
endmodule

GitHub项目地址

https://github.com/liuqidev/8-bits-RISC-CPU-Verilog

Python3 tkinter简明教程

最小实例

1
2
3
4
import tkinter

top = tkinter.Tk()
top.mainloop()

显示一个空窗口,窗口名称为tk

_tkinter.TclError: couldn’t recognize data in image file

tkinter.PhotoImage只支持.gif https://blog.csdn.net/Scm_yyy/article/details/76098955

解决:

1
image = ImageTK.PhotoImage(file=Image.open(f))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import tkinter  
import tkinter.filedialog
from PIL import Image # conda install Pillow
from PIL import ImageTk # https://blog.csdn.net/dekiven1/article/details/77508504

top = tkinter.Tk()

def display_image():
f = tkinter.filedialog.askopenfilename(
parent=top, initialdir='/home/qi/Desktop',
title='Choose file',
filetypes=[('png images', '.png'),
('gif images', '.gif'),
('jpeg images', '.jpg')]
)

new_window = tkinter.Toplevel(top)
# Only .gif is supported
# https://blog.csdn.net/Scm_yyy/article/details/76098955
# image = tkinter.PhotoImage(file=Image.open(f))
# print(f)
image = ImageTk.PhotoImage(Image.open(f))

l1 = tkinter.Label(new_window, image=image)
l1.image = image
l1.pack()

# 显示图片
b1 = tkinter.Button(top, text='Display image', command=display_image)
b1.pack(fill='x')

top.mainloop()

1619年的辽东战役

万历四十七年,公元1619年的辽东战役,这一战役以努尔哈赤所带领的满族军队以少胜多大败明军结束,并成为明朝军队在东北丧失军力优势的转折点。
战役的起因由努尔哈赤挑起,前一年清太祖攻陷抚顺城,屠戮明军,明朝派遣张承荫讨伐失败。努尔哈赤的要求补偿的条件被明朝拒绝,前者继续侵犯辽东,明朝随招兵募款,战势一触即发。

作者考据,明朝这边总领杨镐所带兵力十万左右,努尔哈赤军队数量在5到六万左右。

杨镐将明军分为北,西,南,东南四路,分别为马林,杜松,李如柏,刘𫄧,战线绵延二百里,没有确定的攻击目标和重点。于3月26日出兵,扬言4月28日对努尔哈赤以四十七万兵力攻之,显然是虚张声势,努尔哈赤不吃这一套。4月4日,万历收到出兵之奏本。4月13日,努尔哈赤才得知杨镐出兵的消息。从这一点看似明军占据了主动性。然而,四路大军分别行动后,杨镐只能在辽宁司令部中静候各路战况,直到4月14日,西路杜松部队失败主帅战死。

杜松部队全军覆没的经过如下,14日午前其率兵度过浑河,奇怪的是其将火炮辎重置于河对岸。渡河之后,开战告捷,连克两道栅,生擒满洲兵十四人。遂深入,竟有满洲兵在此埋伏,足有三万之众。杜松部队想占领高地,扭转局势,没想到再次中伏,无人生还。

杜松渡河日期乃是完全符合杨镐所赐的军令时间。抛弃火器,乃是其麾下车营参将所为,原因在于浑河水深流急,渡过很困难,且观察到南面有满人骑兵小分队。杜松低估了车马渡河的困难,匆匆挺近。部下也没有上报这些困难以及给出提议,于是在杜松和满人血刃之时,后方火炮和支援却按兵不动。

明朝官员将此次失败归结于杜松本人的“贪功邀赏”。但作者考证,总领杨镐没有亲赴现场,也没有派遣使者搜集情报,至关重要的一点,其本人不知道满军主力就位于杜松部队途经的线路上。

而且非常讽刺的是,北方马林军就在距离杜松部队不远处的尚间崖附近,在杜松部队和敌人死战时居然不知也没能驰援。14日晚,马林收到西线杜松战败的消息,15日早晨其率领主力四万人撤到之前宿营地,利用先前构筑好的壕沟等,布置好方阵准备反击,监军潘宗颜带一万后卫营断后。书中说还有一万明军(并未讲清是什么部队),被努尔哈赤组织一千骑兵冲击下消灭。最后又和大贝勒一起和马林主力会战,以总共不足一万兵力将明军四万人击溃。潘宗颜在菲芬山被满洲军队尽数消灭。

仅仅又一天时间,明军北线也完全溃散,文官将罪责归咎于马林,马林于三个月后开原之战中战死。

得知西线和北线失利,杨镐召回南线李如柏部队,很迷的是东南线的刘𫄧并未收到停战撤退的命令。李如柏及时撤回,没有受到损失,但受到指控,说他和敌人勾结。


刘𫄧部队三万人,其中有一万三千人的朝鲜部队。朝鲜部队(其中约有五千炮兵),其都元帅姜弘立受命于刘綖,据称受迫参战,且明朝不提供粮饷。刘綖二十年前抗倭时被派驻到朝鲜,据传不得人心并且和杨镐不和(这也有可能是其没有收到停战命令的原因)。

4月20日,满军几路和刘𫄧主力激战,刘綖被俘。南部明、朝联军两万人以火器抵抗,后受到风力影响,姜弘立率朝鲜步兵投降,并将明军残部交给满洲人。

有人说1619年的辽东战争失败几乎是必然的。“匆匆召集,缺乏指导,未经训练,供应不足,装备低劣,纪律松懈”,“个人应尽最大努力,用自己的才干和自我牺牲掩盖制度上的弱点”。

三个月后,努尔哈赤接连兵围开原,攻下铁岭。杨镐被逮论死。总而言之,辽东战役让努尔哈赤清军对明朝军队的战斗力和制度有了清楚认识,也对明朝的国运产生了深远的影响。

黄仁宇其大历史观

本章介绍了作者【美国】黄仁宇写此书的背景,其并非典型的商业性的或者学术性的书。在我看来有点像是纪传体断代史。后文的介绍中说,黄仁宇小时候读书受太史公司马迁影像甚多,印证了笔者的看法。

作者说此书只代表作者的一部分意见,不代表全部的历史观点。这一部分意见,笔者感觉在书中字里行间反反复复出现和批评,那就是“道德代替法律”。作者说:“检察中国的官僚制度,不是否认中国的全部文化”,其“大历史”观,即从世界的空间维度以及长时间的时间维度上看待历史,但从后文可以看出,作者的历史观建立在“地理气候决定论”的基础上。


作者指出,中国立国向来以贫农及小自耕农的经济立场作为基础,农村内部情况复杂,从数量上和关系上理清并管理,必然要经过很长时间以及很大的代价。

后文作者对本书中的观点进一步阐明,作者在书中对于道德,态度是这样的,道德并非万能,道德不能代替技术,也不能代替道德,作者也并未说道德可以完全不要,而是要求能够使用法律和计数解决的问题,应当将道德评判排后。“狭义的道德观念基于狭义的宇宙观,就是武断地说出世界的根源如是,它的结局也必是如此”。

此外作者就“一国两制”,自己的宇宙观作了简要概括。