hiệu ựiều khiển ALU tới op[3..0]. ALU sẽ dựa trên tắn hiệu ựiều khiển này ựể thực hiện các tắnh toán tương ứng và ựưa kết quả ra out[7..0], c, dc, z.
điều khiển ALU: Như ựã mô tả ở trên, ta cần phải ựưa vào các tắn hiệu ựể ựiều khiển ALU, 4 bắt op[3..0] ựược ựưa vào dựa vào Opcode của lệnh ựể ựiều khiển ALU, cụ thể như bảng 5-1:
Như vậy với 4 bit Opcode ta có thể ựịnh nghĩa ựược 16 hoạt ựộng cho bộ ALU. Toàn bộ tập lênh của CPU sẽ ựược xây dựng dựa trên 16 hoạt ựộng cơ bản của bộ ALU này. Trong quá trình thực hiện một lệnh, tới chu kì xung nhịp thứ hai, bộ ựiều khiển sẽ xác ựịnh hoạt ựộng của ALU tương ứng với lệnh ựó và ra lệnh ựiều khiển cho ALU.
5.2.2 Thiết kế bộ nhớ chương trình
Bộ nhớ chương trình là nơi lưu trữ mã chương trình, thông thường ựối với một hệ vi ựiều khiển, bộ nhớ chương trình ựược lưu trữ trong bộ nhớ EEPROM hoặc bộ nhớ Flash. Trong thiết kế hệ vi xử lý này, tác giả thiết kế một bộ nhớ RAM trên FPGA ựể lưu trữ chương trình. Lý do sử dụng bộ nhớ RAM thay vì bộ nhớ EEPROM ựể lưu chương trình là giải pháp này ựơn giản hơn khi nạp chương trình, do mục ựắch chắnh của luận văn là thiết kế lõi của vi xử lý.
để việc thiết kế bộ nhớ chương trình ựược ựơn giản, tác giả sử dụng IP core của Altera ựể làm bộ nhớ chương trình. Các tham số chắnh của module bộ nhớ chương trình như sau:
- bus dữ liệu 12 bits (data[11..0], q[11..0]) do tập lệnh của vi xử lý có ựộ dài 12 bits
- bus ựịa chỉ 11 bits (address[10..0]) cho phép ựánh ựịa chỉ bộ nhớ chương trình có ựộ dài 2048 ô nhớ. Như vậy sẽ cho phép chứa chương trình dài tới 2048 lệnh.
- Wren = 0, do bộ nhớ chương trình là bộ nhớ chỉ ựọc, vì vậy không cần thiết phải ghi dữ liệu vào bộ nhớ chương trình.
spram inst address[10..0] address[10..0] clock clock data[11..0] data[11..0] wren wren q[11..0] q[11..0]
Hình 5 - 4: Sơ ựồ khối của bộ nhớ chương trình Bộ nhớ chương trình hoạt ựộng như sau:
- Ban ựầu sử dụng trình dịch FPGA của Altera ựể nạp dữ liệu ban ựầu, chắnh là chương trình cần nạp vào bộ nhớ chương trình. Dữ liệu nạp ban ựầu là dạng Hexa, là kết quả của trình dịch từ chương trình nguồn sang mã máy, thực tế thao tác này ựòi hỏi cần phát triển thêm một số công cụ, sẽ ựược tác giả trình bày trong phần thực nghiệm.
- Sau khi nạp chương trình dịch từ bộ công cụ của Altera xuống kit FPGA, mã chương trình sẽ ựược lưu trữ trong bộ nhớ RAM.
- Bộ ựếm chương trình PC (Program Counter), sẽ ựược ựưa vào ựường ựịa chỉ của module bộ nhớ chương trình, như vậy ở xung nhịp tiếp theo, lện có ựịa chỉ tương ứng sẽ ựược ựọc ra và ựưa tới ựường ựịa chỉ q.
5.2.3 Bộ chia tần số
để có thể ựiều khiển ựược tần số ựầu vào từ bộ xung nhịp 50MHz của hệ thống, trong thiết kế này sử dụng một bộ chia tần ựể ựiều khiển xung nhịp ựưa vào làm xung nhịp cho vi xử lý
- inclk: Lối vào xung nhịp 50MHz
- outclk: Lối ra của bộ chia tần ựược ựưa tới làm ựầu vào xung nhịp cho vi xử lý.
- Rst: Lối vào dùng ựể reset bộ chia tần.
f req_div inst inclk inclk rst rst outclk outclk
Hình 5 - 5: Sơ ựồ khối của bộ chia tần số 5.2.4 Register File register_f ile inst clk clk rst rst rf _rd_bnk[1..0] rf _rd_bnk[1..0] rf _rd_addr[4..0] rf _rd_addr[4..0] rf _we rf _we rf _wr_bnk[1..0] rf _wr_bnk[1..0] rf _wr_addr[4..0] rf _wr_addr[4..0] rf _wr_data[7..0] rf _wr_data[7..0] rf _rd_data[7..0] rf _rd_data[7..0]
Hình 5 - 6: Sơ ựồ khối của bộ nhớ chương trình
Thông thường khi thiết kế một hệ thống máy tắnh, một bộ nhớ RAM ựược dùng ựể lưu trữ chương trình cũng như lưu trữ dữ liệu. Do ựặc thù chung của hệ vi ựiều khiển là dùng cho những ứng dụng ựiều khiển nhỏ và vừa phải, một bộ nhớ
SRAM vừa phải ựược sử dụng, ở ựây ựược gọi là register file. Register file ựược mô tả như sau:
- clk: đầu vào xung nhịp clock ựồng bộ với xung nhịp hệ thống. - rf_we: đầu vào cho phép ghi dữ liệu vào register file.
- rst: 1 bit lối vào reset register file.
- rf_rd_bnk[1..0]: đầu vào lựa chọn bank trong register file ựể ựọc, Sở dĩ register file ựược chia ra làm nhiều bank khác nhau giống như cách tổ chức vùng nhớ RAM của một hệ máy tắnh lớn cho phép việc quản lý, phân chia bộ nhớ ựược dễ dàng cũng như dễ dàng trong việc thiết kế truy xuất dữ liệu.
- rf_rd_addr[4..0]: đầu vào 5 bits ựịa chỉ cho phép ựánh ựịa chỉ tối ựa 32 thanh ghi trong một bank của register file.
- Rf_wr_bnk[1..0]: 2 bắt dùng ựể lựa chọn bank cần ghi trong register file.
- Rf_wr_addr[4..0]: 5 bắt ựịa chỉ dung ựể chọn thanh ghi trong một bank cần ghi vào.
- Rf_wr_data[7..0]: 8 bit dữ liệu vào ựể ghi dữ liệu lên các thanh ghi ựịa chỉ.
Việc sử dụng register file trong các hệ vi ựiều khiển nhỏ thường rất hay ựược sử dụng vì với một hệ vi ựiều khiển nhỏ, thường không ựòi hỏi một bộ nhớ RAM lớn do ựó dùng register file ựể làm bộ nhớ RAM rất có lợi do:
- Thiết kế bộ nhớ sẽ ựơn giản ựi rất nhiều do không cần phải ựiều khiển bộ nhớ RAM ngoài.
- Việc truy cập bộ nhớ cũng ựơn giản hơn rất nhiều do toàn bộ dữ liệu ựược ựặt trong các thanh ghi do ựó tốc ựộ truy cập sẽ rất nhanh, thường việc truy cập có thể ựược hoàn thành trong vòng một chu kì xung nhịp ựồng hồ. Như vậy sẽ giảm ựược việc ựiều khiển trễ khi truy cập bộ nhớ ngoài cũng như không cần phải thiết kế bộ nhớ Cache như những thiết kế thông thường.
Như phần trên ựã nói, ta có thể hiểu register file chắnh là bộ nhớ RAM của vi ựiều khiển, phần này sẽ ựi sâu mô tả các thành phần bên trong của register file ựể có thể thực hiện lưu trữ dữ liệu.
Vùng nhớ: Module quan trọng nhất của register file chắnh là Module nhớ, chắnh là nơi chứa các dữ liệu khi thực hiện chương trình. Như phần trên ựã trình bày, ựộ lớn của module nhớ là 128 bytes (7 bits ựịa chỉ) ựồng nghĩa với việc bộ nhớ RAM của vi ựiều khiển có kắch thước là 128 bytes.
để tối ưu hóa, thiết kế trong luận văn này tác giả ựã dùng lõi IP Dual Port RAM của Altera ựể làm module nhớ cho register file. Sở dĩ ở ựây phải dùng Dual Port là do trong khi bộ nhớ chương trình chỉ dùng single Port là do Register file là bộ nhớ có thể ựọc và ghi ựược giống như bộ nhớ RAM của hệ máy tắnh.
oe rce rclk rrst wce wclk we wrst raddr[6..0] waddr[6..0] di[7..0] dout[7..0] 1 1 1 generic_dpram:rf0
Hình 5 - 7: IP core ựược dùng ựể làm bộ nhớ chương trình địa chỉ ựọc và ghi dữ liệu lên module nhớ ựược thiết kế như sau: địa chỉ ựọc
assign rd_addr[6] = ~rf_rd_addr[4];
assign rd_addr[5:3] = rf_rd_addr[4] ? {rf_rd_bnk, rf_rd_addr[3]} : 3'h0; assign rd_addr[2:0] = rf_rd_addr[2:0];
địa chỉ ghi
assign wr_addr[6] = ~rf_wr_addr[4];
assign wr_addr[5:3] = rf_wr_addr[4] ? {rf_wr_bnk, rf_wr_addr[3]} : 3'h0; assign wr_addr[2:0] = rf_wr_addr[2:0];
6 7 H ìn h 5 - 8 : S ơ ự ồ b ên t ro n g c ủ a r eg is te r fi le
5.2.5 Thiết kế vùng nhớ Stack
Một hệ thống vi xử lý bao giờ cũng bao gồm các lệnh rẽ nhánh, khi thực hiện những lệnh rẽ nhánh ựó, ta cần một vùng nhớ stack ( ựược cứng hóa) ựể lưu trữ ựịa chỉ của lệnh ựược thực hiện ngay sau ựó, có như vậy thi khi trở về từ chương trình con, vi xử lý mới có thể thực hiện ựược lệnh tiếp theo của chương trình chắnh. Bộ nhớ stack là một bộ nhớ FIFO, dữ liệu ựược ựưa vào khi chương trình thực hiện lệnh call và lấy dữ liệu ra (pop) khi chương trình thực hiện lệnh retlw. Bộ nhớ stack ựược thiết kế có sơ ựồ như hình 5 Ờ 9.
sf if o4x11 inst clk clk push push din[10..0] din[10..0] pop pop dout[10..0] dout[10..0] Hình 5 - 9: Bộ nhớ Stack Hoạt ựộng của bộ nhớ stack như sau:
- clk: ựầu vào xung nhịp ựược ựồng bộ hóa với clock của hệ thống. - push: Nếu giải mã lệnh là lệnh call, thì ựến bước ba trong quá
trình thực hiện lệnh CALL, bước thực thi lệnh, ựầu vào này ựược ựặt lên 1 ựể ghi ựịa chỉ lệnh ngay sau lệnh CALL trong chương trình chắnh vào stack (push).
- Din[10..0] dữ liệu ựưa vào khi thực hiện push.
- Pop: Nếu giải mã lệnh là lệnh RETLW, thì sau khi giải mã lệnh ựặt ựầu vào này lên 1 ựể lấy ựịa chỉ lệnh tương ứng trong stack ra. - Dout[10..0]: địa chỉ lệnh ựọc ựược khi thực hiện pop.
5.3 Thit k b> mô ph?ng h thEng t= ự>ng.
Việc mô phỏng toàn bộ hệ thống rất phức tạp, ựòi hỏi rất nhiều trường hợp mô phỏng khác nhau (cần rất nhiều testcase) ựể có thể bao trùm toàn bộ các trường hợp có thể xảy ra trên hệ thống. Như vậy ựể ựảm bảo rằng một hệ thống sau khi
thiết kế không có lỗi ựòi hỏi quy trình kiểm tra, chạy thử hệ thống rất phức tạp và thường tốn rất nhiều thời gian. Việc thiết kế một hệ thống kiểm tra chương trình tự ựộng là rất cần thiết trong thiết kế một hế thống lớn. Trong luận văn này tác giả xây dựng hệ thống mô phỏng hệ thống như sau:
- Viết chương trình nguồn bằng ngôn ngữ Assembler dựa trên tập lệnh của vi xử lý.
- Dùng trình dịch ựể dịch chương trình sang dạng Hexa, ở bước này thường ta phải phát triển một trình dịch ựể dịch từ mã Assembler sang mã máy (dạng Hexa) nhưng như ựã ựược trình bày ở các phần trên, nếu làm như vậy sẽ rất mất thời gian vào việc thiết kế một chương trình dịch (compiler) trong khi mục ựắch chắnh của tác giả là tập trung vào thiết kế một nhân vi xử lý. Do vậy tác giả ựã sử dụng trình dịch của Microchip ựể dịch chương trình từ mã assembler sang mã máy.
- Khi sử dụng trình dịch của Miccrochip ựể dịch chương trình sang mã máy, mã chương trình sẽ ựược lưu trữ trong một file (thuật ngữ thường dung là Hex file) theo như ựịnh dạng của Intel. Do ựịnh dạng của file chứa mã chương trình của Mento Graphic khác với ựịnh dạng của Intel do ựó tác giả ựã viết một chương trình ựể tự ựộng chuyển ựổi ựịnh dạng file hex của Intel sang ựịnh dạng của Mento Graphic.
- Tạo một thể hiện của hệ vi xử lý trên môi trưởng mô phỏng.
- Khởi tạo một vùng nhớ RAM ựể lưu trữ chương trình cần chạy trên chip FPGA, ựoạn mã tạo vùng nhớ ựể lưu trữ chương trình như sau:
`timescale 1ns / 10ps
module prog_mem ( clk, address, we, din, dout );
input clk;
input [10:0] address; //11 bits address for
instruction
input we;
input [11:0] din; //input
output [11:0] dout;
parameter depth = 2048; reg [10:0] addr_r;
reg [11:0] mem[0:depth-1]; //Declare program memory
always @(posedge clk) addr_r <= address;
assign dout = mem[addr_r]; always @(posedge clk)
if (we) mem[address] <= din; endmodule
để có thể mô phỏng ựược hệ vi xử lý, ta cần viết một testbench thực hiện những nhiệm vụ sau:
- Tạo xung nhịp clock ựể ựưa vào bộ vi xử lý:
always #10 clk = ~clk; always #20 tcki = ~tcki;
- Tạo một thể hiện của CPU ựể thực hiện test, công việc này trong thuật ngữ của thiết kế một hệ thống LSI gọi là tạo DUT (Device Under Test).
// Instantiate one CPU to be tested. mrisc u0( .clk (clk), .rst_in (reset), .inst_addr (inst_addr), .inst_data (inst_data), .portain (portain), .portbin (portbin), .portcin (portcin), .portaout (portaout), .portbout (portbout), .portcout (portcout), .trisa (trisa), .trisb (trisb), .trisc (trisc), .tcki (tcki), .wdt_en (1'b1) );
- Tạo một thể hiện bộ nhớ chương trình:
// Instantiate the Program RAM. prog_mem u1 (
.clk (clk), .address (inst_addr),
.we (1'b0), // This testbench
doesn't allow writing to PRAM
.din (12'b000000000000), // This
testbench doesn't allow writing to PRAM
.dout (inst_data)
);
để có thể thực hiện mô phỏng ựược hệ vi xử lý, ta cần phải load chương trình xuống vùng nhớ mô phỏng này giống như cách download chương trình xuống vùng nhớ EEPROM cho vi ựiều khiển. Ngôn ngữ verilog cho phép ta ựọc một file nhị phân và gán các giá trị trong file ựó tới bộ nhớ cần ghi dữ liệu lên. Vắ dụ
Lệnh:
$readmemh ("./scode/testport.rom", u0.mem)
Cho phép ta ựọc lệnh trong hexa file testport.rom và ghi vào vùng nhớ mem của bộ nhớ chương trình (prog_mem).
đến ựây ta có thể thực hiện mô phỏng ựể kiểm tra chức năng của rất nhiều khối của vi xử lý.
- Chương trình mô phỏng Model Sim cho phép ta chạy chương trình mô phỏng trong một khoảng thời gian nhất ựịnh, như vậy cho phép ta chạy từng bước chương trình và kiểm tra trạng thái các khối chức năng của Datapath cũng như những tắn hiệu của khối ựiều khiển. đến ựây ta có quy trình ựể mô phỏng kiểm tra chức năng của từng module trong bộ vi xử lý như sau:
o Xác ựịnh module trong vi xử lý cần kiểm tra
o Viết một chương trình Assembler nhỏ mà chắc chắn rằng khi chạy chương trình ựó sẽ ảnh hưởng (thay ựổi) trạng thái của module ựó.
o Dịch ựoạn mã Assembler ựó sang mã máy và chuyển ựịnh dạng của Hex file sang ựịnh dạng của Model Sim.
o Tải chương trình Testbench vừa viết vào Model Sim
o Thực hiện chạy từng bước chương trình và kiểm tra trạng thái của module mong muốn.
- Chương trình mô phỏng Model Sim cùng với Teschbench vừa thiết kế cũng cho phép ta chạy một chương trình lớn hơn và in kết quả ra một log file ựể kiểm tra kết quả, phương pháp mô phỏng này sẽ cho phép ta mô phỏng ựược nhiều chức năng của vi xử lý hơn và quá trình mô phỏng, kiểm tra kết quả sẽ nhanh hơn. Quy trình này như sau:
o Viết một chương trình nhỏ thực hiện một chức năng xác ựịnh bằng ngôn ngữ Assembler, dịch chương trình và chuyển ựịnh dạng của Hex file sang ựịnh dạng của Model Sim.
o Tải chương trình vừa viết vào Model Sim
o Thực hiện chạy chương trình và kiểm tra kết quả ựầu ra, kết quả ựầu ra có thể là kết quả hiển thị trên các ựến LED hoặc hiển thị trên LED 7 ựoạn, màn hình LCD. Trong phần cuối của luận văn này, tác giả trình bày một vắ dụ sử dụng vi xử lý ựể ựiểu khiển, hiển thị lên màn hình LCD.
5.4 Mô ph?ng vi ựiu khi8n th=c hi n m>t ựo+n chF$ng trình ự$n gin, ki8m tra ch*c năng các module trong vi ựiu khi8n.
Phần này sẽ mô phỏng vi ựiều khiển thực hiện chương trình thực hiện một ựoạn chương trình ngắn và kiểm tra chức năng của các mô ựun trong vi ựiều khiển bằng phần mềm ModemSim. Chương trình trong phần này sẽ thực hiện ựoạn chương trình ựơn giản: out ra cổng B của vi ựiều khiển các giá trị 0x00 và 0xff. Chương trình này tuy ựơn giản nhưng giúp ta kiểm tra ựược hoạt ựộng của bộ nhớ chương trình, bộ ựếm chương trình, register file, ALU, lệnh gotoẦ Các lệnh tương ứng và mã nhị phân tương ứng (mã máy) của chương trình như sau:
;This program is simple test and will out put 0xff and 0x00 to portB list p=16c57 #include p16c5x.inc main: clrw ;@000 040 tris PORTB ;@006 006 MOVLW 0xff ; CFF
movwf PORTB ;@003 026 movlw 0x00 ; movwf PORTB ;026 goto main nop END
Mã assembler địa chỉ lệnh Mã nhị phân Mô tả lệnh
clrw 000 040 Xóa thanh ghi W
tris PORTB 001 006 Xóa thanh ghi trisB
MOVLW 0xff 002 CFF Nạp FF vào W
Movwf PORTB 003 026 PORTB = W
movlw 0x00 004 C00 Nạp 0x00 vào W
movwf PORTB 005 026 PORTB = W
goto main 006 A00 Nhảy tới ựịa chỉ 0x000 (main)
nop 007 000 NOP
Bảng 5 - 2: Mã nguồn chương trình ựược mô phỏng.