1、前言
本文章主要針對大學(xué)本科階段學(xué)生;
讀文章之前先來幾個靈魂拷問:
1、你是否學(xué)過《微機(jī)原理》、《單片機(jī)》、《匯編語言》之類有關(guān)微型計算機(jī)的課程?
2、上這些課時你的老師是否只是機(jī)械的講著PPT,你聽著無聊,聽不懂,逐漸對計算機(jī)專業(yè)產(chǎn)生了畏懼?
3、這些計算機(jī)專業(yè)的基礎(chǔ)課程你學(xué)懂了嗎?悟了嗎?真正理解了嗎?
4、這些課里面的專業(yè)術(shù)語你理解嗎?寄存器、總線、累加器。。。
以上4條都真正理解的人少之又少,你上學(xué)時怎么都理解不了,出來上班后就逐漸理解了,這是為啥呢?
因?yàn)樯蠈W(xué)時你面對的是枯燥的課本和混日子至念PPT的老師,加之這些東西都是抽象的沒有實(shí)物,你只能靠腦子想象,所以難以理解;
出來上班后你面對的是代碼,電路,板子,項(xiàng)目,以及公司里的大佬,你接觸的是實(shí)實(shí)在在的東西,所以沒有距離感,你天天敲著代碼,書本里的寄存器什么的都是你敲出來的,加之專業(yè)的大佬給你指點(diǎn)明津,所以你很容易就理解了;
2、設(shè)計思想和架構(gòu)
本設(shè)計采用純verilog代碼編寫,可在FPGA上綜合、編譯、運(yùn)行,起實(shí)質(zhì)就是一個小型CPU;
主要參數(shù)如下:
8位數(shù)據(jù)總線;
8位地址總線;
2位控制總線;
256字節(jié)ROM;用于存放cpu指令;
256字節(jié)RAM;用于存放cpu數(shù)據(jù);
1字節(jié)的cache,用于存放cpu計算的中間結(jié)果和預(yù)取;
32字節(jié)通用計算器,用于暫存cpu計算的中間結(jié)果;
2字節(jié)指令寄存器,用于暫存cpu指令和預(yù)取;
CPU功能很簡單,取ROM里面的65、66、67地址的三個常數(shù)累加和并輸出,整個過程由CPU自動完成,功能雖然簡單,卻涵蓋了計算機(jī)架構(gòu)的基本內(nèi)容和操作順序,后續(xù)的功能強(qiáng)大的CPU,也是由這樣簡單的計數(shù)慢慢發(fā)展起來的,其實(shí)CPU的本質(zhì)是很笨的,他只會最簡單的1+1的數(shù)學(xué)題,小學(xué)生水平都不如,只不過他的計算速度很快,顯得很聰明而已,當(dāng)你看懂了代碼就會明白,計算機(jī)真的很蠢。
設(shè)計架構(gòu)如下:
由于線太多不好連接,就畫了一個框架;后面會一一講解;
3、硬件組成講解
ROM用于存儲要執(zhí)行的指令;指令采用精簡指令集;
我們定義的RISC指令集長度類型兩種,分別為短指令和長指令:
其中指令編碼采用三位二進(jìn)制表示,共定義有8種指令。短指令共8位,高三位為指令編碼,低五位為通用寄存器地址。長指令為16位,每個長指令分兩次取,每次取8位,首先取高8位,格式和短指令相通,也是高3位為指令編碼,低5位為通用寄存器地址;第二次取低8位,表示ROM或者RAM地址,取決于指令編碼。
所謂的指令集,聽著很高級,其實(shí)就這么簡單,就是認(rèn)為規(guī)定的一些協(xié)議而已;
因此有指令集如下表所示,為了方便理解指令的縮寫含義,表中用英文進(jìn)行了描述并將縮寫的由來使用加粗來表示:
//rom存儲的是指令集
//短指令(8bit): 高三位為指令編碼,低五位為通用寄存器地址
//3'b000 -->NOP 空操作
//3'b001 -->LDO Loads the contents of the ROM address into the REG address
//3'b010 -->LDA Loads the contents of the RAM address into the REG address
//3'b011 -->STO Store intermediate results into RAM address
//3'b100 -->PRE Prefetch Data from REG address
//3'b101 -->ADD Adds the contents of the REG address or integer to the accumulator
//3'b110 -->LDM Load Multiple
//3'b111 -->HLT 停機(jī)指令
ROM頂層接口如下:
module cpu_rom #(
parameter ADD_NUMBER_0 = 37, //測試的第一個累加數(shù)
parameter ADD_NUMBER_1 = 89, //測試的第二個累加數(shù)
parameter ADD_NUMBER_2 = 53 //測試的第三個累加數(shù)
)
(
input i_rom_read,
input i_rom_ena ,
input [7:0] i_rom_addr,
output [7:0] o_rom_data
);
ROM,只讀指令。接受輸入地址,當(dāng)讀信號和使能信號高電平時輸出對應(yīng)地址存儲的指令,否則輸出保持高阻態(tài)。地址和數(shù)據(jù)都是8位,可尋址以及內(nèi)部存儲的大小為256Bytes。
RAM存儲數(shù)據(jù),可讀可寫;接收8位地址,當(dāng)讀信號和使能信號有效時,輸出對應(yīng)地址存儲的數(shù)據(jù),否則輸出保持高阻態(tài)。當(dāng)寫信號上升沿是觸發(fā),將輸入輸出寫入地址對應(yīng)位置。內(nèi)部存儲以及可循址大小也為256Byters。
RAM頂層接口如下:
module cpu_ram(
input i_ram_ena ,
input i_ram_read ,
input i_ram_write,
input [7:0] i_ram_addr ,
inout [7:0] io_ram_data
);
cpu_PC程序計數(shù)器,有時也叫做指令地址寄存器(Instruction Address Register, IAR),對應(yīng)于Intel X86體系CPU中的指令指針(Instruction pointer)寄存器。其功能是用來存放要執(zhí)行的下一條指令在現(xiàn)行代碼段中的偏移地址。本文中PC由Controller自動修改,使得其中始終存放著下一條將要執(zhí)行指令的地址。因此,PC是用來控制指令序列執(zhí)行流程的寄存器。
cpu_PC頂層接口如下:
module cpu_PC(
input clk ,
input rstn ,
input i_en ,
output reg [7:0] o_pc_addr
);
異步清零。時鐘上升沿觸發(fā),高電平使能時程序計數(shù)器計數(shù),指向下一條要執(zhí)行指令的地址。指令存儲在ROM中,故每次pc_addr加1。
cpu_cache一級緩存,用于儲存計算的中間結(jié)果。
cpu_cache頂層接口如下:
module cpu_cache(
input clk ,
input rstn ,
input i_ena ,
input [7:0] i_data,
output reg [7:0] o_data
);
異步清零。時鐘上升沿觸發(fā),高電平使能時輸出當(dāng)前輸入信號。
cpu_addr_mux地址選擇器,接受控制使能信號對輸入的來自程序計數(shù)器和指令寄存器的地址進(jìn)行選擇;
cpu_addr_mux頂層接口如下:
module cpu_addr_mux(
input [7:0] i_ir_ad,
input [7:0] i_pc_ad,
input i_addr_sel ,
output [7:0] o_addr_bus
);
當(dāng)選擇信號為1時,選擇來自寄存器輸入的地址到數(shù)據(jù)總線,否則將程序計數(shù)器中的地址加載到數(shù)據(jù)總線。
cpu_ALU算術(shù)邏輯運(yùn)算單元,根據(jù)指令類型來決定進(jìn)行哪種運(yùn)算,從而將運(yùn)算結(jié)果輸出通用寄存器或者累加器中。
cpu_ALU頂層接口如下:
module cpu_ALU(
input [2:0] i_op ,
input [7:0] i_alu_in ,
input [7:0] i_accum ,
output reg [7:0] o_alu_out
);
cpu_reg_32通用寄存器,ALU輸出結(jié)果,指令寄存器輸出的操作數(shù)都可以存儲到寄存器中的特定的地址。輸出寄存器中存儲的數(shù)據(jù)到數(shù)據(jù)總線。
cpu_reg_32頂層接口如下:
module cpu_reg_32(
input clk ,
input i_write,
input i_read ,
input [7:0] i_data ,
input [7:0] i_addr ,
output [7:0] o_data
);
當(dāng)寫信號有效時,將輸入數(shù)據(jù)(來自ALU的輸出)存儲到寄存器中的特定地址。當(dāng)讀信號有效時,將寄存器中特定位置的數(shù)據(jù)輸出(到數(shù)據(jù)總線)。寄存器大小為32Bytes。
cpu_ins_reg指令寄存器,從數(shù)據(jù)總線上獲取數(shù)據(jù),根據(jù)輸入控制信號,根據(jù)指令類型將特定指令和地址輸出到ALU,通用寄存器和地址選擇器。
cpu_ins_reg頂層接口如下:
module cpu_ins_reg(
input clk ,
input rstn ,
input [1:0] i_fetch,
input [7:0] i_data ,
output [2:0] o_ins ,
output [4:0] o_ad1 ,
output [7:0] o_ad2
);
異步清零。當(dāng)輸入控制信號為01時表示數(shù)據(jù)總線當(dāng)前為指令(形式為指令編碼+寄存器地址,見第三章),將其從ins和ad1輸出,當(dāng)控制信號為10時,表示當(dāng)前數(shù)據(jù)總線上的為數(shù)據(jù)(8位地址數(shù)據(jù),見第三章),將其從ad2輸出到地址選擇器。
cpu_controller控制器是系統(tǒng)的核心,具有以下功能:取指令,指令排隊(duì),讀寫操作數(shù),總線控制等。這里采用(Mealy型)有限狀態(tài)機(jī)(FSM)來實(shí)現(xiàn)控制器,指令存儲在ROM中來執(zhí)行,控制器接受外界時鐘和復(fù)位信號,控制器根據(jù)當(dāng)前狀態(tài)以及輸入進(jìn)行狀態(tài)的轉(zhuǎn)移。
根據(jù)指令的任務(wù),設(shè)計了如上圖所示的狀態(tài)轉(zhuǎn)移圖,從左至右依次為狀態(tài)Sidle,S0~S12。各個狀態(tài)的含義如下:
cpu_controller頂層接口如下:
module cpu_controller(
input clk ,
input rstn , // clock, reset
input [2:0] i_ins , // i_instructions, 3 bits, 8 types
// Enable signals
output reg o_write_r,
output reg o_read_r ,
output reg o_PC_en , //控制地址總線, o_PC_en為高時地址總線+1
output reg o_ac_ena ,
output reg o_ram_ena,
output reg o_rom_ena,
// ROM: where i_instructions are storaged. Read only.
// RAM: where data is storaged, readable and writable.
output reg o_ram_write ,
output reg o_ram_read ,
output reg o_rom_read ,
output reg o_ad_sel ,
output reg [1:0] o_fetch // 01: to o_fetch from RAM/ROM; 10: to o_fetch from REG
);
最后將各個高性模塊例化成一個頂層,頂層代碼如下:
module risc_8bit_cpu #(
parameter ADD_NUMBER_0 = 20, //測試的第一個累加數(shù)
parameter ADD_NUMBER_1 = 40, //測試的第二個累加數(shù)
parameter ADD_NUMBER_2 = 6 //測試的第三個累加數(shù)
)(
input clk ,
input rstn ,
output [7:0] o_cpu_out
);
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; //地址總線
wire [7:0] addr; //數(shù)據(jù)總線
wire [7:0] accum_out, alu_out;
wire [7:0] ir_ad, pc_ad;
wire [4:0] reg_ad;
wire [2:0] ins;
assign o_cpu_out=accum_out;
//RAM存儲數(shù)據(jù),可讀可寫
cpu_ram u_cpu_ram(
.io_ram_data(data ),
.i_ram_addr(addr ),
.i_ram_ena (ram_ena ),
.i_ram_read(ram_read ),
.i_ram_write(ram_write)
);
//ROM用于存儲要執(zhí)行的指令,只讀
cpu_rom #(
.ADD_NUMBER_0 (ADD_NUMBER_0), //測試的第一個累加數(shù)
.ADD_NUMBER_1 (ADD_NUMBER_1), //測試的第二個累加數(shù)
.ADD_NUMBER_2 (ADD_NUMBER_2) //測試的第三個累加數(shù)
)u_cpu_rom(
.o_rom_data(data ),
.i_rom_addr(addr ),
.i_rom_ena (rom_ena ),
.i_rom_read(rom_read)
);
//地址選擇器,接受控制使能信號對輸入的來自程序計數(shù)器和指令寄存器的地址進(jìn)行選擇
//sel-->1 輸出指令寄存器地址
//sel-->0 輸出程序計數(shù)器地址
cpu_addr_mux u_cpu_addr_mux(
.o_addr_bus(addr ),
.i_addr_sel(ad_sel),
.i_ir_ad (ir_ad ),
.i_pc_ad (pc_ad )
);
//PC程序計數(shù)器,用來存放要執(zhí)行的下一條指令在現(xiàn)行代碼段中的偏移地址
//輸出地址總線的值
//PC_en為高時地址總線+1
cpu_PC u_cpu_PC(
.clk (clk ),
.rstn (rstn ),
.i_en (PC_en),
.o_pc_addr(pc_ad)
);
//緩存,用于儲存計算的中間結(jié)果
cpu_cache u_cpu_cache(
.o_data(accum_out),
.i_data(alu_out ),
.i_ena (ac_ena ),
.clk (clk ),
.rstn (rstn )
);
//算術(shù)邏輯運(yùn)算單元,根據(jù)指令類型來決定進(jìn)行哪種運(yùn)算,從而將運(yùn)算結(jié)果輸出通用寄存器或者累加器中
cpu_ALU u_cpu_ALU(
.o_alu_out(alu_out ),
.i_alu_in (data ),
.i_accum (accum_out),
.i_op (ins )
);
//通用寄存器,ALU輸出結(jié)果,指令寄存器輸出的操作數(shù)都可以存儲到寄存器中的特定的地址。輸出寄存器中存儲的數(shù)據(jù)到數(shù)據(jù)總線
cpu_reg_32 u_cpu_reg_32(
.i_data (alu_out ),
.o_data (data ),
.i_write(write_r ),
.i_read (read_r ),
.i_addr ({ins,reg_ad}),
.clk (clk )
);
//指令寄存器,從數(shù)據(jù)總線上獲取數(shù)據(jù),根據(jù)輸入控制信號,根據(jù)指令類型將特定指令和地址輸出到ALU,通用寄存器和地址選擇器
//fetch==2'b01 operation1, to fetch data from RAM/ROM
//fetch==2'b10 operation2, to fetch data from REG
cpu_ins_reg u_cpu_ins_reg(
.i_data (data ),
.i_fetch(fetch ),
.clk (clk ),
.rstn (rstn ),
.o_ins (ins ),
.o_ad1 (reg_ad),
.o_ad2 (ir_ad )
);
//控制器,核心部分
cpu_controller u_cpu_controller(
.i_ins (ins ),
.clk (clk ),
.rstn (rstn ),
.o_write_r (write_r ),
.o_read_r (read_r ),
.o_PC_en (PC_en ),
.o_fetch (fetch ),
.o_ac_ena (ac_ena ),
.o_ram_ena (ram_ena ),
.o_rom_ena (rom_ena ),
.o_ram_write(ram_write),
.o_ram_read (ram_read ),
.o_rom_read (rom_read ),
.o_ad_sel (ad_sel )
);
endmodule
4、vivado仿真
仿真代碼如下:
`timescale 1ps / 1ps
module risc_8bit_cpu_tb ;
parameter T=10;
reg rstn ;
reg clk ;
risc_8bit_cpu #(
.ADD_NUMBER_0 (20),
.ADD_NUMBER_1 (60),
.ADD_NUMBER_2 (9 )
)u_risc_8bit_cpu(
.rstn (rstn),
.clk (clk ),
.o_cpu_out()
);
initial begin
rstn=0;
clk=0;
#100;
rstn=1;
#1000;
$stop;
end
always #(T/2) clk=~clk;
endmodule
仿真結(jié)果如下:
在testbench中我們填入的三個數(shù)如下:
累加和等于89;仿真波形紅圈的計算結(jié)果也是89;功能正常;
下面改變輸入如下:
此時累加和等于88,再仿真結(jié)果如下:
仿真波形紅圈的計算結(jié)果也是88;功能正常;
5、vivado工程
開發(fā)板:Xilinx Artix7開發(fā)板;
開發(fā)環(huán)境:vivado2019.1;
輸出:led燈;
系統(tǒng)上電后cup核自動運(yùn)行,并輸出累加和,若輸出的累加和與輸入的一致則led等閃爍,否則led常滅;
工程頂層代碼如下:
module top(
input clk_in1_p,
input clk_in1_n,
output led
);
parameter ADD_NUMBER_0 = 20; //測試的第一個累加數(shù)
parameter ADD_NUMBER_1 = 60; //測試的第二個累加數(shù)
parameter ADD_NUMBER_2 = 8; //測試的第三個累加數(shù)
parameter CPU_SUM=ADD_NUMBER_0+ADD_NUMBER_1+ADD_NUMBER_2;
parameter SYS_TIME=100;
parameter SYS_TIPS=1000/SYS_TIME;
parameter LED_CYC_80MS=80000000/SYS_TIPS;
wire [7:0] o_cpu_out;
wire led_run;
reg [31:0] cnt;
assign led_run=(o_cpu_out==CPU_SUM)? 1'b1: 1'b0;
assign led= (cnt<LED_CYC_80MS/2)? 1'b0: 1'b1;
always @(posedge clk_100m) begin
if(~rstn) cnt<='d0;
else if(led_run) begin
if(cnt<LED_CYC_80MS) cnt<=cnt+'d1;
else cnt<='d0;
end
else cnt<='d0;
end
clk_wiz_0 u_clk_wiz_0
(
.clk_100m(clk_100m), // output clk_100m
.locked(rstn), // output locked
.clk_in1_p(clk_in1_p), // input clk_in1_p
.clk_in1_n(clk_in1_n)
);
risc_8bit_cpu #(
.ADD_NUMBER_0(ADD_NUMBER_0), //測試的第一個累加數(shù)
.ADD_NUMBER_1(ADD_NUMBER_1), //測試的第二個累加數(shù)
.ADD_NUMBER_2(ADD_NUMBER_2) //測試的第三個累加數(shù)
)helai_risc_8bit_cpu(
.clk (clk_100m ),
.rstn (rstn ),
.o_cpu_out(o_cpu_out)
);
endmodule
代碼架構(gòu)如下:
6、上板調(diào)試驗(yàn)證
上板輸出演示視頻如下:
上板輸出演示視頻文章來源:http://www.zghlxwxcb.cn/news/detail-502135.html
7、福利:工程源碼獲取
福利:工程代碼的獲取
代碼太大,無法郵箱發(fā)送,以某度網(wǎng)盤鏈接方式發(fā)送,
資料如下:獲取方式:文章末尾的V名片。
網(wǎng)盤資料如下:
也可直接下載工程源碼,
點(diǎn)擊下載工程源碼文章來源地址http://www.zghlxwxcb.cn/news/detail-502135.html
到了這里,關(guān)于FPGA純verilog代碼實(shí)現(xiàn)8位精簡指令集CPU,一學(xué)期的微機(jī)原理不如看懂這套代碼,提供工程源碼和技術(shù)支持的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!