計算機構成 第8回 POCOの構造とVerilog記述 テキスト第5章-第6章 情報工学科 天野英晴
今日の目標はPOCOのVerilog記述の理解 フルスクラッチシリーズは後に機会を設けるので今日やらなくて良い
POCOの全体構成 Y S ext ext11 A B + + ext ext0 PC … … 命令メモリ データメモリ ‘0’ 00 01 2:0 00 Y THB 01 S 00 01 10 pcsel ext ext11 ADD 10 A B alu_bsel + comsel zero 7:0 10:0 00 01 10 ‘1’ zero + ext ext0 7:0 rf_a rf_b 1 pcjr aadr 10:8 badr PC cadr 1 ‘7’ rf_c rwe rf_csel casel 00 01 10 7:5 idatain ddatain iaddr … … ddataout daddr 命令メモリ データメモリ we
もう決まっちゃってる所 Y S ext ext11 A B + + ext ext0 PC assign we = st_op; … … ‘0’ 2:0 00 Y THB 01 S 00 01 10 pcsel ext ext11 ADD 10 rfile rfile_1(.clk(clk), .a(rf_a), .aadr(rd), .b(rf_b), .badr(rs), .c(rf_c), .cadr(cadr), .we(rwe)); A B alu_bsel + comsel zero 7:0 10:0 00 01 10 ‘1’ zero + ext ext0 7:0 rf_a rf_b 1 pcjr assign {opcode,rd,rs,func} = idatain; assign imm=idatain[7:0]; aadr 10:8 badr PC cadr 1 ‘7’ rf_c rwe rf_csel casel 00 01 10 assign ddataout = rf_a; assign daddr = rf_b; 7:5 idatain ddatain assign we = st_op; iaddr … … assign iaddr = pc; ddataout daddr 命令メモリ データメモリ we
データパスの記述 Y S ext ext11 A B + + ext ext0 assign com = (addi_op|laddiu_op) ? `ALU_ADD: (ldi_op|ldiu_op)?`ALU_LD: func[2:0]; ‘0’ 2:0 00 Y THB 01 S 00 01 10 pcsel ext ext11 ADD 10 A B alu_bsel + comsel zero 7:0 10:0 00 01 10 ‘1’ zero + ext ext0 assign alu_b = (addi_op|addiu_op)? { {8{imm[7]}},imm} : (addiu_opldiu_op)? {8’b0,imm}: rf_b; 7:0 rf_a rf_b 1 pcjr aadr 10:8 badr PC cadr 1 ‘7’ rf_c rwe rf_csel casel 00 01 10 7:5 assign cadr = jal_op? 3’b111 : rd; idatain ddatain assign rwe = ld_op | alu_op | ldi_op | ldiu_op | addi_op | addiu_op..|jal_op; assign rf_c = ld_op ? ddatin: jal_op ? pc+1: alu_y; iaddr … … ddataout daddr 命令メモリ データメモリ we
pc周辺 Y S ext ext11 A B + + ext ext0 PC … … 命令メモリ データメモリ ‘0’ 00 01 10 2:0 00 Y THB 01 S 00 01 10 pcsel ext ext11 ADD 10 always @(posedge clk or negedge rst_n) begin if(!rst_n) pc <= 0; else if ((bez_op & rf_a == 16’b0) | (bnz_op & rf_a != 16’b0) | (bpl_op & ~rf_a[15]) | (bmi_op & rf_a[15])) pc<=pc+{{8imm[7]}},imm}+1; else if (jmp_op| jal_op) pc <= pc +{{5{idatain[10]}},idatain[10:0]}}+1; else if(jr_op) pc <= rf_a; else pc<=pc+1; end A B alu_bsel + comsel zero 7:0 10:0 00 01 10 ‘1’ zero + ext ext0 7:0 rf_a rf_b 1 pcjr aadr 10:8 badr PC cadr 1 ‘7’ rf_c rwe rf_csel casel 00 01 10 7:5 idatain ddatain iaddr … … ddataout daddr 命令メモリ データメモリ we
なぜPC周辺のみ図とマッチしないか? always文の中はif文やcase文が使えて便利なのでなるべく中で判断までやってしまう 他の部分でも、マルチプレクサの制御信号などは明示的に書いていない →今回の書き方は、テキストの図に忠実に、しかし以下を例外とした マルチプレクサの制御信号は明示的に書かない → 書いても面倒なだけなので、、 レジスタに対する書き込み制御は always文内に書く → 無理に外に出すより読みやすいので、、
pocoz.v:図に完全に忠実に書いた 記述(decode.vの後半) assign we = st_op; assign rwe = ld_op | alu_op | ldi_op | ldiu_op | addi_op | addiu_op | ldhi_op | jal_op ; assign rf_csel = ld_op ? 2'b01 : jal_op ? 2'b10: 2'b00 ; assign alu_bsel = (addi_op | ldi_op) ? 2'b01 : (addiu_op | ldiu_op) ? 2'b10 : ldhi_op? 2'b11 : 2'b00; assign comsel = (addi_op | addiu_op) ? 2'b01 : (ldi_op | ldiu_op | ldhi_op) ? 2'b10 : 2'b00; assign casel = jal_op; assign pcjr = jr_op; assign pcsel = (bez_op & zero | bnz_op & ~zero | bpl_op & ~mi | bmi_op & mi) ? 2'b01 : jmp_op | jal_op ? 2'b10 : 2'b00; endmodule
pocoz.v:図に完全に忠実に書いた 記述 モジュール decode.v include "def.h" module decode( input [`OPCODE_W-1:0] opcode, input [`OPCODE_W-1:0] func, input zero, mi, output [1:0] alu_bsel, comsel, rf_csel, pcsel, output we, rwe, casel, pcjr); wire st_op, bez_op, bnz_op, bmi_op, bpl_op, addi_op, ld_op, alu_op; wire ldi_op, ldiu_op, ldhi_op, addiu_op, jmp_op, jal_op, jr_op, jalr_op; assign st_op = (opcode == `OP_REG) & (func == `F_ST); assign ld_op = (opcode == `OP_REG) & (func == `F_LD); assign jr_op = (opcode == `OP_REG) & (func == `F_JR); assign jalr_op = (opcode == `OP_REG) & (func == `F_JALR); assign alu_op = (opcode == `OP_REG) & (func[4:3] == 2'b00); assign ldi_op = (opcode == `OP_LDI); assign ldiu_op = (opcode == `OP_LDIU); assign addi_op = (opcode == `OP_ADDI); assign addiu_op = (opcode == `OP_ADDIU); assign ldhi_op = (opcode == `OP_LDHI); assign bez_op = (opcode == `OP_BEZ); assign bnz_op = (opcode == `OP_BNZ); assign bpl_op = (opcode == `OP_BPL); assign bmi_op = (opcode == `OP_BMI); assign jmp_op = (opcode == `OP_JMP); assign jal_op = (opcode == `OP_JAL);
pocoz.vの本体 マルチプレクサの記述が 完全に図とマッチしている assign {opcode, rd, rs, func} = idatain; assign imm = idatain[`IMM_W-1:0]; decode decode_1 (.opcode(opcode), .func(func), .zero(zero), .mi(mi), .alu_bsel(alu_bsel), .comsel(comsel), .rf_csel(rf_csel), .pcsel(pcsel), .we(we), .rwe(rwe), .casel(casel), .pcjr(pcjr)); assign alu_b = alu_bsel==2'b01 ? {{8{imm[7]}},imm} : alu_bsel == 2'b10 ? {8'b0,imm} : alu_bsel == 2'b11 ? {imm, 8'b0} : rf_b; assign com = comsel==2'b01 ? `ALU_ADD: comsel==2'b10 ? `ALU_THB: func[`SEL_W-1:0]; assign rf_c = rf_csel==2'b01 ? ddatain : rf_csel==2'b10 ? pc+1 : alu_y; assign cadr = casel ? 3'b111 : rd; alu alu_1(.a(rf_a), .b(alu_b), .s(com), .y(alu_y)); rfile rfile_1(.clk(clk), .a(rf_a), .aadr(rd), .b(rf_b), .badr(rs), .c(rf_c), .cadr(cadr), .we(rwe)); マルチプレクサの記述が 完全に図とマッチしている
pc周辺も分離して書くことは可能だが見やすいとはいえない pocoz.vの本体 assign zero = rf_a == 16'b0; assign mi = rf_a[15]; assign pcadd = pcsel == 2'b01 ? {{8{imm[7]}},imm} : pcsel == 2'b10 ? {{5{idatain[10]}},idatain[10:0]}: 0; assign pcnext = pcjr ? rf_a: pc+pcadd+1; always @(posedge clk or negedge rst_n) begin if(!rst_n) pc <= 0; else pc <= pcnext; end pc周辺も分離して書くことは可能だが見やすいとはいえない
今回の記述の特徴 出力信号依存の書き方 全ての出力を分離して記述している モジュールA モジュールA・B モジュールB まとめて書ければ可読性が向上する (かもしれない) AとBに共通性があっても 分離して書かなければならない
function文 うまく使うと非常に分かりやすく書ける しかし、落とし穴がある この授業では使わないが興味があればどうぞ 変数スコープが曖昧 代入文は前後関係が生じる 出力をまとめる必要がある この授業では使わないが興味があればどうぞ
pocof.vを参照 内部変数をregで定義 デフォルトの値を最初に書いてしまう その後は後に書いたものが前のを打ち消す function [7:0] decode( input [`OPCODE_W-1:0] op, input [`OPCODE_W-1:0] fu); reg [1:0] alu_bsel, comsel, rf_csel; reg rwe, casel; begin alu_bsel = 2'b00; comsel = 2'b00; rf_csel = 2'b00; rwe = 1'b0; casel = 1'b0; case (op) `OP_REG: if(fu==`F_LD) begin rwe = 1'b1; rf_csel = 2'b01; end else if(fu[4:3] == 2'b00) … 内部変数をregで定義 デフォルトの値を最初に書いてしまう その後は後に書いたものが前のを打ち消す
命令毎に特徴的な出力の動きをまとめて書ける pocof.vを参照 `OP_LDI: begin rwe = 1'b1; comsel = 2'b10; alu_bsel = 2'b01; end `OP_LDIU: begin alu_bsel = 2'b10; end `OP_ADDI: begin comsel = 2'b01; `OP_ADDIU: begin `OP_LDHI: begin alu_bsel = 2'b11; end `OP_JAL: begin casel = 1'b1; rf_csel = 2'b10; end endcase decode = {alu_bsel, comsel, rf_csel, rwe, casel}; end endfunction 命令毎に特徴的な出力の動きをまとめて書ける 最後に全体の信号をくっつけて出力
命令毎に特徴的な出力の動きをまとめて書ける pocof.vを参照 `OP_LDI: begin rwe = 1'b1; comsel = 2'b10; alu_bsel = 2'b01; end `OP_LDIU: begin alu_bsel = 2'b10; end `OP_ADDI: begin comsel = 2'b01; `OP_ADDIU: begin `OP_LDHI: begin alu_bsel = 2'b11; end `OP_JAL: begin casel = 1'b1; rf_csel = 2'b10; end endcase decode = {alu_bsel, comsel, rf_csel, rwe, casel}; end endfunction 命令毎に特徴的な出力の動きをまとめて書ける 最後に全体の信号をくっつけて出力
pocof.vの本体 assign {alu_bsel, comsel, rf_csel, rwe, casel} = decode(opcode,func); always文をレジスタ以外にも使う書き方でも 同様の多入力、多出力的な書き方が可能 一時、論理合成の効率が良かったためFPGAベンダーが推奨したため、広がった しかし入門者は使わないほうが良い 外部の信号線に 関係付けを行う
ディスプレースメント付きレジスタ 間接指定 LDD rd,n(ra): rd ← (n+ra) 01110 ddd aaa nnnnn STD rd,n(ra): (n+ra) ← rd 01111 ddd aaa nnnnn レジスタ間接指定の一種 レジスタとn(ディスプレースメント)を加えた値が実効アドレスになる ループアンローリングができるので、RISCではこの方式が一般的 POCOの今までのタイプと異なる
演習 LDDとSTDを実装せよ。 eximem.datにテストプログラムがあるのでこれをimem.datにコピーして用いよ shapaは対応していないので注意! def.hには加えてある 変更箇所が多く構造とVerilog記述の関係を良く理解しないとできない gtkwaveを使って論理的にバグを追い詰めること