Link命令をどう実装するか(SCP vs. Pipelined)
jump and link
「31番」レジスタ($ra
= リターンアドレス)に「PC+4」を格納しつつ、即値で与えられたアドレスにPCを切り替える。という命令
SCPの場合
http://meseec.ce.rit.edu/eecc550-winter2005/550-chapter5-exercises.pdf
上記のように、 RegDst
と MemToReg
のselectorを拡張する実装が多い。
Pipelinedの場合
/*** EX Rs Forwarding Mux ***/ Mux4 #(.WIDTH(32)) EXRsFwd_Mux ( .sel (EX_RsFwdSel), .in0 (EX_ReadData1_PR), .in1 (M_ALUResult), .in2 (WB_WriteData), .in3 (EX_RestartPC), .out (EX_ReadData1_Fwd) ); /*** EX Rt Forwarding / Link Mux ***/ Mux4 #(.WIDTH(32)) EXRtFwdLnk_Mux ( .sel (EX_RtFwdSel), .in0 (EX_ReadData2_PR), .in1 (M_ALUResult), .in2 (WB_WriteData), .in3 (32'h00000008), .out (EX_ReadData2_Fwd) ); /*** EX ALU Immediate Mux ***/ Mux2 #(.WIDTH(32)) EXALUImm_Mux ( .sel (EX_ALUSrcImm), .in0 (EX_ReadData2_Fwd), .in1 (EX_SignExtImm), .out (EX_ReadData2_Imm) ); /*** EX RtRd / Link Mux ***/ Mux4 #(.WIDTH(5)) EXRtRdLnk_Mux ( .sel (EX_LinkRegDst), .in0 (EX_Rt), .in1 (EX_Rd), .in2 (5'b11111), .in3 (5'bxxxxx), .out (EX_RtRd) );
https://github.com/grantae/mips32r1_core/blob/master/mips32r1/Processor.v#L497-L533
この例では、 RegDst
の方(31番のレジスタを指定する、5bitアドレスの方)は同じように実装している。が、「PC+4」はフォワーディングの方を絡めて実装している。Link命令の際は、ハザード制御ユニットを経由して
という感じで実装しているようだ。こちらがスタンダード?
FPGAにbootcodeやkernelをどう転送するか(SDRAMやUFMとやりとりするには)
いま使用しているDE−10 Liteは残念ながらSRAMを装備しておらず
[方法1]Qsys & System Consoleを使う
Qsys > SDRAM Controller
- bit幅を16にする
- 「wire」を右クリックして「Exported」にする
- メニューの Generate > Show Instantiation Templateの結果をコピーする
Quartusに戻って
- DE10_LITE_Golden_Top.v にペースト
- wireを置き換えていく
参考:
- http://gogofpga.blog.fc2.com/blog-entry-145.html
- ftp://ftp.altera.com/up/pub/Altera_Material/13.0/Tutorials/Verilog/DE2/Using_the_SDRAM.pdf
System Console
あとは適当にデータを読み書きすればよい。
[方法2]DE-10 Lite Control Panelを使う
ここから落とすだけ。
- http://mail.terasic.com.cn/~jmlei/DE10-Lite/DE10_Lite_ControlPanel_V101.zip
- 参考:DE10-Lite Control Panel Install
Questions
Nios 2 を使わずにやりとりできる?(MIPS CPUからSDRAMやUFMをwrite, readできるか)
SDRAM
de10_lite_golden_top.v
のようなテンプレートデザインを使用すると、DRAMピンが露出している。
UFM
- PC --> UFMへのrwは可能っぽいが、CPUから直接は難しい?素直にNiosを使おう。
PCからJTAGで直接書き込みするには?
SDRAM
- System Console > Tcl Console で可能(Avalon MM Bridgeが必要)
UFM
- Qsysで何とかなりそう?
PCからkernel imageを直接SDRAMに転送するのがベストかも
- SDRAMの適当な番地(0x80000 = 8MB)とかにkernelmemfsを転送
- bootcode(カーネル展開コード)をROM(といいつつ実態は回路にする。あるいはUFMにload memory fileを指定してそっちを使っても良さそう)に直接書き込んでおく。リセットしたらここから命令フェッチを始める。
memide.c
を参考に、カーネル本体を物理メモリ --> 物理メモリへ再配置する。
ELFヘッダのパース
- kernelmemfs、こいつをSDRAMにそもそもそのまま配置できるのか?
- 割と余裕でできた。
xv6のfs.imgはルートファイルシステム
kernelmemfsでビルドする場合、出力ファイルにfs.imgがくっついて完成するイメージ。ので特に気にせずELFヘッダに沿ってメモリ展開すれば良さそう。
Soc周辺機器
UART (8250, RS-232規格を満たしていればよい)
説明
実装例
- https://github.com/varmil/uart-verilog
- https://www.nandland.com/vhdl/modules/module-uart-serial-port-rs232.html
PIC (i8259A)
説明
実装例
- https://github.com/jamieiles/80x86/blob/master/fpga/pic/PIC.sv
- https://github.com/alfikpl/ao486/blob/master/rtl/soc/pic/pic.v
Timer (8253/8254)
説明
実装例
※GAIAでは自前のtimerを実装しており、kernelの機能は使ってない。PICに至っては使用すらしていない(CPUに直でインタラプトする)
MIPS Interruptのハードウェア実装(CP0)
CPU受付完了時にassertするIACKピンの実装
We also want to have to ability to service external interrupts. This is useful if a device external to the processor needs attention. To do this, we'll add 2 pins to the processor. The first pin, called IRQ (interrupt request), will be an input that will allow an external device to interrupt the processor. Since we don't want the processor to service any external interrupts before it is finished executing the current instruction, we may have to make the external device wait for several clock cycles. Because of this, we need a way to tell the external device that we've serviced its interrupt. We'll solve this problem by adding the second pin, called IACK (interrupt acknowledge), that will be an output. The following waveform defines the timing for external interrupt requests.
Exceptions and Interrupts for the MIPS architecture
これによってPICのIRR->ISRへの切替が行われるようになる。
CP0のverilog実装
mips32r1_xum/CPZero.v at master · grantae/mips32r1_xum · GitHub
非常に分かりやすい。が、MIPS Release 1では Cause.Ⅳ
Cause.WP
など使用しないフラグもあるので、その辺は自分で作る時は排除しても良さそう。また、このリポジトリではCPUのInt(0)をUART用、Int(4:1)をSwitch用に使用している(PICは使っていない。のでIACK pinも存在しない)
// これがコア側のInt*(4:0)に対応していて、CP0内部でクロック立ち上がり時に、 // Cause_IP[6:2] <= Int[4:0]; でIP部分に代入している //-- Hw Interrupts --// input [4:0] Int, // Five hardware interrupts external to the processor ..... // IE, IP, IMを見て割り込みを起こすかどうか決める // 2行目はEXL, ERLを見ているので、既に例外処理中だったら割り込みを起こさないということか wire Enabled_Interrupt = EXC_NMI | (Status_IE & ((Cause_IP[7:0] & Status_IM[7:0]) != 8'h00)); assign EXC_Int = Enabled_Interrupt & ~Status_EXL & ~Status_ERL & ~ID_IsFlushed; ..... // この実装では割り込みはIDステージで検知する。stall中はReadyしない。 assign ID_Exception_Detect = EXC_Sys | EXC_Bp | EXC_RI | EXC_CpU | EXC_Int; assign ID_Exception_Ready = ~ID_Stall & ID_Exception_Detect & ~ID_Exception_Mask; ..... // ここもクロック立ち上がり時に判定、つまりIPに代入した次のクロックでここへ入ってくる。 // ID stage else if (ID_Exception_Ready) begin Cause_BD <= (Status_EXL) ? Cause_BD : ID_IsBD; Cause_CE <= (COP3) ? 2'b11 : ((COP2) ? 2'b10 : ((COP1) ? 2'b01 : 2'b00)); Cause_ExcCode30 <= Cause_ExcCode_bits; Status_EXL <= 1; EPC <= (Status_EXL) ? EPC : ID_RestartPC; BadVAddr <= BadVAddr; ..... /*** Cause Register ExcCode Field ***/ always @(*) begin // Ordered by Pipeline Stage with Interrupts last if (EXC_AdEL) Cause_ExcCode_bits <= 4'h4; // 00100 else if (EXC_AdES) Cause_ExcCode_bits <= 4'h5; // 00101 else if (EXC_Tr) Cause_ExcCode_bits <= 4'hd; // 01101 else if (EXC_Ov) Cause_ExcCode_bits <= 4'hc; // 01100 else if (EXC_Sys) Cause_ExcCode_bits <= 4'h8; // 01000 else if (EXC_Bp) Cause_ExcCode_bits <= 4'h9; // 01001 else if (EXC_RI) Cause_ExcCode_bits <= 4'ha; // 01010 else if (EXC_CpU) Cause_ExcCode_bits <= 4'hb; // 01011 else if (EXC_AdIF) Cause_ExcCode_bits <= 4'h4; // 00100 else if (EXC_Int) Cause_ExcCode_bits <= 4'h0; // 00000 // OK that NMI writes this. else Cause_ExcCode_bits <= 4'bxxxx; end
EXC_Sys(システムコール例外に関して)
コア側の制御ユニットで、命令デコード時に「syscall」だったらEXC_Sys
wireをassertする。それをCP0がInputとして受取り、検知する。ソフトウェア例外用に用意されている IP[1:0]
は特に使用していないようなので無視して良いだろう。
TLB例外を追加する場合
- TLBL(code=2), TLBS(code=3) を上記CP0クラスに追加する必要あり。
input wire
としてEXC_TLBL
EXC_TLBS
を追加して中のコードもいじる必要があるだろう
xv6 bootmain
0x7c00
はx86のBIOSがMBRを読み取って、物理メモリに展開するときの位置(仕様)
0x10000
はプログラム側でディスク読み取って、物理メモリに展開する際のdstアドレス。これはリンカスクリプトの設定と一致させればよい。
つまり、自作する際はリセットベクタ付近に、
- bootasm.S(MIPSならレジスタ初期化とbootmain()へのjumpくらいしかやらなくてよい。)
- bootmain.c(まず、ディスクからカーネル本体のELFヘッダを読み取り、それに従って本体を物理メモリ上に順次展開していく)
の双方を含むコードを配置しておけば良い。ブートローダ自体はコンフィギュレーションROM等に配置できそうだが、数百KBはある、カーネル本体(ELFイメージ)をどうやってFPGAに転送するのか要調査。
xv6-mipsリンカスクリプトとentry
OUTPUT_FORMAT("elf32-tradlittlemips", "elf32-tradlittlemips", "elf32-tradlittlemips") OUTPUT_ARCH(mips) ENTRY(_start)
この _start
はentry.Sに定義されている。
# By convention, the _start symbol specifies the ELF entry point. .globl _start _start: mfc0 $t0, $COP0_STATUS # Disable interrput li $t1, STATUS_IE or $t0, $t1, $t1 xor $t0, $t0, $t1 # Setup IM registers preparing for enabling interrput ori $t0, $t0, STATUS_IM # COP0 lui $t1, STATUS_CU0 >> 16 or $t0, $t0, $t1 mtc0 $t0, $COP0_STATUS # Set up the stack pointer. la $sp, stack + KSTACKSIZE # Jump to main() la $t0, main jr $t0 nop
ここの最後で main()
へ飛ぶ。memfsの仕組みがよくわかってないが、
xv6のI/O, PIC, IRQ, uart
プロセッサ側とポートを合わせなきゃいけないものと、kernel(ソフト側)で好き勝手に決めればいいものの境界線を探っている。xv6-mipsで定義している
mips.h
static int io_port_base = 0xb4000000;
これはなぜこのアドレスなのかわからない。inb
outb
で uchar *
へキャストしているのが関係ある?
→QEMUのISAバスが Phys 0x1400_0000 に紐付いているから。 0xb400_0000 - 0x1400_0000 = 0xA0000000 (kseg1の先頭)
となる。
uart 0x3F8
COM (hardware interface) - Wikipedia
これは仕様で決まってるっぽい
mips interrupt mask i/O
CPUで、「一体どのハードウェアからinterruptがきたのか」をどうやって判別するのか調べている。ついでにMemory-mapped I/Oの理解もきちんと出来ていないので。「こっからここまではデバイス用I/Oメモリ!」って決めるのは誰?CPUなのかkernelなのか、はたまたデバイスなのか。
https://courses.cs.washington.edu/courses/cse378/07au/lectures/L20-Interrupt.pdf
http://jjc.hydrus.net/cs61c/handouts/interrupts1.pdf
メモリマップドI/Oの入出力ポートとアドレスの割付はハードウェア(address decoder, address code decoderなどと呼ばれる)で決め打ち
このへんを見ると、わかるがI/OメモリマップはCPUの外にあるハードウェア(address decoderという回路)で制御しているっぽい。CPUが出力する物理アドレスを見て、それが特定のアドレス(I/Oにマップされている)の場合、向き先をデバイスに向ける。
CPUに内蔵されているデバイスであれば、ボードが違っても同じアドレスに見えるはずだよね。でも、デバイスを外付けした場合は、ボードの設計によるんだ。この場合は、ボードの仕様書にメモリマップが書かれているはずだよ。
I/Oポートベース(qemuだとPhys0x1400_0000)もハードウェアで好きに設定してよい
(xv6-mipsだと)ベースアドレスからのオフセットで、各周辺デバイスへのメモリマップを行う。COM1を 0x3f8
にしているのもPC/AT互換機のボード設定であり、MIPSおれおれボードで必ずしもこのアドレスに設定する必要はない。(あくまで、address decoderの回路とkernelの設定を一致させればよいだけ)
cpu - Difference between port mapped and memory mapped access? - Super User
The hardware of the system is arranged so that devices on the address bus will only respond to particular addresses which are intended for them, while all other addresses are ignored. This is the job of the address decoding circuitry, and that establishes the memory map of the system.
自作CPUではPICはいらない
パタヘネ本にある通り、ハードウェア側でgeneral exception handler entry point addressを決め打ちする(0x8000_0180
)。例外ベクタは使用しない。PICも必要ない。
DMA (I/O) Controller
I/Oを特に工夫しない通常のデータの流れだと、I/Oデバイスからレジスタファイルへデータ転送(lw
)→レジスタファイルからRAMへデータ転送(sw
) というのが読み出したいbyte分繰り返されることになり、その間CPUは他の仕事をできない。
これを、I/Oデバイス(DMA Controller)⇔RAM間で直接データ転送できるようにするのがDMAである。DMA Controller or I/O Controllerは回路であり、いわゆるマザボ等のハードウェア側で対応しなければ使用できない。
バスとは
単なる概念。Data Busは、ReadData[32], WriteData[32]に分かれていたりする。その集合概念