diff options
Diffstat (limited to 'VexRiscv/src/main/scala/vexriscv/plugin')
39 files changed, 9167 insertions, 0 deletions
diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/AesPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/AesPlugin.scala new file mode 100644 index 0000000..0d4556a --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/AesPlugin.scala @@ -0,0 +1,329 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import vexriscv.{DecoderService, Stageable, VexRiscv} + +/** + * The AesPlugin allow to reduce the instruction count of each AES round by providing the following instruction : + * 1) aes_enc_round(rs1, rs2, sel). rd = rs1 ^ quad_mul(sel, sbox(byte_sel(rs2, sel))) + * 2) aes_enc_round_last(rs1, rs2, sel). rd = rs1 ^ quad_sbox(byte_sel(rs2, sel)) + * 3) aes_dec_round(rs1, rs2, sel). rd = rs1 ^ quad_inv_sbox(quad_mul(sel,byte_sel(rs2, sel))) + * 4) aes_dec_round_last(rs1, rs2, sel). rd = rs1 ^ quad_inv_sbox(byte_sel(rs2, sel)) + * + * Here is what those inner functions mean: + * - sbox apply the sbox transformation on the 'sel' byte of the 32 bits word + * - quad_mul multiply (Galois field) each byte of 32 bits word by a constant (which depend of sel) + * - quad_inv_sbox apply the inverse sbox transformation on each byte of 32 bits word + * + * You can find a complet example of those instruction usage in aes_cusom.h in vexriscv_aes_encrypt and + * vexriscv_aes_decrypt. Those function are made to work on little endian as in the linux kernel default AES + * implementation, but unlike libressl, libopenssl and dropbear ones (swapping the byte of the expended key can fix that). + * + * This plugin implement the processing using a single 32_bits * 512_words rom to fetch the sbox/inv_sbox/multiplication + * results already combined. This rom is formated as following : + * + * From word 0x000 to 0x0FF, it is formatted as follow : (note multiplication are in Galois field) + * [ 7 : 0] : SBox[word_address & 0xFF] * 1 + * [15 : 8] : SBox[word_address & 0xFF] * 2 + * [23 : 16] : SBox[word_address & 0xFF] * 3 + * [31 : 24] : inverse SBox[word_address & 0xFF] * 1 (Used for the last round of the decryption) + * + * From word 0x100 to 0x1FF, it is formatted as follow : + * [ 7 : 0] : inverse SBox[word_address & 0xFF * 14] + * [15 : 8] : inverse SBox[word_address & 0xFF * 9] + * [23 : 16] : inverse SBox[word_address & 0xFF * 13] + * [31 : 24] : inverse SBox[word_address & 0xFF * 11] + * + * So, on each instruction, the following is done (in order) + * 1) Select the 'sel' byte of RS2 + * 2) Read the rom at a address which depend of the RS2 selected byte and the instruction + * 3) Permute the rom read data depending the instruction and the 'sel' argument + * 4) Xor the result with RS1 and return that as instruction result + * + * The instructions are encoded by default as following : + * --SS-LDXXXXXYYYYY000ZZZZZ0001011 + * + * Where : + * - XXXXX is the register file source 2 (RS2) + * - YYYYY is the register file source 1 (RS1) + * - ZZZZZ is the register file destination + * - D=1 mean decrypt, D=0 mean encrypt + * - L=1 mean last round, L=0 mean full round + * - SS specify which byte should be used from RS2 for the processing + * + * In practice the aes-256-cbc performances should improve by a factor 4. See the following results from libopenssl + * from a SoC running linux at 100 Mhz + * type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes 16384 bytes + * aes-256-cbc SW 492.58k 700.22k 796.41k 831.49k 830.09k 832.81k + * aes-256 cbc HW 1781.52k 2834.07k 3323.07k 3486.72k 3465.22k 3440.10k + */ + +case class AesPlugin(encoding : MaskedLiteral = M"-----------------000-----0001011") extends Plugin[VexRiscv]{ + + object IS_AES extends Stageable(Bool) + object CALC extends Stageable(Bits(32 bits)) + + val mapping = new { + def DECRYPT = 25 // 0/1 => encrypt/decrypt + def LAST_ROUND = 26 + def BYTE_SEL = 28 //Which byte should be used in RS2 + } + + //Callback to setup the plugin and ask for different services + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + + val decoderService = pipeline.service(classOf[DecoderService]) + + decoderService.addDefault(IS_AES, False) + decoderService.add( + key = encoding, + List( + IS_AES -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, //Late result + RS1_USE -> True, + RS2_USE -> True + ) + ) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + + + def BANK0 = (TE0, SBOX_INV).zipped.map((te0, inv) => (te0.toLong) | (inv.toLong << 24)) + def BANK1 = TD0 + + + + val onExecute = execute plug new Area{ + import execute._ + val byteSel = input(INSTRUCTION)(mapping.BYTE_SEL, 2 bits).asUInt + val bankSel = input(INSTRUCTION)(mapping.DECRYPT) && !input(INSTRUCTION)(mapping.LAST_ROUND) + val romAddress = U(bankSel ## input(RS2).subdivideIn(8 bits).read(byteSel)) + } + + memory plug new Area{ + import memory._ + + //Decode the rom data + val rom = new Area { + val storage = Mem(Bits(32 bits), 512) initBigInt((BANK0 ++ BANK1).map(BigInt(_))) + + val data = storage.readSync(onExecute.romAddress, !arbitration.isStuck) + val bytes = data.subdivideIn(8 bits) + + def VecUInt(l: Int*) = Vec(l.map(U(_, 2 bits))) + // remap will be used to decode the rom + val remap = Vec( + VecUInt(2, 0, 0, 1), + VecUInt(0, 0, 0, 0), + VecUInt(3, 2, 1, 0), + VecUInt(3, 3, 3, 3) + ) + + val address = U(input(INSTRUCTION)(mapping.DECRYPT) ## input(INSTRUCTION)(mapping.LAST_ROUND)) + val output = remap(address) + } + + val wordDesuffle = new Area{ + val zero = B"0000" + val byteSel = input(INSTRUCTION)(mapping.BYTE_SEL, 2 bits).asUInt + val output = Vec(Bits(8 bits), 4) + + def remap(l : Int*) = Vec(l.map(rom.output(_))) + val sel = byteSel.mux( + 0 -> remap(3, 2, 1, 0), + 1 -> remap(0, 3, 2, 1), + 2 -> remap(1, 0, 3, 2), + 3 -> remap(2, 1, 0, 3) + ) + when(input(INSTRUCTION)(mapping.LAST_ROUND)){ + zero := B"1111" + zero(byteSel) := False + } + + //Finaly, mux the rom data + for(byteId <- 0 to 3){ + output(byteId) := rom.bytes(sel(byteId)) + when(zero(byteId)){ + output(byteId) := 0 + } + } + } + + val xored = wordDesuffle.output.asBits ^ input(RS1) + insert(CALC) := xored + } + + writeBack plug new Area { + import writeBack._ + + when(input(IS_AES)) { + output(REGFILE_WRITE_DATA) := input(CALC) + } + } + } + + // Encryption table which solve a single byte sbox + column mix. Used for all rounds + def TE0 = List( + 0xa5c663, 0x84f87c, 0x99ee77, 0x8df67b, + 0x0dfff2, 0xbdd66b, 0xb1de6f, 0x5491c5, + 0x506030, 0x030201, 0xa9ce67, 0x7d562b, + 0x19e7fe, 0x62b5d7, 0xe64dab, 0x9aec76, + 0x458fca, 0x9d1f82, 0x4089c9, 0x87fa7d, + 0x15effa, 0xebb259, 0xc98e47, 0x0bfbf0, + 0xec41ad, 0x67b3d4, 0xfd5fa2, 0xea45af, + 0xbf239c, 0xf753a4, 0x96e472, 0x5b9bc0, + 0xc275b7, 0x1ce1fd, 0xae3d93, 0x6a4c26, + 0x5a6c36, 0x417e3f, 0x02f5f7, 0x4f83cc, + 0x5c6834, 0xf451a5, 0x34d1e5, 0x08f9f1, + 0x93e271, 0x73abd8, 0x536231, 0x3f2a15, + 0x0c0804, 0x5295c7, 0x654623, 0x5e9dc3, + 0x283018, 0xa13796, 0x0f0a05, 0xb52f9a, + 0x090e07, 0x362412, 0x9b1b80, 0x3ddfe2, + 0x26cdeb, 0x694e27, 0xcd7fb2, 0x9fea75, + 0x1b1209, 0x9e1d83, 0x74582c, 0x2e341a, + 0x2d361b, 0xb2dc6e, 0xeeb45a, 0xfb5ba0, + 0xf6a452, 0x4d763b, 0x61b7d6, 0xce7db3, + 0x7b5229, 0x3edde3, 0x715e2f, 0x971384, + 0xf5a653, 0x68b9d1, 0x000000, 0x2cc1ed, + 0x604020, 0x1fe3fc, 0xc879b1, 0xedb65b, + 0xbed46a, 0x468dcb, 0xd967be, 0x4b7239, + 0xde944a, 0xd4984c, 0xe8b058, 0x4a85cf, + 0x6bbbd0, 0x2ac5ef, 0xe54faa, 0x16edfb, + 0xc58643, 0xd79a4d, 0x556633, 0x941185, + 0xcf8a45, 0x10e9f9, 0x060402, 0x81fe7f, + 0xf0a050, 0x44783c, 0xba259f, 0xe34ba8, + 0xf3a251, 0xfe5da3, 0xc08040, 0x8a058f, + 0xad3f92, 0xbc219d, 0x487038, 0x04f1f5, + 0xdf63bc, 0xc177b6, 0x75afda, 0x634221, + 0x302010, 0x1ae5ff, 0x0efdf3, 0x6dbfd2, + 0x4c81cd, 0x14180c, 0x352613, 0x2fc3ec, + 0xe1be5f, 0xa23597, 0xcc8844, 0x392e17, + 0x5793c4, 0xf255a7, 0x82fc7e, 0x477a3d, + 0xacc864, 0xe7ba5d, 0x2b3219, 0x95e673, + 0xa0c060, 0x981981, 0xd19e4f, 0x7fa3dc, + 0x664422, 0x7e542a, 0xab3b90, 0x830b88, + 0xca8c46, 0x29c7ee, 0xd36bb8, 0x3c2814, + 0x79a7de, 0xe2bc5e, 0x1d160b, 0x76addb, + 0x3bdbe0, 0x566432, 0x4e743a, 0x1e140a, + 0xdb9249, 0x0a0c06, 0x6c4824, 0xe4b85c, + 0x5d9fc2, 0x6ebdd3, 0xef43ac, 0xa6c462, + 0xa83991, 0xa43195, 0x37d3e4, 0x8bf279, + 0x32d5e7, 0x438bc8, 0x596e37, 0xb7da6d, + 0x8c018d, 0x64b1d5, 0xd29c4e, 0xe049a9, + 0xb4d86c, 0xfaac56, 0x07f3f4, 0x25cfea, + 0xafca65, 0x8ef47a, 0xe947ae, 0x181008, + 0xd56fba, 0x88f078, 0x6f4a25, 0x725c2e, + 0x24381c, 0xf157a6, 0xc773b4, 0x5197c6, + 0x23cbe8, 0x7ca1dd, 0x9ce874, 0x213e1f, + 0xdd964b, 0xdc61bd, 0x860d8b, 0x850f8a, + 0x90e070, 0x427c3e, 0xc471b5, 0xaacc66, + 0xd89048, 0x050603, 0x01f7f6, 0x121c0e, + 0xa3c261, 0x5f6a35, 0xf9ae57, 0xd069b9, + 0x911786, 0x5899c1, 0x273a1d, 0xb9279e, + 0x38d9e1, 0x13ebf8, 0xb32b98, 0x332211, + 0xbbd269, 0x70a9d9, 0x89078e, 0xa73394, + 0xb62d9b, 0x223c1e, 0x921587, 0x20c9e9, + 0x4987ce, 0xffaa55, 0x785028, 0x7aa5df, + 0x8f038c, 0xf859a1, 0x800989, 0x171a0d, + 0xda65bf, 0x31d7e6, 0xc68442, 0xb8d068, + 0xc38241, 0xb02999, 0x775a2d, 0x111e0f, + 0xcb7bb0, 0xfca854, 0xd66dbb, 0x3a2c16 + ) + + + // Decryption table which solve a single byte sbox + column mix. Not used in the last round + def TD0 = List( + 0x50a7f451l, 0x5365417el, 0xc3a4171al, 0x965e273al, + 0xcb6bab3bl, 0xf1459d1fl, 0xab58faacl, 0x9303e34bl, + 0x55fa3020l, 0xf66d76adl, 0x9176cc88l, 0x254c02f5l, + 0xfcd7e54fl, 0xd7cb2ac5l, 0x80443526l, 0x8fa362b5l, + 0x495ab1del, 0x671bba25l, 0x980eea45l, 0xe1c0fe5dl, + 0x02752fc3l, 0x12f04c81l, 0xa397468dl, 0xc6f9d36bl, + 0xe75f8f03l, 0x959c9215l, 0xeb7a6dbfl, 0xda595295l, + 0x2d83bed4l, 0xd3217458l, 0x2969e049l, 0x44c8c98el, + 0x6a89c275l, 0x78798ef4l, 0x6b3e5899l, 0xdd71b927l, + 0xb64fe1bel, 0x17ad88f0l, 0x66ac20c9l, 0xb43ace7dl, + 0x184adf63l, 0x82311ae5l, 0x60335197l, 0x457f5362l, + 0xe07764b1l, 0x84ae6bbbl, 0x1ca081fel, 0x942b08f9l, + 0x58684870l, 0x19fd458fl, 0x876cde94l, 0xb7f87b52l, + 0x23d373abl, 0xe2024b72l, 0x578f1fe3l, 0x2aab5566l, + 0x0728ebb2l, 0x03c2b52fl, 0x9a7bc586l, 0xa50837d3l, + 0xf2872830l, 0xb2a5bf23l, 0xba6a0302l, 0x5c8216edl, + 0x2b1ccf8al, 0x92b479a7l, 0xf0f207f3l, 0xa1e2694el, + 0xcdf4da65l, 0xd5be0506l, 0x1f6234d1l, 0x8afea6c4l, + 0x9d532e34l, 0xa055f3a2l, 0x32e18a05l, 0x75ebf6a4l, + 0x39ec830bl, 0xaaef6040l, 0x069f715el, 0x51106ebdl, + 0xf98a213el, 0x3d06dd96l, 0xae053eddl, 0x46bde64dl, + 0xb58d5491l, 0x055dc471l, 0x6fd40604l, 0xff155060l, + 0x24fb9819l, 0x97e9bdd6l, 0xcc434089l, 0x779ed967l, + 0xbd42e8b0l, 0x888b8907l, 0x385b19e7l, 0xdbeec879l, + 0x470a7ca1l, 0xe90f427cl, 0xc91e84f8l, 0x00000000l, + 0x83868009l, 0x48ed2b32l, 0xac70111el, 0x4e725a6cl, + 0xfbff0efdl, 0x5638850fl, 0x1ed5ae3dl, 0x27392d36l, + 0x64d90f0al, 0x21a65c68l, 0xd1545b9bl, 0x3a2e3624l, + 0xb1670a0cl, 0x0fe75793l, 0xd296eeb4l, 0x9e919b1bl, + 0x4fc5c080l, 0xa220dc61l, 0x694b775al, 0x161a121cl, + 0x0aba93e2l, 0xe52aa0c0l, 0x43e0223cl, 0x1d171b12l, + 0x0b0d090el, 0xadc78bf2l, 0xb9a8b62dl, 0xc8a91e14l, + 0x8519f157l, 0x4c0775afl, 0xbbdd99eel, 0xfd607fa3l, + 0x9f2601f7l, 0xbcf5725cl, 0xc53b6644l, 0x347efb5bl, + 0x7629438bl, 0xdcc623cbl, 0x68fcedb6l, 0x63f1e4b8l, + 0xcadc31d7l, 0x10856342l, 0x40229713l, 0x2011c684l, + 0x7d244a85l, 0xf83dbbd2l, 0x1132f9ael, 0x6da129c7l, + 0x4b2f9e1dl, 0xf330b2dcl, 0xec52860dl, 0xd0e3c177l, + 0x6c16b32bl, 0x99b970a9l, 0xfa489411l, 0x2264e947l, + 0xc48cfca8l, 0x1a3ff0a0l, 0xd82c7d56l, 0xef903322l, + 0xc74e4987l, 0xc1d138d9l, 0xfea2ca8cl, 0x360bd498l, + 0xcf81f5a6l, 0x28de7aa5l, 0x268eb7dal, 0xa4bfad3fl, + 0xe49d3a2cl, 0x0d927850l, 0x9bcc5f6al, 0x62467e54l, + 0xc2138df6l, 0xe8b8d890l, 0x5ef7392el, 0xf5afc382l, + 0xbe805d9fl, 0x7c93d069l, 0xa92dd56fl, 0xb31225cfl, + 0x3b99acc8l, 0xa77d1810l, 0x6e639ce8l, 0x7bbb3bdbl, + 0x097826cdl, 0xf418596el, 0x01b79aecl, 0xa89a4f83l, + 0x656e95e6l, 0x7ee6ffaal, 0x08cfbc21l, 0xe6e815efl, + 0xd99be7bal, 0xce366f4al, 0xd4099feal, 0xd67cb029l, + 0xafb2a431l, 0x31233f2al, 0x3094a5c6l, 0xc066a235l, + 0x37bc4e74l, 0xa6ca82fcl, 0xb0d090e0l, 0x15d8a733l, + 0x4a9804f1l, 0xf7daec41l, 0x0e50cd7fl, 0x2ff69117l, + 0x8dd64d76l, 0x4db0ef43l, 0x544daaccl, 0xdf0496e4l, + 0xe3b5d19el, 0x1b886a4cl, 0xb81f2cc1l, 0x7f516546l, + 0x04ea5e9dl, 0x5d358c01l, 0x737487fal, 0x2e410bfbl, + 0x5a1d67b3l, 0x52d2db92l, 0x335610e9l, 0x1347d66dl, + 0x8c61d79al, 0x7a0ca137l, 0x8e14f859l, 0x893c13ebl, + 0xee27a9cel, 0x35c961b7l, 0xede51ce1l, 0x3cb1477al, + 0x59dfd29cl, 0x3f73f255l, 0x79ce1418l, 0xbf37c773l, + 0xeacdf753l, 0x5baafd5fl, 0x146f3ddfl, 0x86db4478l, + 0x81f3afcal, 0x3ec468b9l, 0x2c342438l, 0x5f40a3c2l, + 0x72c31d16l, 0x0c25e2bcl, 0x8b493c28l, 0x41950dffl, + 0x7101a839l, 0xdeb30c08l, 0x9ce4b4d8l, 0x90c15664l, + 0x6184cb7bl, 0x70b632d5l, 0x745c6c48l, 0x4257b8d0l + ) + + // Last round decryption sbox + def SBOX_INV = List( + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d + ) +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/BranchPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/BranchPlugin.scala new file mode 100644 index 0000000..24d42fa --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/BranchPlugin.scala @@ -0,0 +1,386 @@ +package vexriscv.plugin + +import vexriscv.Riscv._ +import vexriscv._ +import spinal.core._ +import spinal.lib._ + +trait BranchPrediction +object NONE extends BranchPrediction +object STATIC extends BranchPrediction +object DYNAMIC extends BranchPrediction +object DYNAMIC_TARGET extends BranchPrediction + +object BranchCtrlEnum extends SpinalEnum(binarySequential){ + val INC,B,JAL,JALR = newElement() +} +object BRANCH_CTRL extends Stageable(BranchCtrlEnum()) + + +case class DecodePredictionCmd() extends Bundle { + val hadBranch = Bool +} +case class DecodePredictionRsp(stage : Stage) extends Bundle { + val wasWrong = Bool +} +case class DecodePredictionBus(stage : Stage) extends Bundle { + val cmd = DecodePredictionCmd() + val rsp = DecodePredictionRsp(stage) +} + +case class FetchPredictionCmd() extends Bundle{ + val hadBranch = Bool + val targetPc = UInt(32 bits) +} +case class FetchPredictionRsp() extends Bundle{ + val wasRight = Bool + val finalPc = UInt(32 bits) + val sourceLastWord = UInt(32 bits) +} +case class FetchPredictionBus(stage : Stage) extends Bundle { + val cmd = FetchPredictionCmd() + val rsp = FetchPredictionRsp() +} + + +trait PredictionInterface{ + def askFetchPrediction() : FetchPredictionBus + def askDecodePrediction() : DecodePredictionBus + def inDebugNoFetch() : Unit +} + + + +class BranchPlugin(earlyBranch : Boolean, + catchAddressMisaligned : Boolean = false, + fenceiGenAsAJump : Boolean = false, + fenceiGenAsANop : Boolean = false, + decodeBranchSrc2 : Boolean = false) extends Plugin[VexRiscv] with PredictionInterface{ + + + def catchAddressMisalignedForReal = catchAddressMisaligned && !pipeline.config.withRvc + lazy val branchStage = if(earlyBranch) pipeline.execute else pipeline.memory + + object BRANCH_CALC extends Stageable(UInt(32 bits)) + object BRANCH_DO extends Stageable(Bool) + object BRANCH_COND_RESULT extends Stageable(Bool) + object IS_FENCEI extends Stageable(Bool) + + var jumpInterface : Flow[UInt] = null + var predictionExceptionPort : Flow[ExceptionCause] = null + var branchExceptionPort : Flow[ExceptionCause] = null + var inDebugNoFetchFlag : Bool = null + + + var decodePrediction : DecodePredictionBus = null + var fetchPrediction : FetchPredictionBus = null + + + override def askFetchPrediction() = { + fetchPrediction = FetchPredictionBus(branchStage) + fetchPrediction + } + + override def askDecodePrediction() = { + decodePrediction = DecodePredictionBus(branchStage) + decodePrediction + } + + + override def inDebugNoFetch(): Unit = inDebugNoFetchFlag := True + + def hasHazardOnBranch = if(earlyBranch) pipeline.service(classOf[HazardService]).hazardOnExecuteRS else False + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + import IntAluPlugin._ + + assert(earlyBranch || withMemoryStage, "earlyBranch must be true when memory stage is disabled!") + + val bActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + SRC_USE_SUB_LESS -> True, + RS1_USE -> True, + RS2_USE -> True, + HAS_SIDE_EFFECT -> True + ) + + val jActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.PC_INCREMENT, + SRC2_CTRL -> Src2CtrlEnum.PC, + SRC_USE_SUB_LESS -> False, + REGFILE_WRITE_VALID -> True, + HAS_SIDE_EFFECT -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + + + decoderService.addDefault(BRANCH_CTRL, BranchCtrlEnum.INC) + decoderService.add(List( + JAL(true) -> (jActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.JAL, ALU_CTRL -> AluCtrlEnum.ADD_SUB)), + JALR -> (jActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.JALR, ALU_CTRL -> AluCtrlEnum.ADD_SUB, RS1_USE -> True)), + BEQ(true) -> (bActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.B)), + BNE(true) -> (bActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.B)), + BLT(true) -> (bActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.B, SRC_LESS_UNSIGNED -> False)), + BGE(true) -> (bActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.B, SRC_LESS_UNSIGNED -> False)), + BLTU(true) -> (bActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.B, SRC_LESS_UNSIGNED -> True)), + BGEU(true) -> (bActions ++ List(BRANCH_CTRL -> BranchCtrlEnum.B, SRC_LESS_UNSIGNED -> True)) + )) + + if(fenceiGenAsAJump) { + decoderService.addDefault(IS_FENCEI, False) + decoderService.add(List( + FENCEI -> (List(IS_FENCEI -> True,HAS_SIDE_EFFECT -> True, BRANCH_CTRL -> BranchCtrlEnum.JAL)) + )) + } + + if(fenceiGenAsANop){ + decoderService.add(List(FENCEI -> List())) + } + + val pcManagerService = pipeline.service(classOf[JumpService]) + + //Priority -1, as DYNAMIC_TARGET misspredicted on non branch instruction should lose against other instructions + //legitim branches, as MRET for instance + jumpInterface = pcManagerService.createJumpInterface(branchStage, priority = -10) + + + if (catchAddressMisalignedForReal) { + val exceptionService = pipeline.service(classOf[ExceptionService]) + branchExceptionPort = exceptionService.newExceptionPort(branchStage) + } + inDebugNoFetchFlag = False.setCompositeName(this, "inDebugNoFetchFlag") + } + + override def build(pipeline: VexRiscv): Unit = { + (fetchPrediction,decodePrediction) match { + case (null, null) => buildWithoutPrediction(pipeline) + case (_ , null) => buildFetchPrediction(pipeline) + case (null, _) => buildDecodePrediction(pipeline) + } + if(fenceiGenAsAJump) { + import pipeline._ + import pipeline.config._ + when(decode.input(IS_FENCEI)) { + decode.output(INSTRUCTION)(12) := False + decode.output(INSTRUCTION)(22) := True + } + execute.arbitration.haltByOther setWhen(execute.arbitration.isValid && execute.input(IS_FENCEI) && stagesFromExecute.tail.map(_.arbitration.isValid).asBits.orR) + } + } + + def buildWithoutPrediction(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + //Do branch calculations (conditions + target PC) + execute plug new Area { + import execute._ + + val less = input(SRC_LESS) + val eq = input(SRC1) === input(SRC2) + + insert(BRANCH_DO) := input(BRANCH_CTRL).mux( + BranchCtrlEnum.INC -> False, + BranchCtrlEnum.JAL -> True, + BranchCtrlEnum.JALR -> True, + BranchCtrlEnum.B -> input(INSTRUCTION)(14 downto 12).mux( + B"000" -> eq , + B"001" -> !eq , + M"1-1" -> !less, + default -> less + ) + ) + + val imm = IMM(input(INSTRUCTION)) + val branch_src1 = (input(BRANCH_CTRL) === BranchCtrlEnum.JALR) ? input(RS1).asUInt | input(PC) + val branch_src2 = input(BRANCH_CTRL).mux( + BranchCtrlEnum.JAL -> imm.j_sext, + BranchCtrlEnum.JALR -> imm.i_sext, + default -> imm.b_sext + ).asUInt + + val branchAdder = branch_src1 + branch_src2 + insert(BRANCH_CALC) := branchAdder(31 downto 1) @@ U"0" + } + + //Apply branchs (JAL,JALR, Bxx) + branchStage plug new Area { + import branchStage._ + jumpInterface.valid := arbitration.isValid && input(BRANCH_DO) && !hasHazardOnBranch + jumpInterface.payload := input(BRANCH_CALC) + arbitration.flushNext setWhen(jumpInterface.valid) + + if(catchAddressMisalignedForReal) { + branchExceptionPort.valid := arbitration.isValid && input(BRANCH_DO) && jumpInterface.payload(1) + branchExceptionPort.code := 0 + branchExceptionPort.badAddr := jumpInterface.payload + + if(branchStage == execute) branchExceptionPort.valid clearWhen(service(classOf[HazardService]).hazardOnExecuteRS) + } + } + } + + + def buildDecodePrediction(pipeline: VexRiscv): Unit = { + object PREDICTION_HAD_BRANCHED extends Stageable(Bool) + + import pipeline._ + import pipeline.config._ + + + decode plug new Area { + import decode._ + insert(PREDICTION_HAD_BRANCHED) := (if(fenceiGenAsAJump) decodePrediction.cmd.hadBranch && !decode.input(IS_FENCEI) else decodePrediction.cmd.hadBranch) + } + + //Do real branch calculation + execute plug new Area { + import execute._ + + val less = input(SRC_LESS) + val eq = input(SRC1) === input(SRC2) + + insert(BRANCH_COND_RESULT) := input(BRANCH_CTRL).mux( + BranchCtrlEnum.INC -> False, + BranchCtrlEnum.JAL -> True, + BranchCtrlEnum.JALR -> True, + BranchCtrlEnum.B -> input(INSTRUCTION)(14 downto 12).mux( + B"000" -> eq , + B"001" -> !eq , + M"1-1" -> !less, + default -> less + ) + ) + + val imm = IMM(input(INSTRUCTION)) + val missAlignedTarget = if(pipeline.config.withRvc) False else (input(BRANCH_COND_RESULT) && input(BRANCH_CTRL).mux( + BranchCtrlEnum.JALR -> (imm.i_sext(1) ^ input(RS1)(1)), + BranchCtrlEnum.JAL -> imm.j_sext(1), + default -> imm.b_sext(1) + )) + + insert(BRANCH_DO) := input(PREDICTION_HAD_BRANCHED) =/= input(BRANCH_COND_RESULT) || missAlignedTarget + + //Calculation of the branch target / correction + val branch_src1,branch_src2 = UInt(32 bits) + switch(input(BRANCH_CTRL)){ + is(BranchCtrlEnum.JALR){ + branch_src1 := input(RS1).asUInt + branch_src2 := imm.i_sext.asUInt + } + default{ + branch_src1 := input(PC) + branch_src2 := ((input(BRANCH_CTRL) === BranchCtrlEnum.JAL) ? imm.j_sext | imm.b_sext).asUInt + when(input(PREDICTION_HAD_BRANCHED)){ //Assume the predictor never predict missaligned stuff, this avoid the need to know if the instruction should branch or not + branch_src2 := (if(pipeline.config.withRvc) Mux(input(IS_RVC), B(2), B(4)) else B(4)).asUInt.resized + } + } + } + val branchAdder = branch_src1 + branch_src2 + insert(BRANCH_CALC) := branchAdder(31 downto 1) @@ U"0" + } + + + // branch JALR or JAL/Bxx prediction miss corrections + val branchStage = if(earlyBranch) execute else memory + branchStage plug new Area { + import branchStage._ + jumpInterface.valid := arbitration.isValid && input(BRANCH_DO) && !hasHazardOnBranch + jumpInterface.payload := input(BRANCH_CALC) + arbitration.flushNext setWhen(jumpInterface.valid) + + if(catchAddressMisalignedForReal) { + val unalignedJump = input(BRANCH_DO) && input(BRANCH_CALC)(1) + branchExceptionPort.valid := arbitration.isValid && unalignedJump + branchExceptionPort.code := 0 + branchExceptionPort.badAddr := input(BRANCH_CALC) //pipeline.stages(pipeline.indexOf(branchStage)-1).input + + if(branchStage == execute) branchExceptionPort.valid clearWhen(service(classOf[HazardService]).hazardOnExecuteRS) + } + } + + decodePrediction.rsp.wasWrong := jumpInterface.valid + } + + + + + + def buildFetchPrediction(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + + //Do branch calculations (conditions + target PC) + object NEXT_PC extends Stageable(UInt(32 bits)) + object TARGET_MISSMATCH extends Stageable(Bool) + object BRANCH_SRC2 extends Stageable(UInt(32 bits)) + val branchSrc2Stage = if(decodeBranchSrc2) decode else execute + execute plug new Area { + import execute._ + + val less = input(SRC_LESS) + val eq = input(SRC1) === input(SRC2) + + insert(BRANCH_DO) := input(BRANCH_CTRL).mux( + BranchCtrlEnum.INC -> False, + BranchCtrlEnum.JAL -> True, + BranchCtrlEnum.JALR -> True, + BranchCtrlEnum.B -> input(INSTRUCTION)(14 downto 12).mux( + B"000" -> eq , + B"001" -> !eq , + M"1-1" -> !less, + default -> less + ) + ) + + val branch_src1 = (input(BRANCH_CTRL) === BranchCtrlEnum.JALR) ? input(RS1).asUInt | input(PC) + + val imm = IMM(branchSrc2Stage.input(INSTRUCTION)) + branchSrc2Stage.insert(BRANCH_SRC2) := branchSrc2Stage.input(BRANCH_CTRL).mux( + BranchCtrlEnum.JAL -> imm.j_sext, + BranchCtrlEnum.JALR -> imm.i_sext, + default -> imm.b_sext + ).asUInt + + val branchAdder = branch_src1 + input(BRANCH_SRC2) + insert(BRANCH_CALC) := branchAdder(31 downto 1) @@ U"0" + insert(NEXT_PC) := input(PC) + (if(pipeline.config.withRvc) ((input(IS_RVC)) ? U(2) | U(4)) else 4) + insert(TARGET_MISSMATCH) := decode.input(PC) =/= input(BRANCH_CALC) + } + + //Apply branchs (JAL,JALR, Bxx) + val branchStage = if(earlyBranch) execute else memory + branchStage plug new Area { + import branchStage._ + + val predictionMissmatch = fetchPrediction.cmd.hadBranch =/= input(BRANCH_DO) || (input(BRANCH_DO) && input(TARGET_MISSMATCH)) + when(inDebugNoFetchFlag) { predictionMissmatch := input(BRANCH_DO)} + fetchPrediction.rsp.wasRight := ! predictionMissmatch + fetchPrediction.rsp.finalPc := input(BRANCH_CALC) + fetchPrediction.rsp.sourceLastWord := { + if(pipeline.config.withRvc) + ((!input(IS_RVC) && input(PC)(1)) ? input(NEXT_PC) | input(PC)) + else + input(PC) + } + + jumpInterface.valid := arbitration.isValid && predictionMissmatch && !hasHazardOnBranch + jumpInterface.payload := (input(BRANCH_DO) ? input(BRANCH_CALC) | input(NEXT_PC)) + arbitration.flushNext setWhen(jumpInterface.valid) + + + if(catchAddressMisalignedForReal) { + branchExceptionPort.valid := arbitration.isValid && input(BRANCH_DO) && input(BRANCH_CALC)(1) + branchExceptionPort.code := 0 + branchExceptionPort.badAddr := input(BRANCH_CALC) + + if(branchStage == execute) branchExceptionPort.valid clearWhen(service(classOf[HazardService]).hazardOnExecuteRS) + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/CfuPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/CfuPlugin.scala new file mode 100644 index 0000000..d343640 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/CfuPlugin.scala @@ -0,0 +1,357 @@ +package vexriscv.plugin + +import vexriscv.{DecoderService, ExceptionCause, ExceptionService, Stage, Stageable, VexRiscv} +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.bmb.WeakConnector +import spinal.lib.bus.misc.{AddressMapping, DefaultMapping} +import vexriscv.Riscv.IMM + +case class CfuPluginParameter( + CFU_VERSION : Int, + CFU_INTERFACE_ID_W : Int, + CFU_FUNCTION_ID_W : Int, + CFU_REORDER_ID_W : Int, + CFU_REQ_RESP_ID_W : Int, + CFU_INPUTS : Int, + CFU_INPUT_DATA_W : Int, + CFU_OUTPUTS : Int, + CFU_OUTPUT_DATA_W : Int, + CFU_FLOW_REQ_READY_ALWAYS : Boolean, + CFU_FLOW_RESP_READY_ALWAYS : Boolean) + +case class CfuBusParameter(CFU_VERSION : Int = 0, + CFU_INTERFACE_ID_W : Int = 0, + CFU_FUNCTION_ID_W : Int, + CFU_CFU_ID_W : Int = 0, + CFU_REORDER_ID_W : Int = 0, + CFU_REQ_RESP_ID_W : Int = 0, + CFU_STATE_INDEX_NUM : Int = 0, + CFU_INPUTS : Int, + CFU_INPUT_DATA_W : Int, + CFU_OUTPUTS : Int, + CFU_OUTPUT_DATA_W : Int, + CFU_FLOW_REQ_READY_ALWAYS : Boolean, + CFU_FLOW_RESP_READY_ALWAYS : Boolean, + CFU_WITH_STATUS : Boolean = false, + CFU_RAW_INSN_W : Int = 0) + +case class CfuCmd( p : CfuBusParameter ) extends Bundle{ + val function_id = UInt(p.CFU_FUNCTION_ID_W bits) + val reorder_id = UInt(p.CFU_REORDER_ID_W bits) + val request_id = UInt(p.CFU_REQ_RESP_ID_W bits) + val inputs = Vec(Bits(p.CFU_INPUT_DATA_W bits), p.CFU_INPUTS) + val state_index = UInt(log2Up(p.CFU_STATE_INDEX_NUM) bits) + val cfu_index = UInt(p.CFU_CFU_ID_W bits) + val raw_insn = Bits(p.CFU_RAW_INSN_W bits) + def weakAssignFrom(m : CfuCmd): Unit ={ + def s = this + WeakConnector(m, s, m.function_id, s.function_id, defaultValue = null, allowUpSize = false, allowDownSize = true , allowDrop = true) + WeakConnector(m, s, m.reorder_id, s.reorder_id, defaultValue = null, allowUpSize = false , allowDownSize = false, allowDrop = false) + WeakConnector(m, s, m.request_id, s.request_id, defaultValue = null, allowUpSize = false, allowDownSize = false, allowDrop = false) + s.inputs := m.inputs + } +} + +case class CfuRsp(p : CfuBusParameter) extends Bundle{ + val response_id = UInt(p.CFU_REQ_RESP_ID_W bits) + val outputs = Vec(Bits(p.CFU_OUTPUT_DATA_W bits), p.CFU_OUTPUTS) + val status = p.CFU_WITH_STATUS generate Bits(3 bits) + + def weakAssignFrom(m : CfuRsp): Unit ={ + def s = this + s.response_id := m.response_id + s.outputs := m.outputs + } +} + +case class CfuBus(p : CfuBusParameter) extends Bundle with IMasterSlave{ + val cmd = Stream(CfuCmd(p)) + val rsp = Stream(CfuRsp(p)) + + def <<(m : CfuBus) : Unit = { + val s = this + s.cmd.arbitrationFrom(m.cmd) + m.rsp.arbitrationFrom(s.rsp) + + s.cmd.weakAssignFrom(m.cmd) + m.rsp.weakAssignFrom(s.rsp) + } + + override def asMaster(): Unit = { + master(cmd) + slave(rsp) + } +} + +object CfuPlugin{ + object Input2Kind extends SpinalEnum{ + val RS, IMM_I = newElement() + } +} + +case class CfuPluginEncoding(instruction : MaskedLiteral, + functionId : List[Range], + input2Kind : CfuPlugin.Input2Kind.E){ + val functionIdWidth = functionId.map(_.size).sum +} + +class CfuPlugin(val stageCount : Int, + val allowZeroLatency : Boolean, + val busParameter : CfuBusParameter, + val encodings : List[CfuPluginEncoding] = null, + val stateAndIndexCsrOffset : Int = 0xBC0, + val statusCsrOffset : Int = 0x801, + val withEnable : Boolean = true) extends Plugin[VexRiscv]{ + def p = busParameter + + assert(p.CFU_INPUTS <= 2) + assert(p.CFU_OUTPUTS == 1) +// assert(p.CFU_FUNCTION_ID_W == 3) + + var bus : CfuBus = null + + lazy val forkStage = pipeline.execute + lazy val joinStage = pipeline.stages(Math.min(pipeline.stages.length - 1, pipeline.indexOf(forkStage) + stageCount)) + + + val CFU_ENABLE = new Stageable(Bool()).setCompositeName(this, "CFU_ENABLE") + val CFU_IN_FLIGHT = new Stageable(Bool()).setCompositeName(this, "CFU_IN_FLIGHT") + val CFU_ENCODING = new Stageable(UInt(log2Up(encodings.size) bits)).setCompositeName(this, "CFU_ENCODING") + val CFU_INPUT_2_KIND = new Stageable(CfuPlugin.Input2Kind()).setCompositeName(this, "CFU_INPUT_2_KIND") + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + bus = master(CfuBus(p)) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(CFU_ENABLE, False) + + for((encoding, id) <- encodings.zipWithIndex){ + var actions = List( + CFU_ENABLE -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> Bool(stageCount == 0), + BYPASSABLE_MEMORY_STAGE -> Bool(stageCount <= 1), + RS1_USE -> True, + CFU_ENCODING -> U(id), + CFU_INPUT_2_KIND -> encoding.input2Kind() + ) + + encoding.input2Kind match { + case CfuPlugin.Input2Kind.RS => + actions :+= RS2_USE -> True + case CfuPlugin.Input2Kind.IMM_I => + } + + decoderService.add( + key = encoding.instruction, + values = actions + ) + } + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val csr = pipeline plug new Area{ + val factory = pipeline.service(classOf[CsrInterface]) + val en = withEnable generate (Reg(Bool()) init(False)) + if(withEnable) factory.rw(stateAndIndexCsrOffset, 31, en) + + val stateId = Reg(UInt(log2Up(p.CFU_STATE_INDEX_NUM) bits)) init(0) + if(p.CFU_STATE_INDEX_NUM > 1) { + assert(stateAndIndexCsrOffset != -1, "CfuPlugin stateCsrIndex need to be set in the parameters") + factory.rw(stateAndIndexCsrOffset, 16, stateId) + } + + val cfuIndex = Reg(UInt(p.CFU_CFU_ID_W bits)) init(0) + if(p.CFU_CFU_ID_W != 0){ + factory.rw(stateAndIndexCsrOffset, 0, cfuIndex) + } + val status = p.CFU_WITH_STATUS generate new Area{ + val CU, OP, FI, OF, SI, CI = RegInit(False) + val flags = List(CU, OP, FI, OF, SI, CI).reverse + factory.rw(statusCsrOffset, flags.zipWithIndex.map(_.swap) :_*) + factory.duringWrite(statusCsrOffset){ + decode.arbitration.haltByOther := True //Handle CSRW to decode + } + } + } + + if(withEnable) when(decode.input(CFU_ENABLE) && !csr.en){ + pipeline.service(classOf[DecoderService]).forceIllegal() + } + + forkStage plug new Area{ + import forkStage._ + val hazard = stages.dropWhile(_ != forkStage).tail.map(s => s.arbitration.isValid && s.input(HAS_SIDE_EFFECT)).orR + val scheduleWish = arbitration.isValid && input(CFU_ENABLE) + val schedule = scheduleWish && !hazard + arbitration.haltItself setWhen(scheduleWish && hazard) + + val hold = RegInit(False) setWhen(schedule) clearWhen(bus.cmd.ready) + val fired = RegInit(False) setWhen(bus.cmd.fire) clearWhen(!arbitration.isStuck) + insert(CFU_IN_FLIGHT) := schedule || hold || fired + + bus.cmd.valid := (schedule || hold) && !fired + arbitration.haltItself setWhen(bus.cmd.valid && !bus.cmd.ready) + +// bus.cmd.function_id := U(input(INSTRUCTION)(14 downto 12)).resized + val functionIdFromInstructinoWidth = encodings.map(_.functionIdWidth).max + val functionsIds = encodings.map(e => U(Cat(e.functionId.map(r => input(INSTRUCTION)(r))), functionIdFromInstructinoWidth bits)) + bus.cmd.cfu_index := csr.cfuIndex + bus.cmd.state_index := csr.stateId + bus.cmd.function_id := functionsIds.read(input(CFU_ENCODING)) + bus.cmd.reorder_id := 0 + bus.cmd.request_id := 0 + bus.cmd.raw_insn := input(INSTRUCTION).resized + if(p.CFU_INPUTS >= 1) bus.cmd.inputs(0) := input(RS1) + if(p.CFU_INPUTS >= 2) bus.cmd.inputs(1) := input(CFU_INPUT_2_KIND).mux( + CfuPlugin.Input2Kind.RS -> input(RS2), + CfuPlugin.Input2Kind.IMM_I -> IMM(input(INSTRUCTION)).h_sext + ) + } + + joinStage plug new Area{ + import joinStage._ + + //If the CFU interface can produce a result combinatorialy and the fork stage isn't the same than the join stage + //Then it is required to add a buffer on rsp to not propagate the fork stage ready := False in the CPU pipeline. + val rsp = if(p.CFU_FLOW_RESP_READY_ALWAYS){ + bus.rsp.toFlow.toStream.queueLowLatency( + size = stageCount + 1, + latency = 0 + ) + } else if(forkStage != joinStage && allowZeroLatency) { + bus.rsp.s2mPipe() + } else { + bus.rsp.combStage() + } + + rsp.ready := False + when(input(CFU_IN_FLIGHT)){ + arbitration.haltItself setWhen(!rsp.valid) + rsp.ready := !arbitration.isStuckByOthers + output(REGFILE_WRITE_DATA) := rsp.outputs(0) + if(p.CFU_WITH_STATUS) when(arbitration.isFiring){ + switch(rsp.status) { + for (i <- 1 to 6) is(i) { + csr.status.flags(i-1) := True + } + } + } + } + } + + pipeline.stages.drop(1).foreach(s => s.output(CFU_IN_FLIGHT) clearWhen(s.arbitration.isStuck)) + addPrePopTask(() => stages.dropWhile(_ != memory).reverse.dropWhile(_ != joinStage).foreach(s => s.input(CFU_IN_FLIGHT).init(False))) + } +} + + +object CfuTest{ + +// stageCount = 0, +// allowZeroLatency = true, + def getCfuParameter() = CfuBusParameter( + CFU_VERSION = 0, + CFU_INTERFACE_ID_W = 0, + CFU_FUNCTION_ID_W = 3, + CFU_REORDER_ID_W = 0, + CFU_REQ_RESP_ID_W = 0, + CFU_INPUTS = 2, + CFU_INPUT_DATA_W = 32, + CFU_OUTPUTS = 1, + CFU_OUTPUT_DATA_W = 32, + CFU_FLOW_REQ_READY_ALWAYS = false, + CFU_FLOW_RESP_READY_ALWAYS = false + ) +} +case class CfuTest() extends Component{ + val io = new Bundle { + val bus = slave(CfuBus(CfuTest.getCfuParameter())) + } + io.bus.rsp.arbitrationFrom(io.bus.cmd) + io.bus.rsp.response_id := io.bus.cmd.request_id + io.bus.rsp.outputs(0) := ~(io.bus.cmd.inputs(0) & io.bus.cmd.inputs(1)) +} + + +case class CfuBb(p : CfuBusParameter) extends BlackBox{ + val io = new Bundle { + val clk, reset = in Bool() + val bus = slave(CfuBus(p)) + } + + mapCurrentClockDomain(io.clk, io.reset) +} + +//case class CfuGray(p : CfuBusParameter) extends BlackBox{ +// val req_function_id = in Bits(p.CFU_FUNCTION_ID_W) +// val req_data = in Bits(p.CFU_REQ_INPUTS) +// val resp_data = in Bits(p.CFU_FUNCTION_ID_W) +// input `CFU_FUNCTION_ID req_function_id, +// input [CFU_REQ_INPUTS-1:0]`CFU_REQ_DATA req_data, +// output [CFU_RESP_OUTPUTS-1:0]`CFU_RESP_DATA resp_data +// io.bus.rsp.arbitrationFrom(io.bus.cmd) +// io.bus.rsp.response_ok := True +// io.bus.rsp.response_id := io.bus.cmd.request_id +// io.bus.rsp.outputs(0) := ~(io.bus.cmd.inputs(0) & io.bus.cmd.inputs(1)) +//} + + +case class CfuDecoder(p : CfuBusParameter, + mappings : Seq[AddressMapping], + pendingMax : Int = 3) extends Component{ + val io = new Bundle { + val input = slave(CfuBus(p)) + val outputs = Vec(master(CfuBus(p)), mappings.size) + } + val hasDefault = mappings.contains(DefaultMapping) + val logic = if(hasDefault && mappings.size == 1){ + io.outputs(0) << io.input + } else new Area { + val hits = Vec(Bool, mappings.size) + for (portId <- 0 until mappings.length) yield { + val slaveBus = io.outputs(portId) + val memorySpace = mappings(portId) + val hit = hits(portId) + hit := (memorySpace match { + case DefaultMapping => !hits.filterNot(_ == hit).orR + case _ => memorySpace.hit(io.input.cmd.function_id) + }) + slaveBus.cmd.valid := io.input.cmd.valid && hit + slaveBus.cmd.payload := io.input.cmd.payload.resized + } + val noHit = if (!hasDefault) !hits.orR else False + io.input.cmd.ready := (hits, io.outputs).zipped.map(_ && _.cmd.ready).orR || noHit + + val rspPendingCounter = Reg(UInt(log2Up(pendingMax + 1) bits)) init(0) + rspPendingCounter := rspPendingCounter + U(io.input.cmd.fire) - U(io.input.rsp.fire) + val rspHits = RegNextWhen(hits, io.input.cmd.fire) + val rspPending = rspPendingCounter =/= 0 + val rspNoHitValid = if (!hasDefault) !rspHits.orR else False + val rspNoHit = !hasDefault generate new Area{ + val doIt = RegInit(False) clearWhen(io.input.rsp.fire) setWhen(io.input.cmd.fire && noHit) + val response_id = RegNextWhen(io.input.cmd.request_id, io.input.cmd.fire) + } + + io.input.rsp.valid := io.outputs.map(_.rsp.valid).orR || (rspPending && rspNoHitValid) + io.input.rsp.payload := io.outputs.map(_.rsp.payload).read(OHToUInt(rspHits)) + if(!hasDefault) when(rspNoHit.doIt) { + io.input.rsp.valid := True + io.input.rsp.response_id := rspNoHit.response_id + } + for(output <- io.outputs) output.rsp.ready := io.input.rsp.ready + + val cmdWait = (rspPending && (hits =/= rspHits || rspNoHitValid)) || rspPendingCounter === pendingMax + when(cmdWait) { + io.input.cmd.ready := False + io.outputs.foreach(_.cmd.valid := False) + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/CsrPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/CsrPlugin.scala new file mode 100644 index 0000000..7731a41 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/CsrPlugin.scala @@ -0,0 +1,1335 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import vexriscv._ +import vexriscv.Riscv._ +import vexriscv.plugin.IntAluPlugin.{ALU_BITWISE_CTRL, ALU_CTRL, AluBitwiseCtrlEnum, AluCtrlEnum} + +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable +import spinal.core.sim._ + +/** + * Created by spinalvm on 21.03.17. + */ + +trait CsrAccess{ + def canWrite : Boolean = false + def canRead : Boolean = false +} +object CsrAccess { + object WRITE_ONLY extends CsrAccess{ + override def canWrite : Boolean = true + } + object READ_ONLY extends CsrAccess{ + override def canRead : Boolean = true + } + object READ_WRITE extends CsrAccess{ + override def canWrite : Boolean = true + override def canRead : Boolean = true + } + object NONE extends CsrAccess +} + + + +case class ExceptionPortInfo(port : Flow[ExceptionCause],stage : Stage, priority : Int, codeWidth : Int) +case class CsrPluginConfig( + catchIllegalAccess : Boolean, + mvendorid : BigInt, + marchid : BigInt, + mimpid : BigInt, + mhartid : BigInt, + misaExtensionsInit : Int, + misaAccess : CsrAccess, + mtvecAccess : CsrAccess, + mtvecInit : BigInt, + mepcAccess : CsrAccess, + mscratchGen : Boolean, + mcauseAccess : CsrAccess, + mbadaddrAccess : CsrAccess, + mcycleAccess : CsrAccess, + minstretAccess : CsrAccess, + ucycleAccess : CsrAccess, + uinstretAccess : CsrAccess = CsrAccess.NONE, + wfiGenAsWait : Boolean, + ecallGen : Boolean, + xtvecModeGen : Boolean = false, + noCsrAlu : Boolean = false, + wfiGenAsNop : Boolean = false, + ebreakGen : Boolean = false, + userGen : Boolean = false, + supervisorGen : Boolean = false, + sscratchGen : Boolean = false, + stvecAccess : CsrAccess = CsrAccess.NONE, + sepcAccess : CsrAccess = CsrAccess.NONE, + scauseAccess : CsrAccess = CsrAccess.NONE, + sbadaddrAccess : CsrAccess = CsrAccess.NONE, + scycleAccess : CsrAccess = CsrAccess.NONE, + sinstretAccess : CsrAccess = CsrAccess.NONE, + satpAccess : CsrAccess = CsrAccess.NONE, + utimeAccess :CsrAccess = CsrAccess.NONE, + medelegAccess : CsrAccess = CsrAccess.NONE, + midelegAccess : CsrAccess = CsrAccess.NONE, + withExternalMhartid : Boolean = false, + mhartidWidth : Int = 0, + pipelineCsrRead : Boolean = false, + pipelinedInterrupt : Boolean = true, + csrOhDecoder : Boolean = true, + deterministicInteruptionEntry : Boolean = false, //Only used for simulatation purposes + wfiOutput : Boolean = false + ){ + assert(!ucycleAccess.canWrite) + def privilegeGen = userGen || supervisorGen + def noException = this.copy(ecallGen = false, ebreakGen = false, catchIllegalAccess = false) + def noExceptionButEcall = this.copy(ecallGen = true, ebreakGen = false, catchIllegalAccess = false) +} + +object CsrPluginConfig{ + def all : CsrPluginConfig = all(0x00000020l) + def small : CsrPluginConfig = small(0x00000020l) + def smallest : CsrPluginConfig = smallest(0x00000020l) + + def openSbi(mhartid : Int, misa : Int) = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 0, + marchid = 0, + mimpid = 0, + mhartid = mhartid, + misaExtensionsInit = misa, + misaAccess = CsrAccess.READ_ONLY, + mtvecAccess = CsrAccess.READ_WRITE, //Could have been WRITE_ONLY :( + mtvecInit = null, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_ONLY, + mbadaddrAccess = CsrAccess.READ_ONLY, + mcycleAccess = CsrAccess.NONE, + minstretAccess = CsrAccess.NONE, + ucycleAccess = CsrAccess.NONE, + wfiGenAsWait = true, + ecallGen = true, + xtvecModeGen = false, + noCsrAlu = false, + wfiGenAsNop = false, + ebreakGen = true, + userGen = true, + supervisorGen = true, + sscratchGen = true, + stvecAccess = CsrAccess.READ_WRITE, + sepcAccess = CsrAccess.READ_WRITE, + scauseAccess = CsrAccess.READ_WRITE, + sbadaddrAccess = CsrAccess.READ_WRITE, + scycleAccess = CsrAccess.NONE, + sinstretAccess = CsrAccess.NONE, + satpAccess = CsrAccess.NONE, + medelegAccess = CsrAccess.READ_WRITE, //Could have been WRITE_ONLY :( + midelegAccess = CsrAccess.READ_WRITE, //Could have been WRITE_ONLY :( + pipelineCsrRead = false, + deterministicInteruptionEntry = false + ) + + def linuxMinimal(mtVecInit : BigInt) = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 1, + marchid = 2, + mimpid = 3, + mhartid = 0, + misaExtensionsInit = 0, //TODO + misaAccess = CsrAccess.NONE, //Read required by some regressions + mtvecAccess = CsrAccess.WRITE_ONLY, //Read required by some regressions + mtvecInit = mtVecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_ONLY, + mbadaddrAccess = CsrAccess.READ_ONLY, + mcycleAccess = CsrAccess.NONE, + minstretAccess = CsrAccess.NONE, + ucycleAccess = CsrAccess.NONE, + uinstretAccess = CsrAccess.NONE, + wfiGenAsWait = true, + ecallGen = true, + xtvecModeGen = false, + noCsrAlu = false, + wfiGenAsNop = false, + ebreakGen = true, + userGen = true, + supervisorGen = true, + sscratchGen = true, + stvecAccess = CsrAccess.READ_WRITE, + sepcAccess = CsrAccess.READ_WRITE, + scauseAccess = CsrAccess.READ_WRITE, + sbadaddrAccess = CsrAccess.READ_WRITE, + scycleAccess = CsrAccess.NONE, + sinstretAccess = CsrAccess.NONE, + satpAccess = CsrAccess.NONE, //Implemented into the MMU plugin + medelegAccess = CsrAccess.WRITE_ONLY, + midelegAccess = CsrAccess.WRITE_ONLY, + pipelineCsrRead = false, + deterministicInteruptionEntry = false + ) + + + def linuxFull(mtVecInit : BigInt) = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 1, + marchid = 2, + mimpid = 3, + mhartid = 0, + misaExtensionsInit = 0, //TODO + misaAccess = CsrAccess.READ_WRITE, + mtvecAccess = CsrAccess.READ_WRITE, + mtvecInit = mtVecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_WRITE, + mbadaddrAccess = CsrAccess.READ_WRITE, + mcycleAccess = CsrAccess.READ_WRITE, + minstretAccess = CsrAccess.READ_WRITE, + ucycleAccess = CsrAccess.READ_ONLY, + uinstretAccess = CsrAccess.READ_ONLY, + wfiGenAsWait = true, + ecallGen = true, + xtvecModeGen = false, + noCsrAlu = false, + wfiGenAsNop = false, + ebreakGen = false, + userGen = true, + supervisorGen = true, + sscratchGen = true, + stvecAccess = CsrAccess.READ_WRITE, + sepcAccess = CsrAccess.READ_WRITE, + scauseAccess = CsrAccess.READ_WRITE, + sbadaddrAccess = CsrAccess.READ_WRITE, + scycleAccess = CsrAccess.READ_WRITE, + sinstretAccess = CsrAccess.READ_WRITE, + satpAccess = CsrAccess.NONE, //Implemented into the MMU plugin + medelegAccess = CsrAccess.READ_WRITE, + midelegAccess = CsrAccess.READ_WRITE, + pipelineCsrRead = false, + deterministicInteruptionEntry = false + ) + + def all(mtvecInit : BigInt) : CsrPluginConfig = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 11, + marchid = 22, + mimpid = 33, + mhartid = 0, + misaExtensionsInit = 66, + misaAccess = CsrAccess.READ_WRITE, + mtvecAccess = CsrAccess.READ_WRITE, + mtvecInit = mtvecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_WRITE, + mbadaddrAccess = CsrAccess.READ_WRITE, + mcycleAccess = CsrAccess.READ_WRITE, + minstretAccess = CsrAccess.READ_WRITE, + ecallGen = true, + wfiGenAsWait = true, + ucycleAccess = CsrAccess.READ_ONLY, + uinstretAccess = CsrAccess.READ_ONLY + ) + + def all2(mtvecInit : BigInt) : CsrPluginConfig = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 11, + marchid = 22, + mimpid = 33, + mhartid = 0, + misaExtensionsInit = 66, + misaAccess = CsrAccess.READ_WRITE, + mtvecAccess = CsrAccess.READ_WRITE, + mtvecInit = mtvecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_WRITE, + mbadaddrAccess = CsrAccess.READ_WRITE, + mcycleAccess = CsrAccess.READ_WRITE, + minstretAccess = CsrAccess.READ_WRITE, + ecallGen = true, + wfiGenAsWait = true, + ucycleAccess = CsrAccess.READ_ONLY, + uinstretAccess = CsrAccess.READ_ONLY, + supervisorGen = true, + sscratchGen = true, + stvecAccess = CsrAccess.READ_WRITE, + sepcAccess = CsrAccess.READ_WRITE, + scauseAccess = CsrAccess.READ_WRITE, + sbadaddrAccess = CsrAccess.READ_WRITE, + scycleAccess = CsrAccess.READ_WRITE, + sinstretAccess = CsrAccess.READ_WRITE, + satpAccess = CsrAccess.READ_WRITE, + medelegAccess = CsrAccess.READ_WRITE, + midelegAccess = CsrAccess.READ_WRITE + ) + + def small(mtvecInit : BigInt) = CsrPluginConfig( + catchIllegalAccess = false, + mvendorid = null, + marchid = null, + mimpid = null, + mhartid = null, + misaExtensionsInit = 66, + misaAccess = CsrAccess.NONE, + mtvecAccess = CsrAccess.NONE, + mtvecInit = mtvecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = false, + mcauseAccess = CsrAccess.READ_ONLY, + mbadaddrAccess = CsrAccess.READ_ONLY, + mcycleAccess = CsrAccess.NONE, + minstretAccess = CsrAccess.NONE, + ecallGen = false, + wfiGenAsWait = false, + ucycleAccess = CsrAccess.NONE, + uinstretAccess = CsrAccess.NONE + ) + + def smallest(mtvecInit : BigInt) = CsrPluginConfig( + catchIllegalAccess = false, + mvendorid = null, + marchid = null, + mimpid = null, + mhartid = null, + misaExtensionsInit = 66, + misaAccess = CsrAccess.NONE, + mtvecAccess = CsrAccess.NONE, + mtvecInit = mtvecInit, + mepcAccess = CsrAccess.NONE, + mscratchGen = false, + mcauseAccess = CsrAccess.READ_ONLY, + mbadaddrAccess = CsrAccess.NONE, + mcycleAccess = CsrAccess.NONE, + minstretAccess = CsrAccess.NONE, + ecallGen = false, + wfiGenAsWait = false, + ucycleAccess = CsrAccess.NONE, + uinstretAccess = CsrAccess.NONE + ) + + def secure(mtvecInit : BigInt) = CsrPluginConfig( + catchIllegalAccess = true, + mvendorid = 1, + marchid = 2, + mimpid = 3, + mhartid = 0, + misaExtensionsInit = 0x101064, // RV32GCFMU + misaAccess = CsrAccess.READ_WRITE, + mtvecAccess = CsrAccess.READ_WRITE, + mtvecInit = mtvecInit, + mepcAccess = CsrAccess.READ_WRITE, + mscratchGen = true, + mcauseAccess = CsrAccess.READ_WRITE, + mbadaddrAccess = CsrAccess.READ_WRITE, + mcycleAccess = CsrAccess.READ_WRITE, + minstretAccess = CsrAccess.READ_WRITE, + ucycleAccess = CsrAccess.READ_ONLY, + uinstretAccess = CsrAccess.READ_ONLY, + wfiGenAsWait = true, + ecallGen = true, + userGen = true, + medelegAccess = CsrAccess.READ_WRITE, + midelegAccess = CsrAccess.READ_WRITE + ) + +} +case class CsrWrite(that : Data, bitOffset : Int) +case class CsrRead(that : Data , bitOffset : Int) +case class CsrReadToWriteOverride(that : Data, bitOffset : Int) //Used for special cases, as MIP where there shadow stuff +case class CsrOnWrite(doThat :() => Unit) +case class CsrDuringWrite(doThat :() => Unit) +case class CsrDuringRead(doThat :() => Unit) +case class CsrDuring(doThat :() => Unit) +case class CsrOnRead(doThat : () => Unit) + + +case class CsrMapping() extends Area with CsrInterface { + val mapping = mutable.LinkedHashMap[Int,ArrayBuffer[Any]]() + val always = ArrayBuffer[Any]() + val readDataSignal, readDataInit, writeDataSignal = Bits(32 bits) + val allowCsrSignal = False + val hazardFree = Bool() + + readDataSignal := readDataInit + def addMappingAt(address : Int,that : Any) = mapping.getOrElseUpdate(address,new ArrayBuffer[Any]) += that + override def r(csrAddress : Int, bitOffset : Int, that : Data): Unit = addMappingAt(csrAddress, CsrRead(that,bitOffset)) + override def w(csrAddress : Int, bitOffset : Int, that : Data): Unit = addMappingAt(csrAddress, CsrWrite(that,bitOffset)) + override def r2w(csrAddress : Int, bitOffset : Int, that : Data): Unit = addMappingAt(csrAddress, CsrReadToWriteOverride(that,bitOffset)) + override def onWrite(csrAddress: Int)(body: => Unit): Unit = addMappingAt(csrAddress, CsrOnWrite(() => body)) + override def duringWrite(csrAddress: Int)(body: => Unit): Unit = addMappingAt(csrAddress, CsrDuringWrite(() => body)) + override def duringRead(csrAddress: Int)(body: => Unit): Unit = addMappingAt(csrAddress, CsrDuringRead(() => body)) + override def during(csrAddress: Int)(body: => Unit): Unit = addMappingAt(csrAddress, CsrDuring(() => body)) + override def onRead(csrAddress: Int)(body: => Unit): Unit = addMappingAt(csrAddress, CsrOnRead(() => {body})) + override def duringAny(): Bool = ??? + override def duringAnyRead(body: => Unit) : Unit = always += CsrDuringRead(() => body) + override def duringAnyWrite(body: => Unit) : Unit = always += CsrDuringWrite(() => body) + override def onAnyRead(body: => Unit) : Unit = always += CsrOnRead(() => body) + override def onAnyWrite(body: => Unit) : Unit = always += CsrOnWrite(() => body) + override def readData() = readDataSignal + override def writeData() = writeDataSignal + override def allowCsr() = allowCsrSignal := True + override def isHazardFree() = hazardFree +} + + +trait CsrInterface{ + def onWrite(csrAddress : Int)(doThat : => Unit) : Unit + def onRead(csrAddress : Int)(doThat : => Unit) : Unit + def duringWrite(csrAddress: Int)(body: => Unit): Unit + def duringRead(csrAddress: Int)(body: => Unit): Unit + def during(csrAddress: Int)(body: => Unit): Unit + def duringAny(): Bool + def r(csrAddress : Int, bitOffset : Int, that : Data): Unit + def w(csrAddress : Int, bitOffset : Int, that : Data): Unit + def rw(csrAddress : Int, bitOffset : Int,that : Data): Unit ={ + r(csrAddress,bitOffset,that) + w(csrAddress,bitOffset,that) + } + def duringAnyRead(body: => Unit) : Unit //Called all the durration of a Csr write instruction in the execute stage + def duringAnyWrite(body: => Unit) : Unit //same than above for read + def onAnyRead(body: => Unit) : Unit + def onAnyWrite(body: => Unit) : Unit + def allowCsr() : Unit //In case your csr do not use the regular API with csrAddress but is implemented using "side channels", you can call that if the current csr is implemented + def isHazardFree() : Bool // You should not have any side effect nor use readData() until this return True + + def r2w(csrAddress : Int, bitOffset : Int,that : Data): Unit + + def rw(csrAddress : Int, thats : (Int, Data)*) : Unit = for(that <- thats) rw(csrAddress,that._1, that._2) + def w(csrAddress : Int, thats : (Int, Data)*) : Unit = for(that <- thats) w(csrAddress,that._1, that._2) + def r(csrAddress : Int, thats : (Int, Data)*) : Unit = for(that <- thats) r(csrAddress,that._1, that._2) + def rw[T <: Data](csrAddress : Int, that : T): Unit = rw(csrAddress,0,that) + def w[T <: Data](csrAddress : Int, that : T): Unit = w(csrAddress,0,that) + def r [T <: Data](csrAddress : Int, that : T): Unit = r(csrAddress,0,that) + def isWriting(csrAddress : Int) : Bool = { + val ret = False + onWrite(csrAddress){ + ret := True + } + ret + } + + def isReading(csrAddress : Int) : Bool = { + val ret = False + onRead(csrAddress){ + ret := True + } + ret + } + + def readData() : Bits //Return the 32 bits internal signal of the CsrPlugin for you to override (if you want) + def writeData() : Bits //Return the 32 bits value that the CsrPlugin want to write in the CSR (depend on readData combinatorialy) +} + + +trait IContextSwitching{ + def isContextSwitching : Bool +} +trait IWake{ + def askWake() : Unit +} + +class CsrPlugin(val config: CsrPluginConfig) extends Plugin[VexRiscv] with ExceptionService with PrivilegeService with InterruptionInhibitor with ExceptionInhibitor with IContextSwitching with CsrInterface with IWake{ + import config._ + import CsrAccess._ + + assert(!(wfiGenAsNop && wfiGenAsWait)) + + def xlen = 32 + + //Mannage ExceptionService calls + val exceptionPortsInfos = ArrayBuffer[ExceptionPortInfo]() + override def newExceptionPort(stage : Stage, priority : Int = 0, codeWidth : Int = 4) = { + val interface = Flow(ExceptionCause(codeWidth)) + exceptionPortsInfos += ExceptionPortInfo(interface,stage,priority,codeWidth) + interface + } + + + + var exceptionPendings : Vec[Bool] = null + override def isExceptionPending(stage : Stage): Bool = exceptionPendings(pipeline.stages.indexOf(stage)) + + var redoInterface : Flow[UInt] = null + var jumpInterface : Flow[UInt] = null + var timerInterrupt, externalInterrupt, softwareInterrupt : Bool = null + var externalInterruptS : Bool = null + var forceMachineWire : Bool = null + var privilege : UInt = null + var selfException : Flow[ExceptionCause] = null + var contextSwitching : Bool = null + var thirdPartyWake : Bool = null + var inWfi : Bool = null + var externalMhartId : UInt = null + var utime : UInt = null + + override def askWake(): Unit = thirdPartyWake := True + + override def isContextSwitching = contextSwitching + + object EnvCtrlEnum extends SpinalEnum(binarySequential){ + val NONE, XRET = newElement() + val WFI = if(wfiGenAsWait) newElement() else null + val ECALL = if(ecallGen) newElement() else null + val EBREAK = if(ebreakGen) newElement() else null + } + + object ENV_CTRL extends Stageable(EnvCtrlEnum()) + object IS_CSR extends Stageable(Bool) + object IS_SFENCE_VMA extends Stageable(Bool) + object CSR_WRITE_OPCODE extends Stageable(Bool) + object CSR_READ_OPCODE extends Stageable(Bool) + object PIPELINED_CSR_READ extends Stageable(Bits(32 bits)) + + var allowInterrupts : Bool = null + var allowException : Bool = null + var allowEbreakException : Bool = null + + var csrMapping : CsrMapping = null + + //Print CSR mapping + def printCsr() { + for ((address, things) <- csrMapping.mapping) { + println("0x" + address.toHexString + " => ") + for (thing <- things) { + println(" - " + thing) + } + } + } + + + //Interruption and exception data model + case class Delegator(var enable : Bool, privilege : Int) + case class InterruptSpec(var cond : Bool, id : Int, privilege : Int, delegators : List[Delegator]) + case class ExceptionSpec(id : Int, delegators : List[Delegator]) + var interruptSpecs = ArrayBuffer[InterruptSpec]() + var exceptionSpecs = ArrayBuffer[ExceptionSpec]() + + def addInterrupt(cond : Bool, id : Int, privilege : Int, delegators : List[Delegator]): Unit = { + interruptSpecs += InterruptSpec(cond, id, privilege, delegators) + } + + override def r(csrAddress: Int, bitOffset: Int, that: Data): Unit = csrMapping.r(csrAddress, bitOffset, that) + override def w(csrAddress: Int, bitOffset: Int, that: Data): Unit = csrMapping.w(csrAddress, bitOffset, that) + override def r2w(csrAddress: Int, bitOffset: Int, that: Data): Unit = csrMapping.r2w(csrAddress, bitOffset, that) + override def onWrite(csrAddress: Int)(body: => Unit): Unit = csrMapping.onWrite(csrAddress)(body) + override def duringWrite(csrAddress: Int)(body: => Unit): Unit = csrMapping.duringWrite(csrAddress)(body) + override def onRead(csrAddress: Int)(body: => Unit): Unit = csrMapping.onRead(csrAddress)(body) + override def duringRead(csrAddress: Int)(body: => Unit): Unit = csrMapping.duringRead(csrAddress)(body) + override def during(csrAddress: Int)(body: => Unit): Unit = csrMapping.during(csrAddress)(body) + override def duringAny(): Bool = pipeline.execute.arbitration.isValid && pipeline.execute.input(IS_CSR) + override def duringAnyRead(body: => Unit) = csrMapping.duringAnyRead(body) + override def duringAnyWrite(body: => Unit) = csrMapping.duringAnyWrite(body) + override def onAnyRead(body: => Unit) = csrMapping.onAnyRead(body) + override def onAnyWrite(body: => Unit) = csrMapping.onAnyWrite(body) + override def allowCsr() = csrMapping.allowCsr() + override def readData() = csrMapping.readData() + override def writeData() = csrMapping.writeData() + override def isHazardFree() = csrMapping.isHazardFree() + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + + if(!config.ebreakGen) { + SpinalWarning("This VexRiscv configuration is set without software ebreak instruction support. Some software may rely on it (ex: Rust). (This isn't related to JTAG ebreak)") + } + + csrMapping = new CsrMapping() + + inWfi = False.addTag(Verilator.public) + + thirdPartyWake = False + + val defaultEnv = List[(Stageable[_ <: BaseType],Any)]( + ) + + val defaultCsrActions = List[(Stageable[_ <: BaseType],Any)]( + IS_CSR -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> True + ) ++ (if(catchIllegalAccess) List(HAS_SIDE_EFFECT -> True) else Nil) + + val nonImmediatActions = defaultCsrActions ++ List( + SRC1_CTRL -> Src1CtrlEnum.RS, + RS1_USE -> True + ) + + val immediatActions = defaultCsrActions ++ List( + SRC1_CTRL -> Src1CtrlEnum.URS1 + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + + decoderService.addDefault(ENV_CTRL, EnvCtrlEnum.NONE) + decoderService.addDefault(IS_CSR, False) + decoderService.add(List( + CSRRW -> nonImmediatActions, + CSRRS -> nonImmediatActions, + CSRRC -> nonImmediatActions, + CSRRWI -> immediatActions, + CSRRSI -> immediatActions, + CSRRCI -> immediatActions, + MRET -> (defaultEnv ++ List(ENV_CTRL -> EnvCtrlEnum.XRET, HAS_SIDE_EFFECT -> True)), + SRET -> (defaultEnv ++ List(ENV_CTRL -> EnvCtrlEnum.XRET, HAS_SIDE_EFFECT -> True)) + )) + if(wfiGenAsWait) decoderService.add(WFI, defaultEnv ++ List(ENV_CTRL -> EnvCtrlEnum.WFI)) + if(wfiGenAsNop) decoderService.add(WFI, Nil) + if(ecallGen) decoderService.add(ECALL, defaultEnv ++ List(ENV_CTRL -> EnvCtrlEnum.ECALL, HAS_SIDE_EFFECT -> True)) + if(ebreakGen) decoderService.add(EBREAK, defaultEnv ++ List(ENV_CTRL -> EnvCtrlEnum.EBREAK, HAS_SIDE_EFFECT -> True)) + + val pcManagerService = pipeline.service(classOf[JumpService]) + jumpInterface = pcManagerService.createJumpInterface(pipeline.stages.last) + jumpInterface.valid := False + jumpInterface.payload.assignDontCare() + + + if(supervisorGen) { + redoInterface = pcManagerService.createJumpInterface(pipeline.execute, -20) //Should lose against dynamic_target branch prediction correction + } + + exceptionPendings = Vec(Bool, pipeline.stages.length) + timerInterrupt = in Bool() setName("timerInterrupt") + externalInterrupt = in Bool() setName("externalInterrupt") + softwareInterrupt = in Bool() setName("softwareInterrupt") default(False) + if(supervisorGen){ +// timerInterruptS = in Bool() setName("timerInterruptS") + externalInterruptS = in Bool() setName("externalInterruptS") + } + contextSwitching = Bool().setName("contextSwitching") + + privilege = UInt(2 bits).setName("CsrPlugin_privilege") + forceMachineWire = False + + if(catchIllegalAccess || ecallGen || ebreakGen) + selfException = newExceptionPort(pipeline.execute) + + allowInterrupts = True + allowException = True + allowEbreakException = True + + for (i <- interruptSpecs) i.cond = i.cond.pull() + + + pipeline.update(MPP, UInt(2 bits)) + + if(withExternalMhartid) externalMhartId = in UInt(mhartidWidth bits) + if(utimeAccess != CsrAccess.NONE) utime = in UInt(64 bits) setName("utime") + + if(supervisorGen) { + decoderService.addDefault(IS_SFENCE_VMA, False) + decoderService.add(SFENCE_VMA, List(IS_SFENCE_VMA -> True)) + } + } + + def inhibateInterrupts() : Unit = allowInterrupts := False + def inhibateException() : Unit = allowException := False + def inhibateEbreakException() : Unit = allowEbreakException := False + + override def isUser() : Bool = privilege === 0 + override def isSupervisor(): Bool = privilege === 1 + override def isMachine(): Bool = privilege === 3 + override def forceMachine(): Unit = forceMachineWire := True + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + val fetcher = service(classOf[IBusFetcher]) + val trapCodeWidth = log2Up((List(16) ++ interruptSpecs.map(_.id + 1) ++ exceptionPortsInfos.map(p => 1 << widthOf(p.port.code))).max) + + //Define CSR mapping utilities + implicit class CsrAccessPimper(csrAccess : CsrAccess){ + def apply(csrAddress : Int, thats : (Int, Data)*) : Unit = { + if(csrAccess == `WRITE_ONLY` || csrAccess == `READ_WRITE`) for(that <- thats) csrMapping.w(csrAddress,that._1, that._2) + if(csrAccess == `READ_ONLY` || csrAccess == `READ_WRITE`) for(that <- thats) csrMapping.r(csrAddress,that._1, that._2) + } + def apply(csrAddress : Int, that : Data) : Unit = { + if(csrAccess == `WRITE_ONLY` || csrAccess == `READ_WRITE`) csrMapping.w(csrAddress, 0, that) + if(csrAccess == `READ_ONLY` || csrAccess == `READ_WRITE`) csrMapping.r(csrAddress, 0, that) + } + } + + + case class Xtvec() extends Bundle { + val mode = Bits(2 bits) + val base = UInt(xlen-2 bits) + } + + val privilegeReg = privilegeGen generate RegInit(U"11") + privilege := (if(privilegeGen) privilegeReg else U"11") + + when(forceMachineWire) { privilege := 3 } + + val machineCsr = pipeline plug new Area{ + //Define CSR registers + // Status => MXR, SUM, TVM, TW, TSE ? + val misa = new Area{ + val base = Reg(UInt(2 bits)) init(U"01") allowUnsetRegToAvoidLatch + val extensions = Reg(Bits(26 bits)) init(misaExtensionsInit) allowUnsetRegToAvoidLatch + } + + val mtvec = Reg(Xtvec()).allowUnsetRegToAvoidLatch + + if(mtvecInit != null) mtvec.mode init(mtvecInit & 0x3) + if(mtvecInit != null) mtvec.base init(mtvecInit / 4) + val mepc = Reg(UInt(xlen bits)) + val mstatus = new Area{ + val MIE, MPIE = RegInit(False) + val MPP = RegInit(U"11") + } + val mip = new Area{ + val MEIP = RegNext(externalInterrupt) + val MTIP = RegNext(timerInterrupt) + val MSIP = RegNext(softwareInterrupt) + } + val mie = new Area{ + val MEIE, MTIE, MSIE = RegInit(False) + } + val mscratch = if(mscratchGen) Reg(Bits(xlen bits)) else null + val mcause = new Area{ + val interrupt = Reg(Bool) + val exceptionCode = Reg(UInt(trapCodeWidth bits)) + } + val mtval = Reg(UInt(xlen bits)) + val mcycle = Reg(UInt(64 bits)) init(0) + val minstret = Reg(UInt(64 bits)) init(0) + + + val medeleg = supervisorGen generate new Area { + val IAM, IAF, II, LAM, LAF, SAM, SAF, EU, ES, IPF, LPF, SPF = RegInit(False) + val mapping = mutable.LinkedHashMap(0 -> IAM, 1 -> IAF, 2 -> II, 4 -> LAM, 5 -> LAF, 6 -> SAM, 7 -> SAF, 8 -> EU, 9 -> ES, 12 -> IPF, 13 -> LPF, 15 -> SPF) + } + val mideleg = supervisorGen generate new Area { + val ST, SE, SS = RegInit(False) + } + + if(mvendorid != null) READ_ONLY(CSR.MVENDORID, U(mvendorid)) + if(marchid != null) READ_ONLY(CSR.MARCHID , U(marchid )) + if(mimpid != null) READ_ONLY(CSR.MIMPID , U(mimpid )) + if(mhartid != null && !withExternalMhartid) READ_ONLY(CSR.MHARTID , U(mhartid )) + if(withExternalMhartid) READ_ONLY(CSR.MHARTID , externalMhartId) + misaAccess(CSR.MISA, xlen-2 -> misa.base , 0 -> misa.extensions) + + //Machine CSR + READ_WRITE(CSR.MSTATUS, 7 -> mstatus.MPIE, 3 -> mstatus.MIE) + READ_ONLY(CSR.MIP, 11 -> mip.MEIP, 7 -> mip.MTIP) + READ_WRITE(CSR.MIP, 3 -> mip.MSIP) + READ_WRITE(CSR.MIE, 11 -> mie.MEIE, 7 -> mie.MTIE, 3 -> mie.MSIE) + + r(CSR.MSTATUS, 11 -> mstatus.MPP) + onWrite(CSR.MSTATUS){ + switch(writeData()(12 downto 11)){ + is(3){ mstatus.MPP := 3 } + if(supervisorGen) is(1){ mstatus.MPP := 1 } + if(userGen) is(0){ mstatus.MPP := 0 } + } + } + + mtvecAccess(CSR.MTVEC, 2 -> mtvec.base, 0 -> mtvec.mode) + mepcAccess(CSR.MEPC, mepc) + if(mscratchGen) READ_WRITE(CSR.MSCRATCH, mscratch) + mcauseAccess(CSR.MCAUSE, xlen-1 -> mcause.interrupt, 0 -> mcause.exceptionCode) + mbadaddrAccess(CSR.MBADADDR, mtval) + mcycleAccess(CSR.MCYCLE, mcycle(31 downto 0)) + mcycleAccess(CSR.MCYCLEH, mcycle(63 downto 32)) + minstretAccess(CSR.MINSTRET, minstret(31 downto 0)) + minstretAccess(CSR.MINSTRETH, minstret(63 downto 32)) + + if(supervisorGen) { + for((id, enable) <- medeleg.mapping) medelegAccess(CSR.MEDELEG, id -> enable) + midelegAccess(CSR.MIDELEG, 9 -> mideleg.SE, 5 -> mideleg.ST, 1 -> mideleg.SS) + } + + //User CSR + ucycleAccess(CSR.UCYCLE, mcycle(31 downto 0)) + ucycleAccess(CSR.UCYCLEH, mcycle(63 downto 32)) + uinstretAccess(CSR.UINSTRET, minstret(31 downto 0)) + uinstretAccess(CSR.UINSTRETH, minstret(63 downto 32)) + + if(utimeAccess != CsrAccess.NONE) { + utimeAccess(CSR.UTIME, utime(31 downto 0)) + utimeAccess(CSR.UTIMEH, utime(63 downto 32)) + } + + pipeline(MPP) := mstatus.MPP + } + + val supervisorCsr = ifGen(supervisorGen) { + pipeline plug new Area { + val sstatus = new Area { + val SIE, SPIE = RegInit(False) + val SPP = RegInit(U"1") + } + + val sip = new Area { + val SEIP_SOFT = RegInit(False) + val SEIP_INPUT = RegNext(externalInterruptS) + val SEIP_OR = SEIP_SOFT || SEIP_INPUT + val STIP = RegInit(False) + val SSIP = RegInit(False) + } + val sie = new Area { + val SEIE, STIE, SSIE = RegInit(False) + } + val stvec = Reg(Xtvec()).allowUnsetRegToAvoidLatch + val sscratch = if (sscratchGen) Reg(Bits(xlen bits)) else null + + val scause = new Area { + val interrupt = Reg(Bool) + val exceptionCode = Reg(UInt(trapCodeWidth bits)) + } + val stval = Reg(UInt(xlen bits)) + val sepc = Reg(UInt(xlen bits)) + val satp = new Area { + val PPN = Reg(Bits(22 bits)) + val ASID = Reg(Bits(9 bits)) + val MODE = Reg(Bits(1 bits)) + } + + //Supervisor CSR + for(offset <- List(CSR.MSTATUS, CSR.SSTATUS)) READ_WRITE(offset,8 -> sstatus.SPP, 5 -> sstatus.SPIE, 1 -> sstatus.SIE) + for(offset <- List(CSR.MIP, CSR.SIP)) { + READ_WRITE(offset, 5 -> sip.STIP, 1 -> sip.SSIP) + READ_ONLY(offset, 9 -> sip.SEIP_OR) + WRITE_ONLY(offset, 9 -> sip.SEIP_SOFT) + r2w(offset, 9, sip.SEIP_SOFT) + } + + for(offset <- List(CSR.MIE, CSR.SIE)) READ_WRITE(offset, 9 -> sie.SEIE, 5 -> sie.STIE, 1 -> sie.SSIE) + + + stvecAccess(CSR.STVEC, 2 -> stvec.base, 0 -> stvec.mode) + sepcAccess(CSR.SEPC, sepc) + if(sscratchGen) READ_WRITE(CSR.SSCRATCH, sscratch) + scauseAccess(CSR.SCAUSE, xlen-1 -> scause.interrupt, 0 -> scause.exceptionCode) + sbadaddrAccess(CSR.SBADADDR, stval) + satpAccess(CSR.SATP, 31 -> satp.MODE, 22 -> satp.ASID, 0 -> satp.PPN) + + + val rescheduleLogic = supervisorGen generate new Area { + redoInterface.valid := False + redoInterface.payload := decode.input(PC) + + val rescheduleNext = False + when(execute.arbitration.isValid && execute.input(IS_SFENCE_VMA)) { rescheduleNext := True } + duringWrite(CSR.SATP) { rescheduleNext := True } + + when(rescheduleNext){ + redoInterface.valid := True + execute.arbitration.flushNext := True + decode.arbitration.haltByOther := True + } + } + } + } + + + + pipeline plug new Area{ + import machineCsr._ + import supervisorCsr._ + + val lastStage = pipeline.stages.last + val beforeLastStage = pipeline.stages(pipeline.stages.size-2) + val stagesFromExecute = pipeline.stages.dropWhile(_ != execute) + + //Manage counters + mcycle := mcycle + 1 + when(lastStage.arbitration.isFiring) { + minstret := minstret + 1 + } + + + if(supervisorGen) { + addInterrupt(sip.STIP && sie.STIE, id = 5, privilege = 1, delegators = List(Delegator(mideleg.ST, 3))) + addInterrupt(sip.SSIP && sie.SSIE, id = 1, privilege = 1, delegators = List(Delegator(mideleg.SS, 3))) + addInterrupt(sip.SEIP_OR && sie.SEIE, id = 9, privilege = 1, delegators = List(Delegator(mideleg.SE, 3))) + + for((id, enable) <- medeleg.mapping) exceptionSpecs += ExceptionSpec(id, List(Delegator(enable, 3))) + } + + addInterrupt(mip.MTIP && mie.MTIE, id = 7, privilege = 3, delegators = Nil) + addInterrupt(mip.MSIP && mie.MSIE, id = 3, privilege = 3, delegators = Nil) + addInterrupt(mip.MEIP && mie.MEIE, id = 11, privilege = 3, delegators = Nil) + + + val mepcCaptureStage = if(exceptionPortsInfos.nonEmpty) lastStage else decode + + + //Aggregate all exception port and remove required instructions + val exceptionPortCtrl = exceptionPortsInfos.nonEmpty generate new Area{ + val codeWidth = exceptionPortsInfos.map(_.codeWidth).max + val firstStageIndexWithExceptionPort = exceptionPortsInfos.map(i => indexOf(i.stage)).min + val exceptionValids = Vec(stages.map(s => Bool().setPartialName(s.getName()))) + val exceptionValidsRegs = Vec(stages.map(s => Reg(Bool).init(False).setPartialName(s.getName()))).allowUnsetRegToAvoidLatch + val exceptionContext = Reg(ExceptionCause(codeWidth)) + val exceptionTargetPrivilegeUncapped = U"11" + + switch(exceptionContext.code){ + for(s <- exceptionSpecs){ + is(s.id){ + var exceptionPrivilegs = if (supervisorGen) List(1, 3) else List(3) + while(exceptionPrivilegs.length != 1){ + val p = exceptionPrivilegs.head + if (exceptionPrivilegs.tail.forall(e => s.delegators.exists(_.privilege == e))) { + val delegUpOn = s.delegators.filter(_.privilege > p).map(_.enable).fold(True)(_ && _) + val delegDownOff = !s.delegators.filter(_.privilege <= p).map(_.enable).orR + when(delegUpOn && delegDownOff) { + exceptionTargetPrivilegeUncapped := p + } + } + exceptionPrivilegs = exceptionPrivilegs.tail + } + } + } + } + val exceptionTargetPrivilege = privilege.max(exceptionTargetPrivilegeUncapped) + + val groupedByStage = exceptionPortsInfos.map(_.stage).distinct.map(s => { + val stagePortsInfos = exceptionPortsInfos.filter(_.stage == s).sortWith(_.priority > _.priority) + val stagePort = stagePortsInfos.length match{ + case 1 => { + stagePortsInfos.head.port.translateWith(stagePortsInfos.head.port.payload.resizeCode(codeWidth)) + } + case _ => { + val groupedPort = Flow(ExceptionCause(codeWidth)) + val valids = stagePortsInfos.map(_.port.valid) + val codes = stagePortsInfos.map(_.port.payload.resizeCode(codeWidth)) + groupedPort.valid := valids.orR + groupedPort.payload := MuxOH(OHMasking.first(stagePortsInfos.map(_.port.valid).asBits), codes) + groupedPort + } + } + ExceptionPortInfo(stagePort,s,0, codeWidth) + }) + + val sortedByStage = groupedByStage.sortWith((a, b) => pipeline.indexOf(a.stage) < pipeline.indexOf(b.stage)) +// sortedByStage.zipWithIndex.foreach(e => e._1.port.setName(e._1.stage.getName() + "_exception_agregat")) + exceptionValids := exceptionValidsRegs + for(portInfo <- sortedByStage; port = portInfo.port ; stage = portInfo.stage; stageId = indexOf(portInfo.stage)) { + when(port.valid) { + stage.arbitration.flushNext := True + stage.arbitration.removeIt := True + exceptionValids(stageId) := True + exceptionContext := port.payload + } + } + + for(stageId <- firstStageIndexWithExceptionPort until stages.length; stage = stages(stageId) ){ + val previousStage = if(stageId == firstStageIndexWithExceptionPort) stage else stages(stageId-1) + when(!stage.arbitration.isStuck){ + exceptionValidsRegs(stageId) := (if(stageId != firstStageIndexWithExceptionPort) exceptionValids(stageId-1) && !previousStage.arbitration.isStuck else False) + }otherwise{ + if(stage != stages.last) + exceptionValidsRegs(stageId) := exceptionValids(stageId) + else + exceptionValidsRegs(stageId) := False + } + when(stage.arbitration.isFlushed){ + exceptionValids(stageId) := False + } + } + + when(exceptionValids.orR){ + fetcher.haltIt() + } + + //Avoid the PC register of the last stage to change durring an exception handleing (Used to fill Xepc) + stages.last.dontSample.getOrElseUpdate(PC, ArrayBuffer[Bool]()) += exceptionValids.last + exceptionPendings := exceptionValidsRegs + } + + + + + + //Process interrupt request, code and privilege + val interrupt = new Area { + val valid = if(pipelinedInterrupt) RegNext(False) init(False) else False + val code = if(pipelinedInterrupt) Reg(UInt(trapCodeWidth bits)) else UInt(trapCodeWidth bits).assignDontCare() + var privilegs = if (supervisorGen) List(1, 3) else List(3) + val targetPrivilege = if(pipelinedInterrupt) Reg(UInt(2 bits)) else UInt(2 bits).assignDontCare() + val privilegeAllowInterrupts = mutable.LinkedHashMap[Int, Bool]() + if (supervisorGen) privilegeAllowInterrupts += 1 -> ((sstatus.SIE && privilege === U"01") || privilege < U"01") + privilegeAllowInterrupts += 3 -> (mstatus.MIE || privilege < U"11") + while (privilegs.nonEmpty) { + val p = privilegs.head + when(privilegeAllowInterrupts(p)) { + for (i <- interruptSpecs + if i.privilege <= p //EX : Machine timer interrupt can't go into supervisor mode + if privilegs.tail.forall(e => i.delegators.exists(_.privilege == e))) { // EX : Supervisor timer need to have machine mode delegator + val delegUpOn = i.delegators.filter(_.privilege > p).map(_.enable).fold(True)(_ && _) + val delegDownOff = !i.delegators.filter(_.privilege <= p).map(_.enable).orR + when(i.cond && delegUpOn && delegDownOff) { + valid := True + code := i.id + targetPrivilege := p + } + } + } + privilegs = privilegs.tail + } + + code.addTag(Verilator.public) + } + + + + + val exception = if(exceptionPortCtrl != null) exceptionPortCtrl.exceptionValids.last && allowException else False + val lastStageWasWfi = if(wfiGenAsWait) RegNext(lastStage.arbitration.isFiring && lastStage.input(ENV_CTRL) === EnvCtrlEnum.WFI) init(False) else False + + + + //Used to make the pipeline empty softly (for interrupts) + val pipelineLiberator = new Area{ + val pcValids = Vec(RegInit(False), stagesFromExecute.length) + val active = interrupt.valid && allowInterrupts && decode.arbitration.isValid + when(active){ + decode.arbitration.haltByOther := True + for((stage, reg, previous) <- (stagesFromExecute, pcValids, True :: pcValids.toList).zipped){ + when(!stage.arbitration.isStuck){ + reg := previous + } + } + } + when(!active || decode.arbitration.isRemoved) { + pcValids.foreach(_ := False) + } + +// val pcValids = for(stage <- stagesFromExecute) yield RegInit(False) clearWhen(!started) setWhen(!stage.arbitration.isValid) + val done = CombInit(pcValids.last) + if(exceptionPortCtrl != null) done.clearWhen(exceptionPortCtrl.exceptionValidsRegs.tail.orR) + } + + //Interrupt/Exception entry logic + val interruptJump = Bool.addTag(Verilator.public) + interruptJump := interrupt.valid && pipelineLiberator.done && allowInterrupts + if(pipelinedInterrupt) interrupt.valid clearWhen(interruptJump) //avoid double fireing + + val hadException = RegNext(exception) init(False) addTag(Verilator.public) + pipelineLiberator.done.clearWhen(hadException) + + + val targetPrivilege = CombInit(interrupt.targetPrivilege) + if(exceptionPortCtrl != null) when(hadException) { + targetPrivilege := exceptionPortCtrl.exceptionTargetPrivilege + } + + val trapCause = CombInit(interrupt.code.resize(trapCodeWidth)) + if(exceptionPortCtrl != null) when( hadException){ + trapCause := exceptionPortCtrl.exceptionContext.code.resized + } + + val xtvec = Xtvec().assignDontCare() + switch(targetPrivilege){ + if(supervisorGen) is(1) { xtvec := supervisorCsr.stvec } + is(3){ xtvec := machineCsr.mtvec } + } + + when(hadException || interruptJump){ + fetcher.haltIt() //Avoid having the fetch confused by the incomming privilege switch + + jumpInterface.valid := True + jumpInterface.payload := (if(!xtvecModeGen) xtvec.base @@ U"00" else (xtvec.mode === 0 || hadException) ? (xtvec.base @@ U"00") | ((xtvec.base + trapCause) @@ U"00") ) + lastStage.arbitration.flushNext := True + + if(privilegeGen) privilegeReg := targetPrivilege + + switch(targetPrivilege){ + if(supervisorGen) is(1) { + sstatus.SIE := False + sstatus.SPIE := sstatus.SIE + sstatus.SPP := privilege(0 downto 0) + scause.interrupt := !hadException + scause.exceptionCode := trapCause + sepc := mepcCaptureStage.input(PC) + if (exceptionPortCtrl != null) when(hadException){ + stval := exceptionPortCtrl.exceptionContext.badAddr + } + } + + is(3){ + mstatus.MIE := False + mstatus.MPIE := mstatus.MIE + mstatus.MPP := privilege + mcause.interrupt := !hadException + mcause.exceptionCode := trapCause + mepc := mepcCaptureStage.input(PC) + if(exceptionPortCtrl != null) when(hadException){ + mtval := exceptionPortCtrl.exceptionContext.badAddr + } + } + } + } + + if(exceptionPortCtrl == null){ + if(mbadaddrAccess == CsrAccess.READ_ONLY) mtval := 0 + if(sbadaddrAccess == CsrAccess.READ_ONLY) stval := 0 + } + + lastStage plug new Area{ + import lastStage._ + + //Manage MRET / SRET instructions + when(arbitration.isValid && input(ENV_CTRL) === EnvCtrlEnum.XRET) { + fetcher.haltIt() + jumpInterface.valid := True + lastStage.arbitration.flushNext := True + switch(input(INSTRUCTION)(29 downto 28)){ + is(3){ + mstatus.MPP := U"00" + mstatus.MIE := mstatus.MPIE + mstatus.MPIE := True + jumpInterface.payload := mepc + if(privilegeGen) privilegeReg := mstatus.MPP + } + if(supervisorGen) is(1){ + sstatus.SPP := U"0" + sstatus.SIE := sstatus.SPIE + sstatus.SPIE := True + jumpInterface.payload := sepc + if(privilegeGen) privilegeReg := U"0" @@ sstatus.SPP + } + } + } + } + + + contextSwitching := jumpInterface.valid + + //CSR read/write instructions management + decode plug new Area{ + import decode._ + + val imm = IMM(input(INSTRUCTION)) + insert(CSR_WRITE_OPCODE) := ! ( + (input(INSTRUCTION)(14 downto 13) === B"01" && input(INSTRUCTION)(rs1Range) === 0) + || (input(INSTRUCTION)(14 downto 13) === B"11" && imm.z === 0) + ) + insert(CSR_READ_OPCODE) := input(INSTRUCTION)(13 downto 7) =/= B"0100000" + } + + + execute plug new Area{ + import execute._ + //Manage WFI instructions + if(wfiOutput) out(inWfi) + val wfiWake = RegNext(interruptSpecs.map(_.cond).orR || thirdPartyWake) init(False) + if(wfiGenAsWait) when(arbitration.isValid && input(ENV_CTRL) === EnvCtrlEnum.WFI){ + inWfi := True + when(!wfiWake){ + arbitration.haltItself := True + } + } + } + + decode.arbitration.haltByOther setWhen(stagesFromExecute.map(s => s.arbitration.isValid && s.input(ENV_CTRL) === EnvCtrlEnum.XRET).asBits.orR) + + execute plug new Area { + import execute._ + def previousStage = decode + val blockedBySideEffects = stagesFromExecute.tail.map(s => s.arbitration.isValid).asBits().orR || pipeline.service(classOf[HazardService]).hazardOnExecuteRS// && s.input(HAS_SIDE_EFFECT) to improve be less pessimistic + + val illegalAccess = True + val illegalInstruction = False + if(selfException != null) { + selfException.valid := False + selfException.code.assignDontCare() + selfException.badAddr := input(INSTRUCTION).asUInt + if(catchIllegalAccess) when(illegalAccess || illegalInstruction){ + selfException.valid := True + selfException.code := 2 + } + } + + //Manage MRET / SRET instructions + when(arbitration.isValid && input(ENV_CTRL) === EnvCtrlEnum.XRET) { + when(input(INSTRUCTION)(29 downto 28).asUInt > privilege) { + illegalInstruction := True + } + } + + + //Manage ECALL instructions + if(ecallGen) when(arbitration.isValid && input(ENV_CTRL) === EnvCtrlEnum.ECALL){ + selfException.valid := True + switch(privilege) { + is(0) { selfException.code := 8 } + if(supervisorGen) is(1) { selfException.code := 9 } + default { selfException.code := 11 } + } + } + + + if(ebreakGen) when(arbitration.isValid && input(ENV_CTRL) === EnvCtrlEnum.EBREAK && allowEbreakException){ + selfException.valid := True + selfException.code := 3 + } + + + val imm = IMM(input(INSTRUCTION)) + def writeSrc = input(SRC1) + def readData = csrMapping.readDataSignal + def writeData = csrMapping.writeDataSignal + val writeInstruction = arbitration.isValid && input(IS_CSR) && input(CSR_WRITE_OPCODE) + val readInstruction = arbitration.isValid && input(IS_CSR) && input(CSR_READ_OPCODE) + val writeEnable = writeInstruction && !arbitration.isStuck + val readEnable = readInstruction && !arbitration.isStuck + csrMapping.hazardFree := !blockedBySideEffects + + val readToWriteData = CombInit(readData) + writeData := (if(noCsrAlu) writeSrc else input(INSTRUCTION)(13).mux( + False -> writeSrc, + True -> Mux(input(INSTRUCTION)(12), readToWriteData & ~writeSrc, readToWriteData | writeSrc) + )) + + when(arbitration.isValid && input(IS_CSR)) { + if(!pipelineCsrRead) output(REGFILE_WRITE_DATA) := readData + } + + when(arbitration.isValid && (input(IS_CSR) || (if(supervisorGen) input(IS_SFENCE_VMA) else False))) { + arbitration.haltItself setWhen(blockedBySideEffects) + } + + if(pipelineCsrRead){ + insert(PIPELINED_CSR_READ) := readData + when(memory.arbitration.isValid && memory.input(IS_CSR)) { + memory.output(REGFILE_WRITE_DATA) := memory.input(PIPELINED_CSR_READ) + } + } +// +// Component.current.rework{ +// when(arbitration.isFiring && input(IS_CSR)) { +// memory.input(REGFILE_WRITE_DATA).getDrivingReg := readData +// } +// } + + //Translation of the csrMapping into real logic + val csrAddress = input(INSTRUCTION)(csrRange) + Component.current.afterElaboration{ + def doJobs(jobs : ArrayBuffer[Any]): Unit ={ + val withWrite = jobs.exists(j => j.isInstanceOf[CsrWrite] || j.isInstanceOf[CsrOnWrite] || j.isInstanceOf[CsrDuringWrite]) + val withRead = jobs.exists(j => j.isInstanceOf[CsrRead] || j.isInstanceOf[CsrOnRead]) + if(withRead && withWrite) { + illegalAccess := False + } else { + if (withWrite) illegalAccess.clearWhen(input(CSR_WRITE_OPCODE)) + if (withRead) illegalAccess.clearWhen(input(CSR_READ_OPCODE)) + } + + + for (element <- jobs) element match { + case element : CsrDuringWrite => when(writeInstruction){element.doThat()} + case element : CsrDuringRead => when(readInstruction){element.doThat()} + case element : CsrDuring => {element.doThat()} + case _ => + } + when(writeEnable) { + for (element <- jobs) element match { + case element: CsrWrite => element.that.assignFromBits(writeData(element.bitOffset, element.that.getBitsWidth bits)) + case element: CsrOnWrite => element.doThat() + case _ => + } + } + + when(readEnable) { + for (element <- jobs) element match { + case element: CsrOnRead => + element.doThat() + case _ => + } + } + } + + def doJobsOverride(jobs : ArrayBuffer[Any]): Unit ={ + for (element <- jobs) element match { + case element: CsrReadToWriteOverride if element.that.getBitsWidth != 0 => readToWriteData(element.bitOffset, element.that.getBitsWidth bits) := element.that.asBits + case _ => + } + } + + csrOhDecoder match { + case false => { + csrMapping.readDataInit := 0 + switch(csrAddress) { + for ((address, jobs) <- csrMapping.mapping) { + is(address) { + doJobs(jobs) + for (element <- jobs) element match { + case element: CsrRead if element.that.getBitsWidth != 0 => csrMapping.readDataInit (element.bitOffset, element.that.getBitsWidth bits) := element.that.asBits + case _ => + } + } + } + } + switch(csrAddress) { + for ((address, jobs) <- csrMapping.mapping if jobs.exists(_.isInstanceOf[CsrReadToWriteOverride])) { + is(address) { + doJobsOverride(jobs) + } + } + } + } + case true => { + val oh = csrMapping.mapping.keys.toList.distinct.map(address => address -> RegNextWhen(decode.input(INSTRUCTION)(csrRange) === address, !execute.arbitration.isStuck).setCompositeName(this, "csr_" + address)).toMap + val readDatas = ArrayBuffer[Bits]() + for ((address, jobs) <- csrMapping.mapping) { + when(oh(address)){ + doJobs(jobs) + } + if(jobs.exists(_.isInstanceOf[CsrRead])) { + val masked = B(0, 32 bits) + when(oh(address)) (for (element <- jobs) element match { + case element: CsrRead if element.that.getBitsWidth != 0 => masked(element.bitOffset, element.that.getBitsWidth bits) := element.that.asBits + case _ => + }) + readDatas += masked + } + } + csrMapping.readDataInit := readDatas.reduceBalancedTree(_ | _) + for ((address, jobs) <- csrMapping.mapping) { + when(oh(address)){ + doJobsOverride(jobs) + } + } + } + } + + csrMapping.always.foreach { + case element : CsrDuringWrite => when(writeInstruction){element.doThat()} + case element : CsrDuringRead => when(readInstruction){element.doThat()} + case element : CsrOnWrite => when(writeEnable){element.doThat()} + case element : CsrOnRead => when(readEnable){element.doThat()} + } + + illegalAccess clearWhen(csrMapping.allowCsrSignal) + + when(privilege < csrAddress(9 downto 8).asUInt){ + illegalAccess := True + readInstruction := False + writeInstruction := False + } + illegalAccess clearWhen(!arbitration.isValid || !input(IS_CSR)) + } + } + } + } +} + + +class UserInterruptPlugin(interruptName : String, code : Int, privilege : Int = 3) extends Plugin[VexRiscv]{ + var interrupt, interruptEnable : Bool = null + override def setup(pipeline: VexRiscv): Unit = { + val csr = pipeline.service(classOf[CsrPlugin]) + interrupt = in.Bool().setName(interruptName) + val interruptPending = RegNext(interrupt) init(False) + val interruptEnable = RegInit(False).setName(interruptName + "_enable") + csr.addInterrupt(interruptPending && interruptEnable, code, privilege, Nil) + csr.r(csrAddress = CSR.MIP, bitOffset = code,interruptPending) + csr.rw(csrAddress = CSR.MIE, bitOffset = code, interruptEnable) + } + override def build(pipeline: VexRiscv): Unit = {} +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DBusCachedPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DBusCachedPlugin.scala new file mode 100644 index 0000000..80d4409 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DBusCachedPlugin.scala @@ -0,0 +1,554 @@ +package vexriscv.plugin + +import vexriscv.ip._ +import vexriscv._ +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba4.axi.Axi4 + +import scala.collection.mutable.ArrayBuffer + + +class DAxiCachedPlugin(config : DataCacheConfig, memoryTranslatorPortConfig : Any = null) extends DBusCachedPlugin(config, memoryTranslatorPortConfig) { + var dAxi : Axi4 = null + + override def build(pipeline: VexRiscv): Unit = { + super.build(pipeline) + dBus.setAsDirectionLess() + dAxi = master(dBus.toAxi4Shared().toAxi4()).setName("dAxi") + dBus = null //For safety, as nobody should use it anymore :) + } +} + +trait DBusEncodingService { + def addLoadWordEncoding(key: MaskedLiteral): Unit + def addStoreWordEncoding(key: MaskedLiteral): Unit + def bypassStore(data : Bits) : Unit + def loadData() : Bits +} + +class DBusCachedPlugin(val config : DataCacheConfig, + memoryTranslatorPortConfig : Any = null, + dBusCmdMasterPipe : Boolean = false, + dBusCmdSlavePipe : Boolean = false, + dBusRspSlavePipe : Boolean = false, + relaxedMemoryTranslationRegister : Boolean = false, + csrInfo : Boolean = false) extends Plugin[VexRiscv] with DBusAccessService with DBusEncodingService with VexRiscvRegressionArg { + import config._ + assert(!(config.withExternalAmo && !dBusRspSlavePipe)) + assert(isPow2(cacheSize)) + assert(!(memoryTranslatorPortConfig != null && config.cacheSize/config.wayCount > 4096), "When the D$ is used with MMU, each way can't be bigger than a page (4096 bytes)") + + var dBus : DataCacheMemBus = null + var mmuBus : MemoryTranslatorBus = null + var exceptionBus : Flow[ExceptionCause] = null + var privilegeService : PrivilegeService = null + var redoBranch : Flow[UInt] = null + + @dontName var dBusAccess : DBusAccess = null + override def newDBusAccess(): DBusAccess = { + assert(dBusAccess == null) + dBusAccess = DBusAccess() + dBusAccess + } + + override def getVexRiscvRegressionArgs(): Seq[String] = { + var args = List[String]() + args :+= "DBUS=CACHED" + args :+= s"DBUS_LOAD_DATA_WIDTH=$memDataWidth" + args :+= s"DBUS_STORE_DATA_WIDTH=$cpuDataWidth" + if(withLrSc) args :+= "LRSC=yes" + if(withAmo) args :+= "AMO=yes" + if(config.withExclusive && config.withInvalidate) args ++= List("DBUS_EXCLUSIVE=yes", "DBUS_INVALIDATE=yes") + args + } + + + override def addLoadWordEncoding(key : MaskedLiteral): Unit = { + val decoderService = pipeline.service(classOf[DecoderService]) + val cfg = pipeline.config + import cfg._ + + decoderService.add( + key, + List( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC_USE_SUB_LESS -> False, + MEMORY_ENABLE -> True, + RS1_USE -> True, + IntAluPlugin.ALU_CTRL -> IntAluPlugin.AluCtrlEnum.ADD_SUB, + SRC2_CTRL -> Src2CtrlEnum.IMI, + // REGFILE_WRITE_VALID -> True, + // BYPASSABLE_EXECUTE_STAGE -> False, + // BYPASSABLE_MEMORY_STAGE -> False, + MEMORY_WR -> False, + HAS_SIDE_EFFECT -> True + ) + ) + + if(withLrSc) decoderService.add(key, Seq(MEMORY_LRSC -> False)) + if(withAmo) decoderService.add(key, Seq(MEMORY_AMO -> False)) + } + override def addStoreWordEncoding(key : MaskedLiteral): Unit = { + val decoderService = pipeline.service(classOf[DecoderService]) + val cfg = pipeline.config + import cfg._ + + decoderService.add( + key, + List( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC_USE_SUB_LESS -> False, + MEMORY_ENABLE -> True, + RS1_USE -> True, + IntAluPlugin.ALU_CTRL -> IntAluPlugin.AluCtrlEnum.ADD_SUB, + SRC2_CTRL -> Src2CtrlEnum.IMS, +// RS2_USE -> True, + MEMORY_WR -> True, + HAS_SIDE_EFFECT -> True + ) + ) + + if(withLrSc) decoderService.add(key, Seq(MEMORY_LRSC -> False)) + if(withAmo) decoderService.add(key, Seq(MEMORY_AMO -> False)) + } + + val bypassStoreList = ArrayBuffer[(Bool, Bits)]() + + override def bypassStore(data: Bits): Unit = { + val prefix = s"DBusBypass${bypassStoreList.size}" + bypassStoreList += ConditionalContext.isTrue().setName(prefix + "_cond") -> CombInit(data).setName(prefix + "_value") + assert(config.cpuDataWidth >= data.getWidth, "Data cache word width is too small for that") + } + + + override def loadData(): Bits = pipeline.stages.last.output(MEMORY_LOAD_DATA) + + object MEMORY_ENABLE extends Stageable(Bool) + object MEMORY_MANAGMENT extends Stageable(Bool) + object MEMORY_WR extends Stageable(Bool) + object MEMORY_LRSC extends Stageable(Bool) + object MEMORY_AMO extends Stageable(Bool) + object MEMORY_FENCE extends Stageable(Bool) + object MEMORY_FORCE_CONSTISTENCY extends Stageable(Bool) + object IS_DBUS_SHARING extends Stageable(Bool()) + object MEMORY_VIRTUAL_ADDRESS extends Stageable(UInt(32 bits)) + object MEMORY_STORE_DATA_RF extends Stageable(Bits(32 bits)) +// object MEMORY_STORE_DATA_CPU extends Stageable(Bits(config.cpuDataWidth bits)) + object MEMORY_LOAD_DATA extends Stageable(Bits(config.cpuDataWidth bits)) + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + dBus = master(DataCacheMemBus(this.config)).setName("dBus") + + val decoderService = pipeline.service(classOf[DecoderService]) + + val stdActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC_USE_SUB_LESS -> False, + MEMORY_ENABLE -> True, + RS1_USE -> True, + IntAluPlugin.ALU_CTRL -> IntAluPlugin.AluCtrlEnum.ADD_SUB + ) + + val loadActions = stdActions ++ List( + SRC2_CTRL -> Src2CtrlEnum.IMI, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + MEMORY_WR -> False, + HAS_SIDE_EFFECT -> True + ) + + val storeActions = stdActions ++ List( + SRC2_CTRL -> Src2CtrlEnum.IMS, + RS2_USE -> True, + MEMORY_WR -> True, + HAS_SIDE_EFFECT -> True + ) + + decoderService.addDefault(MEMORY_ENABLE, False) + decoderService.add( + List(LB, LH, LW, LBU, LHU, LWU).map(_ -> loadActions) ++ + List(SB, SH, SW).map(_ -> storeActions) + ) + + if(withLrSc){ + List(LB, LH, LW, LBU, LHU, LWU, SB, SH, SW).foreach(e => + decoderService.add(e, Seq(MEMORY_LRSC -> False)) + ) + decoderService.add( + key = LR, + values = loadActions.filter(_._1 != SRC2_CTRL) ++ Seq( + SRC_ADD_ZERO -> True, + MEMORY_LRSC -> True + ) + ) + decoderService.add( + key = SC, + values = storeActions.filter(_._1 != SRC2_CTRL) ++ Seq( + SRC_ADD_ZERO -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + MEMORY_LRSC -> True + ) + ) + } + + if(withAmo){ + List(LB, LH, LW, LBU, LHU, LWU, SB, SH, SW).foreach(e => + decoderService.add(e, Seq(MEMORY_AMO -> False)) + ) + val amoActions = storeActions.filter(_._1 != SRC2_CTRL) ++ Seq( + SRC_ADD_ZERO -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + MEMORY_AMO -> True + ) + + for(i <- List(AMOSWAP, AMOADD, AMOXOR, AMOAND, AMOOR, AMOMIN, AMOMAX, AMOMINU, AMOMAXU)){ + decoderService.add(i, amoActions) + } + } + + if(withAmo && withLrSc){ + for(i <- List(AMOSWAP, AMOADD, AMOXOR, AMOAND, AMOOR, AMOMIN, AMOMAX, AMOMINU, AMOMAXU)){ + decoderService.add(i, List(MEMORY_LRSC -> False)) + } + for(i <- List(LR, SC)){ + decoderService.add(i, List(MEMORY_AMO -> False)) + } + } + + def MANAGEMENT = M"-------00000-----101-----0001111" + + decoderService.addDefault(MEMORY_MANAGMENT, False) + decoderService.add(MANAGEMENT, List( + MEMORY_MANAGMENT -> True, + RS1_USE -> True + )) + + withWriteResponse match { + case false => decoderService.add(FENCE, Nil) + case true => { + decoderService.addDefault(MEMORY_FENCE, False) + decoderService.add(FENCE, List(MEMORY_FENCE -> True)) + } + } + + mmuBus = pipeline.service(classOf[MemoryTranslator]).newTranslationPort(MemoryTranslatorPort.PRIORITY_DATA ,memoryTranslatorPortConfig) + redoBranch = pipeline.service(classOf[JumpService]).createJumpInterface(if(pipeline.writeBack != null) pipeline.writeBack else pipeline.memory) + + if(catchSomething) + exceptionBus = pipeline.service(classOf[ExceptionService]).newExceptionPort(if(pipeline.writeBack == null) pipeline.memory else pipeline.writeBack) + + if(pipeline.serviceExist(classOf[PrivilegeService])) + privilegeService = pipeline.service(classOf[PrivilegeService]) + + pipeline.update(DEBUG_BYPASS_CACHE, False) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val twoStageMmu = mmuBus.p.latency match { + case 0 => false + case 1 => true + } + + val cache = new DataCache( + this.config.copy( + mergeExecuteMemory = writeBack == null, + rfDataWidth = 32 + ), + mmuParameter = mmuBus.p + ) + + //Interconnect the plugin dBus with the cache dBus with some optional pipelining + def optionPipe[T](cond : Boolean, on : T)(f : T => T) : T = if(cond) f(on) else on + def cmdBuf = optionPipe(dBusCmdSlavePipe, cache.io.mem.cmd)(_.s2mPipe()) + dBus.cmd << optionPipe(dBusCmdMasterPipe, cmdBuf)(_.m2sPipe()) + cache.io.mem.rsp << (dBusRspSlavePipe match { + case false => dBus.rsp + case true if !withExternalAmo => dBus.rsp.m2sPipe() + case true if withExternalAmo => { + val rsp = Flow (DataCacheMemRsp(cache.p)) + rsp.valid := RegNext(dBus.rsp.valid) init(False) + rsp.exclusive := RegNext(dBus.rsp.exclusive) + rsp.error := RegNext(dBus.rsp.error) + rsp.last := RegNext(dBus.rsp.last) + rsp.aggregated := RegNext(dBus.rsp.aggregated) + rsp.data := RegNextWhen(dBus.rsp.data, dBus.rsp.valid && !cache.io.cpu.writeBack.keepMemRspData) + rsp + } + }) + + if(withInvalidate) { + cache.io.mem.inv << dBus.inv + cache.io.mem.ack >> dBus.ack + cache.io.mem.sync << dBus.sync + } + + pipeline plug new Area{ + //Memory bandwidth counter + val rspCounter = Reg(UInt(32 bits)) init(0) + when(dBus.rsp.valid){ + rspCounter := rspCounter + 1 + } + } + + decode plug new Area { + import decode._ + + when(mmuBus.busy && arbitration.isValid && input(MEMORY_ENABLE)) { + arbitration.haltItself := True + } + + + //Manage write to read hit ordering (ensure invalidation timings) + val fence = new Area { + insert(MEMORY_FORCE_CONSTISTENCY) := False + when(input(INSTRUCTION)(25)) { //RL + if (withLrSc) insert(MEMORY_FORCE_CONSTISTENCY) setWhen (input(MEMORY_LRSC)) + if (withAmo) insert(MEMORY_FORCE_CONSTISTENCY) setWhen (input(MEMORY_AMO)) + } + } + } + + execute plug new Area { + import execute._ + + val size = input(INSTRUCTION)(13 downto 12).asUInt + cache.io.cpu.execute.isValid := arbitration.isValid && input(MEMORY_ENABLE) + cache.io.cpu.execute.address := input(SRC_ADD).asUInt + cache.io.cpu.execute.args.wr := input(MEMORY_WR) + insert(MEMORY_STORE_DATA_RF) := size.mux( + U(0) -> input(RS2)( 7 downto 0) ## input(RS2)( 7 downto 0) ## input(RS2)(7 downto 0) ## input(RS2)(7 downto 0), + U(1) -> input(RS2)(15 downto 0) ## input(RS2)(15 downto 0), + default -> input(RS2)(31 downto 0) + ) + cache.io.cpu.execute.args.size := size.resized + + if(twoStageMmu) { + mmuBus.cmd(0).isValid := cache.io.cpu.execute.isValid + mmuBus.cmd(0).isStuck := arbitration.isStuck + mmuBus.cmd(0).virtualAddress := input(SRC_ADD).asUInt + mmuBus.cmd(0).bypassTranslation := False +// KeepAttribute(mmuBus.cmd(0)) +// KeepAttribute(mmuBus.cmd(1)) + } + + cache.io.cpu.flush.valid := arbitration.isValid && input(MEMORY_MANAGMENT) + cache.io.cpu.flush.singleLine := input(INSTRUCTION)(Riscv.rs1Range) =/= 0 + cache.io.cpu.flush.lineId := U(input(RS1) >> log2Up(bytePerLine)).resized + cache.io.cpu.execute.args.totalyConsistent := input(MEMORY_FORCE_CONSTISTENCY) + arbitration.haltItself setWhen(cache.io.cpu.flush.isStall || cache.io.cpu.execute.haltIt) + + if(withLrSc) { + cache.io.cpu.execute.args.isLrsc := False + when(input(MEMORY_LRSC)){ + cache.io.cpu.execute.args.isLrsc := True + } + } + + if(withAmo){ + cache.io.cpu.execute.isAmo := input(MEMORY_AMO) + cache.io.cpu.execute.amoCtrl.alu := input(INSTRUCTION)(31 downto 29) + cache.io.cpu.execute.amoCtrl.swap := input(INSTRUCTION)(27) + } + + + when(cache.io.cpu.execute.refilling && arbitration.isValid){ + arbitration.haltByOther := True + } + + if(relaxedMemoryTranslationRegister) { + insert(MEMORY_VIRTUAL_ADDRESS) := cache.io.cpu.execute.address + memory.input(MEMORY_VIRTUAL_ADDRESS) + if(writeBack != null) addPrePopTask( () => + KeepAttribute(memory.input(MEMORY_VIRTUAL_ADDRESS).getDrivingReg) + ) + } + } + + val mmuAndBufferStage = if(writeBack != null) memory else execute + mmuAndBufferStage plug new Area { + import mmuAndBufferStage._ + + cache.io.cpu.memory.isValid := arbitration.isValid && input(MEMORY_ENABLE) + cache.io.cpu.memory.isStuck := arbitration.isStuck + cache.io.cpu.memory.address := (if(relaxedMemoryTranslationRegister) input(MEMORY_VIRTUAL_ADDRESS) else if(mmuAndBufferStage == execute) cache.io.cpu.execute.address else U(input(REGFILE_WRITE_DATA))) + + mmuBus.cmd.last.isValid := cache.io.cpu.memory.isValid + mmuBus.cmd.last.isStuck := cache.io.cpu.memory.isStuck + mmuBus.cmd.last.virtualAddress := cache.io.cpu.memory.address + mmuBus.cmd.last.bypassTranslation := False + mmuBus.end := !arbitration.isStuck || arbitration.removeIt + cache.io.cpu.memory.mmuRsp := mmuBus.rsp + cache.io.cpu.memory.mmuRsp.isIoAccess setWhen(pipeline(DEBUG_BYPASS_CACHE) && !cache.io.cpu.memory.isWrite) + } + + val managementStage = stages.last + val mgs = managementStage plug new Area{ + import managementStage._ + cache.io.cpu.writeBack.isValid := arbitration.isValid && input(MEMORY_ENABLE) + cache.io.cpu.writeBack.isStuck := arbitration.isStuck + cache.io.cpu.writeBack.isFiring := arbitration.isFiring + cache.io.cpu.writeBack.isUser := (if(privilegeService != null) privilegeService.isUser() else False) + cache.io.cpu.writeBack.address := U(input(REGFILE_WRITE_DATA)) + cache.io.cpu.writeBack.storeData.subdivideIn(32 bits).foreach(_ := input(MEMORY_STORE_DATA_RF)) + afterElaboration(for((cond, value) <- bypassStoreList) when(cond){ + cache.io.cpu.writeBack.storeData.subdivideIn(widthOf(value) bits).foreach(_ := value) //Not optimal, but ok + }) + + val fence = if(withInvalidate) new Area { + cache.io.cpu.writeBack.fence := input(INSTRUCTION)(31 downto 20).as(FenceFlags()) + val aquire = False + if(withWriteResponse) when(input(MEMORY_ENABLE) && input(INSTRUCTION)(26)) { //AQ + if(withLrSc) when(input(MEMORY_LRSC)){ + aquire := True + } + if(withAmo) when(input(MEMORY_AMO)){ + aquire := True + } + } + + when(aquire){ + cache.io.cpu.writeBack.fence.forceAll() + } + + when(!input(MEMORY_FENCE) || !arbitration.isFiring){ + cache.io.cpu.writeBack.fence.clearAll() + } + + when(arbitration.isValid && (input(MEMORY_FENCE) || aquire)){ + mmuAndBufferStage.arbitration.haltByOther := True //Ensure that the fence affect the memory stage instruction by stoping it + } + } + + redoBranch.valid := False + redoBranch.payload := input(PC) + arbitration.flushIt setWhen(redoBranch.valid) + arbitration.flushNext setWhen(redoBranch.valid) + + if(catchSomething) { + exceptionBus.valid := False //cache.io.cpu.writeBack.mmuMiss || cache.io.cpu.writeBack.accessError || cache.io.cpu.writeBack.illegalAccess || cache.io.cpu.writeBack.unalignedAccess + exceptionBus.badAddr := U(input(REGFILE_WRITE_DATA)) + exceptionBus.code.assignDontCare() + } + + + when(arbitration.isValid && input(MEMORY_ENABLE)) { + if (catchAccessError) when(cache.io.cpu.writeBack.accessError) { + exceptionBus.valid := True + exceptionBus.code := (input(MEMORY_WR) ? U(7) | U(5)).resized + } + if(catchIllegal) when (cache.io.cpu.writeBack.mmuException) { + exceptionBus.valid := True + exceptionBus.code := (input(MEMORY_WR) ? U(15) | U(13)).resized + } + if (catchUnaligned) when(cache.io.cpu.writeBack.unalignedAccess) { + exceptionBus.valid := True + exceptionBus.code := (input(MEMORY_WR) ? U(6) | U(4)).resized + } + + when(cache.io.cpu.redo) { + redoBranch.valid := True + if(catchSomething) exceptionBus.valid := False + } + } + + arbitration.haltItself.setWhen(cache.io.cpu.writeBack.isValid && cache.io.cpu.writeBack.haltIt) + + val rspSplits = cache.io.cpu.writeBack.data.subdivideIn(8 bits) + val rspShifted = Bits(cpuDataWidth bits) + //Generate minimal mux to move from a wide aligned memory read to the register file shifter representation + for(i <- 0 until cpuDataWidth/8){ + val srcSize = 1 << (log2Up(cpuDataBytes) - log2Up(i+1)) + val srcZipped = rspSplits.zipWithIndex.filter{case (v, b) => b % (cpuDataBytes/srcSize) == i} + val src = srcZipped.map(_._1) + val range = cache.cpuWordToRfWordRange.high downto cache.cpuWordToRfWordRange.high+1-log2Up(srcSize) + val sel = cache.io.cpu.writeBack.address(range) +// println(s"$i $srcSize $range ${srcZipped.map(_._2).mkString(",")}") + rspShifted(i*8, 8 bits) := src.read(sel) + } + + val rspRf = CombInit(rspShifted(31 downto 0)) + if(withLrSc) when(input(MEMORY_LRSC) && input(MEMORY_WR)){ + rspRf := B(!cache.io.cpu.writeBack.exclusiveOk).resized + } + + val rspFormated = input(INSTRUCTION)(13 downto 12).mux( + 0 -> B((31 downto 8) -> (rspRf(7) && !input(INSTRUCTION)(14)),(7 downto 0) -> rspRf(7 downto 0)), + 1 -> B((31 downto 16) -> (rspRf(15) && ! input(INSTRUCTION)(14)),(15 downto 0) -> rspRf(15 downto 0)), + default -> rspRf //W + ) + + when(arbitration.isValid && input(MEMORY_ENABLE)) { + output(REGFILE_WRITE_DATA) := rspFormated + } + + insert(MEMORY_LOAD_DATA) := rspShifted + } + + //Share access to the dBus (used by self refilled MMU) + if(dBusAccess != null) pipeline plug new Area{ + dBusAccess.cmd.ready := False + val forceDatapath = False + when(dBusAccess.cmd.valid){ + decode.arbitration.haltByOther := True + val exceptionService = pipeline.service(classOf[ExceptionService]) + when(!stagesFromExecute.map(s => s.arbitration.isValid || exceptionService.isExceptionPending(s)).orR){ + when(!cache.io.cpu.execute.refilling) { + cache.io.cpu.execute.isValid := True + dBusAccess.cmd.ready := !execute.arbitration.isStuck + } + cache.io.cpu.execute.args.wr := False //dBusAccess.cmd.write +// execute.insert(MEMORY_STORE_DATA_RF) := dBusAccess.cmd.data //Not implemented + cache.io.cpu.execute.args.size := dBusAccess.cmd.size.resized + if(withLrSc) execute.input(MEMORY_LRSC) := False + if(withAmo) execute.input(MEMORY_AMO) := False + cache.io.cpu.execute.address := dBusAccess.cmd.address //Will only be 12 muxes + forceDatapath := True + } + } + execute.insert(IS_DBUS_SHARING) := dBusAccess.cmd.fire + mmuBus.cmd.last.bypassTranslation setWhen(mmuAndBufferStage.input(IS_DBUS_SHARING)) + if(twoStageMmu) mmuBus.cmd(0).bypassTranslation setWhen(execute.input(IS_DBUS_SHARING)) + + if(mmuAndBufferStage != execute) (cache.io.cpu.memory.isValid setWhen(mmuAndBufferStage.input(IS_DBUS_SHARING))) + cache.io.cpu.writeBack.isValid setWhen(managementStage.input(IS_DBUS_SHARING)) + dBusAccess.rsp.valid := managementStage.input(IS_DBUS_SHARING) && !cache.io.cpu.writeBack.isWrite && (cache.io.cpu.redo || !cache.io.cpu.writeBack.haltIt) + dBusAccess.rsp.data := mgs.rspRf + dBusAccess.rsp.error := cache.io.cpu.writeBack.unalignedAccess || cache.io.cpu.writeBack.accessError + dBusAccess.rsp.redo := cache.io.cpu.redo + component.addPrePopTask{() => + managementStage.input(IS_DBUS_SHARING).getDrivingReg clearWhen(dBusAccess.rsp.fire) + when(forceDatapath){ + execute.output(REGFILE_WRITE_DATA) := dBusAccess.cmd.address.asBits + } + if(mmuAndBufferStage != execute) mmuAndBufferStage.input(IS_DBUS_SHARING) init(False) + managementStage.input(IS_DBUS_SHARING) init(False) + when(dBusAccess.rsp.valid){ + managementStage.input(IS_DBUS_SHARING).getDrivingReg := False + } + } + } + + when(stages.last.arbitration.haltByOther){ + cache.io.cpu.writeBack.isValid := False + } + + if(csrInfo){ + val csr = service(classOf[CsrPlugin]) + csr.r(0xCC0, 0 -> U(cacheSize/wayCount), 20 -> U(bytePerLine)) + } + } +} + + diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DBusSimplePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DBusSimplePlugin.scala new file mode 100644 index 0000000..372cfcc --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DBusSimplePlugin.scala @@ -0,0 +1,614 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba3.ahblite.{AhbLite3Config, AhbLite3Master} +import spinal.lib.bus.amba4.axi._ +import spinal.lib.bus.avalon.{AvalonMM, AvalonMMConfig} +import spinal.lib.bus.bmb.{Bmb, BmbParameter} +import spinal.lib.bus.wishbone.{Wishbone, WishboneConfig} +import spinal.lib.bus.simple._ +import vexriscv.ip.DataCacheMemCmd + +import scala.collection.mutable.ArrayBuffer + + +case class DBusSimpleCmd() extends Bundle{ + val wr = Bool + val address = UInt(32 bits) + val data = Bits(32 bit) + val size = UInt(2 bit) +} + +case class DBusSimpleRsp() extends Bundle with IMasterSlave{ + val ready = Bool + val error = Bool + val data = Bits(32 bit) + + override def asMaster(): Unit = { + out(ready,error,data) + } +} + + +object DBusSimpleBus{ + def getAxi4Config() = Axi4Config( + addressWidth = 32, + dataWidth = 32, + useId = false, + useRegion = false, + useBurst = false, + useLock = false, + useQos = false, + useLen = false, + useResp = true + ) + + def getAvalonConfig() = AvalonMMConfig.pipelined( + addressWidth = 32, + dataWidth = 32).copy( + useByteEnable = true, + useResponse = true, + maximumPendingReadTransactions = 1 + ) + + def getWishboneConfig() = WishboneConfig( + addressWidth = 30, + dataWidth = 32, + selWidth = 4, + useSTALL = false, + useLOCK = false, + useERR = true, + useRTY = false, + tgaWidth = 0, + tgcWidth = 0, + tgdWidth = 0, + useBTE = true, + useCTI = true + ) + + def getPipelinedMemoryBusConfig() = PipelinedMemoryBusConfig( + addressWidth = 32, + dataWidth = 32 + ) + + def getAhbLite3Config() = AhbLite3Config( + addressWidth = 32, + dataWidth = 32 + ) + def getBmbParameter() = BmbParameter( + addressWidth = 32, + dataWidth = 32, + lengthWidth = 2, + sourceWidth = 0, + contextWidth = 1, + alignment = BmbParameter.BurstAlignement.LENGTH + ) +} + +case class DBusSimpleBus(bigEndian : Boolean = false) extends Bundle with IMasterSlave{ + val cmd = Stream(DBusSimpleCmd()) + val rsp = DBusSimpleRsp() + + override def asMaster(): Unit = { + master(cmd) + slave(rsp) + } + + def cmdS2mPipe() : DBusSimpleBus = { + val s = DBusSimpleBus(bigEndian) + s.cmd << this.cmd.s2mPipe() + this.rsp := s.rsp + s + } + + def genMask(cmd : DBusSimpleCmd) = { + if(bigEndian) + cmd.size.mux( + U(0) -> B"1000", + U(1) -> B"1100", + default -> B"1111" + ) |>> cmd.address(1 downto 0) + else + cmd.size.mux( + U(0) -> B"0001", + U(1) -> B"0011", + default -> B"1111" + ) |<< cmd.address(1 downto 0) + } + + def toAxi4Shared(stageCmd : Boolean = false, pendingWritesMax : Int = 7): Axi4Shared = { + val axi = Axi4Shared(DBusSimpleBus.getAxi4Config()) + + val cmdPreFork = if (stageCmd) cmd.stage.stage().s2mPipe() else cmd + + val pendingWrites = CounterUpDown( + stateCount = pendingWritesMax + 1, + incWhen = cmdPreFork.fire && cmdPreFork.wr, + decWhen = axi.writeRsp.fire + ) + + val hazard = (pendingWrites =/= 0 && cmdPreFork.valid && !cmdPreFork.wr) || pendingWrites === pendingWritesMax + val (cmdFork, dataFork) = StreamFork2(cmdPreFork.haltWhen(hazard)) + axi.sharedCmd.arbitrationFrom(cmdFork) + axi.sharedCmd.write := cmdFork.wr + axi.sharedCmd.prot := "010" + axi.sharedCmd.cache := "1111" + axi.sharedCmd.size := cmdFork.size.resized + axi.sharedCmd.addr := cmdFork.address + + val dataStage = dataFork.throwWhen(!dataFork.wr) + axi.writeData.arbitrationFrom(dataStage) + axi.writeData.last := True + axi.writeData.data := dataStage.data + axi.writeData.strb := genMask(dataStage).resized + + + rsp.ready := axi.r.valid + rsp.error := !axi.r.isOKAY() + rsp.data := axi.r.data + + axi.r.ready := True + axi.b.ready := True + axi + } + + def toAxi4(stageCmd : Boolean = true) = this.toAxi4Shared(stageCmd).toAxi4() + + + + def toAvalon(stageCmd : Boolean = true): AvalonMM = { + val avalonConfig = DBusSimpleBus.getAvalonConfig() + val mm = AvalonMM(avalonConfig) + val cmdStage = if(stageCmd) cmd.stage else cmd + mm.read := cmdStage.valid && !cmdStage.wr + mm.write := cmdStage.valid && cmdStage.wr + mm.address := (cmdStage.address >> 2) @@ U"00" + mm.writeData := cmdStage.data(31 downto 0) + mm.byteEnable := genMask(cmdStage).resized + + + cmdStage.ready := mm.waitRequestn + rsp.ready :=mm.readDataValid + rsp.error := mm.response =/= AvalonMM.Response.OKAY + rsp.data := mm.readData + + mm + } + + def toWishbone(): Wishbone = { + val wishboneConfig = DBusSimpleBus.getWishboneConfig() + val bus = Wishbone(wishboneConfig) + val cmdStage = cmd.halfPipe() + + bus.ADR := cmdStage.address >> 2 + bus.CTI :=B"000" + bus.BTE := "00" + bus.SEL := genMask(cmdStage).resized + when(!cmdStage.wr) { + bus.SEL := "1111" + } + bus.WE := cmdStage.wr + bus.DAT_MOSI := cmdStage.data + + cmdStage.ready := cmdStage.valid && bus.ACK + bus.CYC := cmdStage.valid + bus.STB := cmdStage.valid + + rsp.ready := cmdStage.valid && !bus.WE && bus.ACK + rsp.data := bus.DAT_MISO + rsp.error := False //TODO + bus + } + + def toPipelinedMemoryBus() : PipelinedMemoryBus = { + val pipelinedMemoryBusConfig = DBusSimpleBus.getPipelinedMemoryBusConfig() + val bus = PipelinedMemoryBus(pipelinedMemoryBusConfig) + bus.cmd.valid := cmd.valid + bus.cmd.write := cmd.wr + bus.cmd.address := cmd.address.resized + bus.cmd.data := cmd.data + bus.cmd.mask := genMask(cmd) + cmd.ready := bus.cmd.ready + + rsp.ready := bus.rsp.valid + rsp.data := bus.rsp.data + + bus + } + + def toAhbLite3Master(avoidWriteToReadHazard : Boolean): AhbLite3Master = { + val bus = AhbLite3Master(DBusSimpleBus.getAhbLite3Config()) + bus.HADDR := this.cmd.address + bus.HWRITE := this.cmd.wr + bus.HSIZE := B(this.cmd.size, 3 bits) + bus.HBURST := 0 + bus.HPROT := "1111" + bus.HTRANS := this.cmd.valid ## B"0" + bus.HMASTLOCK := False + bus.HWDATA := RegNextWhen(this.cmd.data, bus.HREADY) + this.cmd.ready := bus.HREADY + + val pending = RegInit(False) clearWhen(bus.HREADY) setWhen(this.cmd.fire && !this.cmd.wr) + this.rsp.ready := bus.HREADY && pending + this.rsp.data := bus.HRDATA + this.rsp.error := bus.HRESP + + if(avoidWriteToReadHazard) { + val writeDataPhase = RegNextWhen(bus.HTRANS === 2 && bus.HWRITE, bus.HREADY) init (False) + val potentialHazard = this.cmd.valid && !this.cmd.wr && writeDataPhase + when(potentialHazard) { + bus.HTRANS := 0 + this.cmd.ready := False + } + } + bus + } + + def toBmb() : Bmb = { + val pipelinedMemoryBusConfig = DBusSimpleBus.getBmbParameter() + val bus = Bmb(pipelinedMemoryBusConfig) + + bus.cmd.valid := cmd.valid + bus.cmd.last := True + bus.cmd.context(0) := cmd.wr + bus.cmd.opcode := (cmd.wr ? B(Bmb.Cmd.Opcode.WRITE) | B(Bmb.Cmd.Opcode.READ)) + bus.cmd.address := cmd.address.resized + bus.cmd.data := cmd.data + bus.cmd.length := cmd.size.mux( + 0 -> U"00", + 1 -> U"01", + default -> U"11" + ) + bus.cmd.mask := genMask(cmd) + + cmd.ready := bus.cmd.ready + + rsp.ready := bus.rsp.valid && !bus.rsp.context(0) + rsp.data := bus.rsp.data + rsp.error := bus.rsp.isError + bus.rsp.ready := True + + bus + } +} + + +class DBusSimplePlugin(catchAddressMisaligned : Boolean = false, + catchAccessFault : Boolean = false, + earlyInjection : Boolean = false, /*, idempotentRegions : (UInt) => Bool = (x) => False*/ + emitCmdInMemoryStage : Boolean = false, + onlyLoadWords : Boolean = false, + withLrSc : Boolean = false, + val bigEndian : Boolean = false, + memoryTranslatorPortConfig : Any = null) extends Plugin[VexRiscv] with DBusAccessService { + + var dBus : DBusSimpleBus = null + assert(!(emitCmdInMemoryStage && earlyInjection)) + object MEMORY_ENABLE extends Stageable(Bool) + object MEMORY_READ_DATA extends Stageable(Bits(32 bits)) + object MEMORY_ADDRESS_LOW extends Stageable(UInt(2 bits)) + object ALIGNEMENT_FAULT extends Stageable(Bool) + object MMU_FAULT extends Stageable(Bool) + object MEMORY_ATOMIC extends Stageable(Bool) + object ATOMIC_HIT extends Stageable(Bool) + object MEMORY_STORE extends Stageable(Bool) + + var memoryExceptionPort : Flow[ExceptionCause] = null + var rspStage : Stage = null + var mmuBus : MemoryTranslatorBus = null + var redoBranch : Flow[UInt] = null + val catchSomething = catchAccessFault || catchAddressMisaligned || memoryTranslatorPortConfig != null + + @dontName var dBusAccess : DBusAccess = null + override def newDBusAccess(): DBusAccess = { + assert(dBusAccess == null) + dBusAccess = DBusAccess() + dBusAccess + } + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + import pipeline._ + + val decoderService = pipeline.service(classOf[DecoderService]) + + val stdActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC_USE_SUB_LESS -> False, + MEMORY_ENABLE -> True, + RS1_USE -> True + ) ++ (if(catchAccessFault || catchAddressMisaligned) List(IntAluPlugin.ALU_CTRL -> IntAluPlugin.AluCtrlEnum.ADD_SUB) else Nil) //Used for access fault bad address in memory stage + + val loadActions = stdActions ++ List( + SRC2_CTRL -> Src2CtrlEnum.IMI, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> Bool(earlyInjection), + MEMORY_STORE -> False, + HAS_SIDE_EFFECT -> True + ) + + val storeActions = stdActions ++ List( + SRC2_CTRL -> Src2CtrlEnum.IMS, + RS2_USE -> True, + MEMORY_STORE -> True, + HAS_SIDE_EFFECT -> True + ) + + decoderService.addDefault(MEMORY_ENABLE, False) + decoderService.add( + (if(onlyLoadWords) List(LW) else List(LB, LH, LW, LBU, LHU, LWU)).map(_ -> loadActions) ++ + List(SB, SH, SW).map(_ -> storeActions) + ) + + + if(withLrSc){ + List(LB, LH, LW, LBU, LHU, LWU, SB, SH, SW).foreach(e => + decoderService.add(e, Seq(MEMORY_ATOMIC -> False)) + ) + decoderService.add( + key = LR, + values = loadActions.filter(_._1 != SRC2_CTRL) ++ Seq( + SRC_ADD_ZERO -> True, + MEMORY_ATOMIC -> True + ) + ) + + decoderService.add( + key = SC, + values = storeActions.filter(_._1 != SRC2_CTRL) ++ Seq( + SRC_ADD_ZERO -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + MEMORY_ATOMIC -> True + ) + ) + } + + decoderService.add(FENCE, Nil) + + rspStage = if(stages.last == execute) execute else (if(emitCmdInMemoryStage) writeBack else memory) + if(catchSomething) { + val exceptionService = pipeline.service(classOf[ExceptionService]) + memoryExceptionPort = exceptionService.newExceptionPort(rspStage) + } + + if(memoryTranslatorPortConfig != null) { + mmuBus = pipeline.service(classOf[MemoryTranslator]).newTranslationPort(MemoryTranslatorPort.PRIORITY_DATA, memoryTranslatorPortConfig) + redoBranch = pipeline.service(classOf[JumpService]).createJumpInterface(if(pipeline.memory != null) pipeline.memory else pipeline.execute) + } + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + object MMU_RSP extends Stageable(MemoryTranslatorRsp(mmuBus.p)) + + dBus = master(DBusSimpleBus(bigEndian)).setName("dBus") + + + decode plug new Area { + import decode._ + + if(mmuBus != null) when(mmuBus.busy && arbitration.isValid && input(MEMORY_ENABLE)) { + arbitration.haltItself := True + } + } + + //Emit dBus.cmd request + val cmdSent = if(rspStage == execute) RegInit(False) setWhen(dBus.cmd.fire) clearWhen(!execute.arbitration.isStuck) else False + val cmdStage = if(emitCmdInMemoryStage) memory else execute + cmdStage plug new Area{ + import cmdStage._ + val privilegeService = pipeline.serviceElse(classOf[PrivilegeService], PrivilegeServiceDefault()) + + + if (catchAddressMisaligned) + insert(ALIGNEMENT_FAULT) := (dBus.cmd.size === 2 && dBus.cmd.address(1 downto 0) =/= 0) || (dBus.cmd.size === 1 && dBus.cmd.address(0 downto 0) =/= 0) + else + insert(ALIGNEMENT_FAULT) := False + + + val skipCmd = False + skipCmd setWhen(input(ALIGNEMENT_FAULT)) + + dBus.cmd.valid := arbitration.isValid && input(MEMORY_ENABLE) && !arbitration.isStuckByOthers && !arbitration.isFlushed && !skipCmd && !cmdSent + dBus.cmd.wr := input(MEMORY_STORE) + dBus.cmd.size := input(INSTRUCTION)(13 downto 12).asUInt + dBus.cmd.payload.data := dBus.cmd.size.mux ( + U(0) -> input(RS2)(7 downto 0) ## input(RS2)(7 downto 0) ## input(RS2)(7 downto 0) ## input(RS2)(7 downto 0), + U(1) -> input(RS2)(15 downto 0) ## input(RS2)(15 downto 0), + default -> input(RS2)(31 downto 0) + ) + when(arbitration.isValid && input(MEMORY_ENABLE) && !dBus.cmd.ready && !skipCmd && !cmdSent){ + arbitration.haltItself := True + } + + insert(MEMORY_ADDRESS_LOW) := dBus.cmd.address(1 downto 0) + + //formal + val formalMask = dBus.genMask(dBus.cmd) + + insert(FORMAL_MEM_ADDR) := dBus.cmd.address & U"xFFFFFFFC" + insert(FORMAL_MEM_WMASK) := (dBus.cmd.valid && dBus.cmd.wr) ? formalMask | B"0000" + insert(FORMAL_MEM_RMASK) := (dBus.cmd.valid && !dBus.cmd.wr) ? formalMask | B"0000" + insert(FORMAL_MEM_WDATA) := dBus.cmd.payload.data + + val mmu = (mmuBus != null) generate new Area { + mmuBus.cmd.last.isValid := arbitration.isValid && input(MEMORY_ENABLE) + mmuBus.cmd.last.isStuck := arbitration.isStuck + mmuBus.cmd.last.virtualAddress := input(SRC_ADD).asUInt + mmuBus.cmd.last.bypassTranslation := False + mmuBus.end := !arbitration.isStuck || arbitration.isRemoved + dBus.cmd.address := mmuBus.rsp.physicalAddress + + //do not emit memory request if MMU refilling + insert(MMU_FAULT) := input(MMU_RSP).exception || (!input(MMU_RSP).allowWrite && input(MEMORY_STORE)) || (!input(MMU_RSP).allowRead && !input(MEMORY_STORE)) + skipCmd.setWhen(input(MMU_FAULT) || input(MMU_RSP).refilling) + + insert(MMU_RSP) := mmuBus.rsp + } + + val mmuLess = (mmuBus == null) generate new Area{ + dBus.cmd.address := input(SRC_ADD).asUInt + } + + + val atomic = withLrSc generate new Area{ + val reserved = RegInit(False) + insert(ATOMIC_HIT) := reserved + when(arbitration.isFiring && input(MEMORY_ENABLE) && (if(mmuBus != null) !input(MMU_FAULT) else True) && !skipCmd){ + reserved setWhen(input(MEMORY_ATOMIC)) + reserved clearWhen(input(MEMORY_STORE)) + } + when(input(MEMORY_STORE) && input(MEMORY_ATOMIC) && !input(ATOMIC_HIT)){ + skipCmd := True + } + } + } + + //Collect dBus.rsp read responses + rspStage plug new Area { + val s = rspStage; import s._ + + + insert(MEMORY_READ_DATA) := dBus.rsp.data + + arbitration.haltItself setWhen(arbitration.isValid && input(MEMORY_ENABLE) && !input(MEMORY_STORE) && (!dBus.rsp.ready || (if(rspStage == execute) !cmdSent else False))) + + if(catchSomething) { + memoryExceptionPort.valid := False + memoryExceptionPort.code.assignDontCare() + memoryExceptionPort.badAddr := input(REGFILE_WRITE_DATA).asUInt + + if(catchAccessFault) when(dBus.rsp.ready && dBus.rsp.error && !input(MEMORY_STORE)) { + memoryExceptionPort.valid := True + memoryExceptionPort.code := 5 + } + + if(catchAddressMisaligned) when(input(ALIGNEMENT_FAULT)){ + memoryExceptionPort.code := (input(MEMORY_STORE) ? U(6) | U(4)).resized + memoryExceptionPort.valid := True + } + + if(memoryTranslatorPortConfig != null) { + redoBranch.valid := False + redoBranch.payload := input(PC) + + when(input(MMU_RSP).refilling){ + redoBranch.valid := True + memoryExceptionPort.valid := False + } elsewhen(input(MMU_FAULT)) { + memoryExceptionPort.valid := True + memoryExceptionPort.code := (input(MEMORY_STORE) ? U(15) | U(13)).resized + } + + arbitration.flushIt setWhen(redoBranch.valid) + arbitration.flushNext setWhen(redoBranch.valid) + } + + when(!(arbitration.isValid && input(MEMORY_ENABLE) && (Bool(cmdStage != rspStage) || !arbitration.isStuckByOthers))){ + if(catchSomething) memoryExceptionPort.valid := False + if(memoryTranslatorPortConfig != null) redoBranch.valid := False + } + + } + } + + //Reformat read responses, REGFILE_WRITE_DATA overriding + val injectionStage = if(earlyInjection) memory else stages.last + injectionStage plug new Area { + import injectionStage._ + + + val rspShifted = MEMORY_READ_DATA() + rspShifted := input(MEMORY_READ_DATA) + if(bigEndian) + switch(input(MEMORY_ADDRESS_LOW)){ + is(1){rspShifted(31 downto 24) := input(MEMORY_READ_DATA)(23 downto 16)} + is(2){rspShifted(31 downto 16) := input(MEMORY_READ_DATA)(15 downto 0)} + is(3){rspShifted(31 downto 24) := input(MEMORY_READ_DATA)(7 downto 0)} + } + else + switch(input(MEMORY_ADDRESS_LOW)){ + is(1){rspShifted(7 downto 0) := input(MEMORY_READ_DATA)(15 downto 8)} + is(2){rspShifted(15 downto 0) := input(MEMORY_READ_DATA)(31 downto 16)} + is(3){rspShifted(7 downto 0) := input(MEMORY_READ_DATA)(31 downto 24)} + } + + val rspFormated = + if(bigEndian) + input(INSTRUCTION)(13 downto 12).mux( + 0 -> B((31 downto 8) -> (rspShifted(31) && !input(INSTRUCTION)(14)),(7 downto 0) -> rspShifted(31 downto 24)), + 1 -> B((31 downto 16) -> (rspShifted(31) && ! input(INSTRUCTION)(14)),(15 downto 0) -> rspShifted(31 downto 16)), + default -> rspShifted //W + ) + else + input(INSTRUCTION)(13 downto 12).mux( + 0 -> B((31 downto 8) -> (rspShifted(7) && !input(INSTRUCTION)(14)),(7 downto 0) -> rspShifted(7 downto 0)), + 1 -> B((31 downto 16) -> (rspShifted(15) && ! input(INSTRUCTION)(14)),(15 downto 0) -> rspShifted(15 downto 0)), + default -> rspShifted //W + ) + + when(arbitration.isValid && input(MEMORY_ENABLE)) { + output(REGFILE_WRITE_DATA) := (if(!onlyLoadWords) rspFormated else input(MEMORY_READ_DATA)) + if(withLrSc){ + when(input(MEMORY_ATOMIC) && input(MEMORY_STORE)){ + output(REGFILE_WRITE_DATA) := (!input(ATOMIC_HIT)).asBits.resized + } + } + } + +// if(!earlyInjection && !emitCmdInMemoryStage && config.withWriteBackStage) +// assert(!(arbitration.isValid && input(MEMORY_ENABLE) && !input(MEMORY_STORE) && arbitration.isStuck),"DBusSimplePlugin doesn't allow writeback stage stall when read happend") + + //formal + insert(FORMAL_MEM_RDATA) := input(MEMORY_READ_DATA) + } + + //Share access to the dBus (used by self refilled MMU) + val dBusSharing = (dBusAccess != null) generate new Area{ + val state = Reg(UInt(2 bits)) init(0) + dBusAccess.cmd.ready := False + dBusAccess.rsp.valid := False + dBusAccess.rsp.data := dBus.rsp.data + dBusAccess.rsp.error := dBus.rsp.error + dBusAccess.rsp.redo := False + + switch(state){ + is(0){ + when(dBusAccess.cmd.valid){ + decode.arbitration.haltItself := True + when(!stages.dropWhile(_ != execute).map(_.arbitration.isValid).orR){ + state := 1 + } + } + } + is(1){ + decode.arbitration.haltItself := True + dBus.cmd.valid := True + dBus.cmd.address := dBusAccess.cmd.address + dBus.cmd.wr := dBusAccess.cmd.write + dBus.cmd.data := dBusAccess.cmd.data + dBus.cmd.size := dBusAccess.cmd.size + when(dBus.cmd.ready){ + state := (dBusAccess.cmd.write ? U(0) | U(2)) + dBusAccess.cmd.ready := True + } + } + is(2){ + decode.arbitration.haltItself := True + when(dBus.rsp.ready){ + dBusAccess.rsp.valid := True + state := 0 + } + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala new file mode 100644 index 0000000..01c2acd --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DebugPlugin.scala @@ -0,0 +1,364 @@ +package vexriscv.plugin + +import spinal.lib.com.jtag.{Jtag, JtagTapInstructionCtrl} +import spinal.lib.system.debugger.{JtagBridge, JtagBridgeNoTap, SystemDebugger, SystemDebuggerConfig, SystemDebuggerMemBus} +import vexriscv.plugin.IntAluPlugin.{ALU_CTRL, AluCtrlEnum} +import vexriscv._ +import vexriscv.ip._ +import spinal.core._ +import spinal.lib._ +import spinal.lib.blackbox.xilinx.s7.BSCANE2 +import spinal.lib.bus.amba3.apb.{Apb3, Apb3Config} +import spinal.lib.bus.avalon.{AvalonMM, AvalonMMConfig} +import spinal.lib.bus.bmb.{Bmb, BmbAccessCapabilities, BmbAccessParameter, BmbParameter} +import spinal.lib.bus.simple.PipelinedMemoryBus + +import scala.collection.mutable.ArrayBuffer + + +case class DebugExtensionCmd() extends Bundle{ + val wr = Bool + val address = UInt(8 bit) + val data = Bits(32 bit) +} +case class DebugExtensionRsp() extends Bundle{ + val data = Bits(32 bit) +} + +object DebugExtensionBus{ + def getBmbAccessParameter(source : BmbAccessCapabilities) = source.copy( + addressWidth = 8, + dataWidth = 32, + lengthWidthMax = 2, + alignment = BmbParameter.BurstAlignement.LENGTH + ) +} + +case class DebugExtensionBus() extends Bundle with IMasterSlave{ + val cmd = Stream(DebugExtensionCmd()) + val rsp = DebugExtensionRsp() //one cycle latency + + override def asMaster(): Unit = { + master(cmd) + in(rsp) + } + + def fromApb3(): Apb3 ={ + val apb = Apb3(Apb3Config( + addressWidth = 8, + dataWidth = 32, + useSlaveError = false + )) + + cmd.valid := apb.PSEL(0) && apb.PENABLE + cmd.wr := apb.PWRITE + cmd.address := apb.PADDR + cmd.data := apb.PWDATA + + apb.PREADY := cmd.ready + apb.PRDATA := rsp.data + + apb + } + + def fromAvalon(): AvalonMM ={ + val bus = AvalonMM(AvalonMMConfig.fixed(addressWidth = 8,dataWidth = 32, readLatency = 1)) + + cmd.valid := bus.read || bus.write + cmd.wr := bus.write + cmd.address := bus.address + cmd.data := bus.writeData + + bus.waitRequestn := cmd.ready + bus.readData := rsp.data + + bus + } + + def fromPipelinedMemoryBus(): PipelinedMemoryBus ={ + val bus = PipelinedMemoryBus(32, 32) + + cmd.arbitrationFrom(bus.cmd) + cmd.wr := bus.cmd.write + cmd.address := bus.cmd.address.resized + cmd.data := bus.cmd.data + + bus.rsp.valid := RegNext(cmd.fire) init(False) + bus.rsp.data := rsp.data + + bus + } + + def fromBmb(): Bmb ={ + val bus = Bmb(BmbParameter( + addressWidth = 8, + dataWidth = 32, + lengthWidth = 2, + sourceWidth = 0, + contextWidth = 0 + )) + + cmd.arbitrationFrom(bus.cmd) + cmd.wr := bus.cmd.isWrite + cmd.address := bus.cmd.address + cmd.data := bus.cmd.data + + bus.rsp.valid := RegNext(cmd.fire) init(False) + bus.rsp.data := rsp.data + bus.rsp.last := True + bus.rsp.setSuccess() + + bus + } + + def from(c : SystemDebuggerConfig) : SystemDebuggerMemBus = { + val mem = SystemDebuggerMemBus(c) + cmd.valid := mem.cmd.valid + cmd.wr := mem.cmd.wr + cmd.data := mem.cmd.data + cmd.address := mem.cmd.address.resized + mem.cmd.ready := cmd.ready + mem.rsp.valid := RegNext(cmd.fire).init(False) + mem.rsp.payload := rsp.data + mem + } + + def fromJtag(): Jtag ={ + val jtagConfig = SystemDebuggerConfig( + memAddressWidth = 32, + memDataWidth = 32, + remoteCmdWidth = 1 + ) + val jtagBridge = new JtagBridge(jtagConfig) + val debugger = new SystemDebugger(jtagConfig) + debugger.io.remote <> jtagBridge.io.remote + debugger.io.mem <> this.from(jtagConfig) + + jtagBridge.io.jtag + } + + def fromJtagInstructionCtrl(jtagClockDomain : ClockDomain, jtagHeaderIgnoreWidth : Int): JtagTapInstructionCtrl ={ + val jtagConfig = SystemDebuggerConfig( + memAddressWidth = 32, + memDataWidth = 32, + remoteCmdWidth = 1 + ) + val jtagBridge = new JtagBridgeNoTap(jtagConfig, jtagClockDomain, jtagHeaderIgnoreWidth) + val debugger = new SystemDebugger(jtagConfig) + debugger.io.remote <> jtagBridge.io.remote + debugger.io.mem <> this.from(jtagConfig) + + jtagBridge.io.ctrl + } + + def fromBscane2(usedId : Int, jtagHeaderIgnoreWidth : Int): Unit ={ + val jtagConfig = SystemDebuggerConfig() + + val bscane2 = BSCANE2(usedId) + val jtagClockDomain = ClockDomain(bscane2.TCK) + + val jtagBridge = new JtagBridgeNoTap(jtagConfig, jtagClockDomain, jtagHeaderIgnoreWidth) + jtagBridge.io.ctrl << bscane2.toJtagTapInstructionCtrl() + + val debugger = new SystemDebugger(jtagConfig) + debugger.io.remote <> jtagBridge.io.remote + debugger.io.mem <> this.from(debugger.io.mem.c) + } +} + +case class DebugExtensionIo() extends Bundle with IMasterSlave{ + val bus = DebugExtensionBus() + val resetOut = Bool + + override def asMaster(): Unit = { + master(bus) + in(resetOut) + } +} + +class DebugPlugin(var debugClockDomain : ClockDomain, hardwareBreakpointCount : Int = 0, BreakpointReadback : Boolean = false) extends Plugin[VexRiscv] { + + var io : DebugExtensionIo = null + val injectionAsks = ArrayBuffer[(Stage, Bool)]() + var injectionPort : Stream[Bits] = null + + + object IS_EBREAK extends Stageable(Bool) + object DO_EBREAK extends Stageable(Bool) + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + io = slave(DebugExtensionIo()).setName("debug") + + val decoderService = pipeline.service(classOf[DecoderService]) + + decoderService.addDefault(IS_EBREAK, False) + decoderService.add(EBREAK,List(IS_EBREAK -> True)) + + injectionPort = pipeline.service(classOf[IBusFetcher]).getInjectionPort() + + if(pipeline.serviceExist(classOf[ReportService])){ + val report = pipeline.service(classOf[ReportService]) + report.add("debug" -> { + val e = new DebugReport() + e.hardwareBreakpointCount = hardwareBreakpointCount + e + }) + } + } + + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val logic = debugClockDomain {pipeline plug new Area{ + val iBusFetcher = service(classOf[IBusFetcher]) + val firstCycle = RegNext(False) setWhen (io.bus.cmd.ready) + val secondCycle = RegNext(firstCycle) + val resetIt = RegInit(False) + val haltIt = RegInit(False) + val stepIt = RegInit(False) + + val isPipBusy = RegNext(stages.map(_.arbitration.isValid).orR || iBusFetcher.incoming()) + val godmode = RegInit(False) setWhen(haltIt && !isPipBusy) + val haltedByBreak = RegInit(False) + val debugUsed = RegInit(False) setWhen(io.bus.cmd.valid) addAttribute(Verilator.public) + val disableEbreak = RegInit(False) + + val allowEBreak = debugUsed && !disableEbreak + + val hardwareBreakpoints = Vec(Reg(new Bundle{ + val valid = Bool() + val pc = UInt(31 bits) + }), hardwareBreakpointCount) + hardwareBreakpoints.foreach(_.valid init(False)) + + val busReadDataReg = Reg(Bits(32 bit)) + when(stages.last.arbitration.isValid) { + busReadDataReg := stages.last.output(REGFILE_WRITE_DATA) + } + io.bus.cmd.ready := True + io.bus.rsp.data := busReadDataReg + when(!RegNext(io.bus.cmd.address(2))){ + io.bus.rsp.data(0) := resetIt + io.bus.rsp.data(1) := haltIt + io.bus.rsp.data(2) := isPipBusy + io.bus.rsp.data(3) := haltedByBreak + io.bus.rsp.data(4) := stepIt + } + if (BreakpointReadback) { + switch(RegNext(io.bus.cmd.address(7 downto 2))) { + for(i <- 0 until hardwareBreakpointCount){ + is(0x10 + i){ + io.bus.rsp.data(31 downto 1) := hardwareBreakpoints(i).pc.asBits + io.bus.rsp.data(0) := hardwareBreakpoints(i).valid + } + } + } + } + + + injectionPort.valid := False + injectionPort.payload := io.bus.cmd.data + + when(io.bus.cmd.valid) { + switch(io.bus.cmd.address(7 downto 2)) { + is(0x0) { + when(io.bus.cmd.wr) { + stepIt := io.bus.cmd.data(4) + resetIt setWhen (io.bus.cmd.data(16)) clearWhen (io.bus.cmd.data(24)) + haltIt setWhen (io.bus.cmd.data(17)) clearWhen (io.bus.cmd.data(25)) + haltedByBreak clearWhen (io.bus.cmd.data(25)) + godmode clearWhen(io.bus.cmd.data(25)) + disableEbreak setWhen (io.bus.cmd.data(18)) clearWhen (io.bus.cmd.data(26)) + } + } + is(0x1) { + when(io.bus.cmd.wr) { + injectionPort.valid := True + io.bus.cmd.ready := injectionPort.ready + } + } + for(i <- 0 until hardwareBreakpointCount){ + is(0x10 + i){ + when(io.bus.cmd.wr){ + hardwareBreakpoints(i).assignFromBits(io.bus.cmd.data) + } + } + } + } + } + + decode.insert(DO_EBREAK) := !haltIt && (decode.input(IS_EBREAK) || hardwareBreakpoints.map(hb => hb.valid && hb.pc === (decode.input(PC) >> 1)).foldLeft(False)(_ || _)) && allowEBreak + when(execute.arbitration.isValid && execute.input(DO_EBREAK)){ + execute.arbitration.haltByOther := True + busReadDataReg := execute.input(PC).asBits + when(stagesFromExecute.tail.map(_.arbitration.isValid).orR === False){ + iBusFetcher.haltIt() + execute.arbitration.flushIt := True + execute.arbitration.flushNext := True + haltIt := True + haltedByBreak := True + } + } + + when(haltIt) { + iBusFetcher.haltIt() + } + + when(stepIt && iBusFetcher.incoming()) { + iBusFetcher.haltIt() + when(decode.arbitration.isValid) { + haltIt := True + } + } + + //Avoid having two C instruction executed in a single step + if(pipeline.config.withRvc){ + val cleanStep = RegNext(stepIt && decode.arbitration.isFiring) init(False) + execute.arbitration.flushNext setWhen(cleanStep) + when(cleanStep){ + execute.arbitration.flushNext := True + iBusFetcher.forceNoDecode() + } + } + + io.resetOut := RegNext(resetIt) + + if(serviceExist(classOf[InterruptionInhibitor])) { + when(haltIt || stepIt) { + service(classOf[InterruptionInhibitor]).inhibateInterrupts() + } + } + + when(godmode) { + pipeline.plugins.foreach{ + case p : ExceptionInhibitor => p.inhibateException() + case _ => + } + pipeline.plugins.foreach{ + case p : PrivilegeService => p.forceMachine() + case _ => + } + pipeline.plugins.foreach{ + case p : PredictionInterface => p.inDebugNoFetch() + case _ => + } + if(pipeline.things.contains(DEBUG_BYPASS_CACHE)) pipeline(DEBUG_BYPASS_CACHE) := True + } + when(allowEBreak) { + pipeline.plugins.foreach { + case p: ExceptionInhibitor => p.inhibateEbreakException() + case _ => + } + } + + val wakeService = serviceElse(classOf[IWake], null) + if(wakeService != null) when(haltIt){ + wakeService.askWake() + } + }} + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DecoderSimplePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DecoderSimplePlugin.scala new file mode 100644 index 0000000..a525b77 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DecoderSimplePlugin.scala @@ -0,0 +1,402 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.core.internals.Literal +import spinal.lib._ +import vexriscv.demo.GenFull + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer + + +case class Masked(value : BigInt,care : BigInt){ + assert((value & ~care) == 0) + var isPrime = true + + def < (that: Masked) = value < that.value || value == that.value && ~care < ~that.care + + def intersects(x: Masked) = ((value ^ x.value) & care & x.care) == 0 + + def covers(x: Masked) = ((value ^ x.value) & care | (~x.care) & care) == 0 + + def setPrime(value : Boolean) = { + isPrime = value + this + } + + def mergeOneBitDifSmaller(x: Masked) = { + val bit = value - x.value + val ret = new Masked(value &~ bit, care & ~bit) + // ret.isPrime = isPrime || x.isPrime + isPrime = false + x.isPrime = false + ret + } + def isSimilarOneBitDifSmaller(x: Masked) = { + val diff = value - x.value + care == x.care && value > x.value && (diff & diff - 1) == 0 + } + + + def === (hard : Bits) : Bool = (hard & care) === (value & care) + + def toString(bitCount : Int) = (0 until bitCount).map(i => if(care.testBit(i)) (if(value.testBit(i)) "1" else "0") else "-").reverseIterator.reduce(_+_) +} + +class DecoderSimplePlugin(catchIllegalInstruction : Boolean = false, + throwIllegalInstruction : Boolean = false, + assertIllegalInstruction : Boolean = false, + forceLegalInstructionComputation : Boolean = false, + decoderIsolationBench : Boolean = false, + stupidDecoder : Boolean = false) extends Plugin[VexRiscv] with DecoderService { + override def add(encoding: Seq[(MaskedLiteral, Seq[(Stageable[_ <: BaseType], Any)])]): Unit = encoding.foreach(e => this.add(e._1,e._2)) + override def add(key: MaskedLiteral, values: Seq[(Stageable[_ <: BaseType], Any)]): Unit = { + val instructionModel = encodings.getOrElseUpdate(key,ArrayBuffer[(Stageable[_ <: BaseType], BaseType)]()) + values.map{case (a,b) => { + assert(!instructionModel.contains(a), s"Over specification of $a") + val value = b match { + case e: SpinalEnumElement[_] => e() + case e: BaseType => e + } + instructionModel += (a->value) + }} + } + + override def addDefault(key: Stageable[_ <: BaseType], value: Any): Unit = { + assert(!defaults.contains(key)) + defaults(key) = value match{ + case e : SpinalEnumElement[_] => e() + case e : BaseType => e + } + } + + def forceIllegal() : Unit = if(catchIllegalInstruction) pipeline.decode.input(pipeline.config.LEGAL_INSTRUCTION) := False + + val defaults = mutable.LinkedHashMap[Stageable[_ <: BaseType], BaseType]() + val encodings = mutable.LinkedHashMap[MaskedLiteral,ArrayBuffer[(Stageable[_ <: BaseType], BaseType)]]() + var decodeExceptionPort : Flow[ExceptionCause] = null + + + override def setup(pipeline: VexRiscv): Unit = { + if(!catchIllegalInstruction) { + SpinalWarning("This VexRiscv configuration is set without illegal instruction catch support. Some software may rely on it (ex: Rust)") + } + if(catchIllegalInstruction) { + val exceptionService = pipeline.plugins.filter(_.isInstanceOf[ExceptionService]).head.asInstanceOf[ExceptionService] + decodeExceptionPort = exceptionService.newExceptionPort(pipeline.decode).setName("decodeExceptionPort") + } + } + + val detectLegalInstructions = catchIllegalInstruction || throwIllegalInstruction || forceLegalInstructionComputation || assertIllegalInstruction + + object ASSERT_ERROR extends Stageable(Bool) + + override def build(pipeline: VexRiscv): Unit = { + import pipeline.config._ + import pipeline.decode._ + + val stageables = (encodings.flatMap(_._2.map(_._1)) ++ defaults.map(_._1)).toList.distinct + + + if(stupidDecoder){ + if (detectLegalInstructions) insert(LEGAL_INSTRUCTION) := False + for(stageable <- stageables){ + if(defaults.contains(stageable)){ + insert(stageable).assignFrom(defaults(stageable)) + } else { + insert(stageable).assignDontCare() + } + } + for((key, tasks) <- encodings){ + when(input(INSTRUCTION) === key){ + if (detectLegalInstructions) insert(LEGAL_INSTRUCTION) := True + for((stageable, value) <- tasks){ + insert(stageable).assignFrom(value) + } + } + } + } else { + var offset = 0 + var defaultValue, defaultCare = BigInt(0) + val offsetOf = mutable.LinkedHashMap[Stageable[_ <: BaseType], Int]() + + //Build defaults value and field offset map + stageables.foreach(e => { + defaults.get(e) match { + case Some(value) => { + value.head.source match { + case literal: EnumLiteral[_] => literal.fixEncoding(e.dataType.asInstanceOf[SpinalEnumCraft[_]].getEncoding) + case _ => + } + defaultValue += value.head.source.asInstanceOf[Literal].getValue << offset + defaultCare += ((BigInt(1) << e.dataType.getBitsWidth) - 1) << offset + + } + case _ => + } + offsetOf(e) = offset + offset += e.dataType.getBitsWidth + }) + + //Build spec + val spec = encodings.map { case (key, values) => + var decodedValue = defaultValue + var decodedCare = defaultCare + for ((e, literal) <- values) { + literal.head.source match { + case literal: EnumLiteral[_] => literal.fixEncoding(e.dataType.asInstanceOf[SpinalEnumCraft[_]].getEncoding) + case _ => + } + val offset = offsetOf(e) + decodedValue |= literal.head.source.asInstanceOf[Literal].getValue << offset + decodedCare |= ((BigInt(1) << e.dataType.getBitsWidth) - 1) << offset + } + (Masked(key.value, key.careAbout), Masked(decodedValue, decodedCare)) + } + + + // logic implementation + val decodedBits = Bits(stageables.foldLeft(0)(_ + _.dataType.getBitsWidth) bits) + decodedBits := Symplify(input(INSTRUCTION), spec, decodedBits.getWidth) + if (detectLegalInstructions) insert(LEGAL_INSTRUCTION) := Symplify.logicOf(input(INSTRUCTION), SymplifyBit.getPrimeImplicantsByTrueAndDontCare(spec.unzip._1.toSeq, Nil, 32)) + if (throwIllegalInstruction) { + input(LEGAL_INSTRUCTION) //Fill the request for later (prePopTask) + Component.current.addPrePopTask(() => arbitration.isValid clearWhen(!input(LEGAL_INSTRUCTION))) + } + if(assertIllegalInstruction){ + val reg = RegInit(False) setWhen(arbitration.isValid) clearWhen(arbitration.isRemoved || !arbitration.isStuck) + insert(ASSERT_ERROR) := arbitration.isValid || reg + } + + if(decoderIsolationBench){ + KeepAttribute(RegNext(KeepAttribute(RegNext(decodedBits.removeAssignments().asInput())))) + out(Bits(32 bits)).setName("instruction") := KeepAttribute(RegNext(KeepAttribute(RegNext(input(INSTRUCTION))))) + } + + //Unpack decodedBits and insert fields in the pipeline + offset = 0 + stageables.foreach(e => { + insert(e).assignFromBits(decodedBits(offset, e.dataType.getBitsWidth bits)) + // insert(e).assignFromBits(RegNext(decodedBits(offset, e.dataType.getBitsWidth bits))) + offset += e.dataType.getBitsWidth + }) + } + + if(catchIllegalInstruction){ + decodeExceptionPort.valid := arbitration.isValid && !input(LEGAL_INSTRUCTION) // ?? HalitIt to alow decoder stage to wait valid data from 2 stages cache cache ?? + decodeExceptionPort.code := 2 + decodeExceptionPort.badAddr := input(INSTRUCTION).asUInt + } + if(assertIllegalInstruction){ + pipeline.stages.tail.foreach(s => s.output(ASSERT_ERROR) clearWhen(s.arbitration.isRemoved)) + assert(!pipeline.stages.last.output(ASSERT_ERROR)) + } + } + + def bench(toplevel : VexRiscv): Unit ={ + toplevel.rework{ + import toplevel.config._ + toplevel.getAllIo.toList.foreach{io => + if(io.isInput) { io.assignDontCare()} + io.setAsDirectionLess() + } + toplevel.decode.input(INSTRUCTION).removeAssignments() + toplevel.decode.input(INSTRUCTION) := Delay((in Bits(32 bits)).setName("instruction"),2) + val stageables = encodings.flatMap(_._2.map(_._1)).toSet + stageables.foreach(e => out(RegNext(RegNext(toplevel.decode.insert(e)).setName(e.getName())))) + if(catchIllegalInstruction) out(RegNext(RegNext(toplevel.decode.insert(LEGAL_INSTRUCTION)).setName(LEGAL_INSTRUCTION.getName()))) + // toplevel.getAdditionalNodesRoot.clear() + } + } +} + +object DecodingBench extends App{ + SpinalVerilog{ + val top = GenFull.cpu() + top.service(classOf[DecoderSimplePlugin]).bench(top) + top + } +} + + +object Symplify{ + val cache = mutable.LinkedHashMap[Bits,mutable.LinkedHashMap[Masked,Bool]]() + def getCache(addr : Bits) = cache.getOrElseUpdate(addr,mutable.LinkedHashMap[Masked,Bool]()) + + //Generate terms logic for the given input + def logicOf(input : Bits,terms : Seq[Masked]) = terms.map(t => getCache(input).getOrElseUpdate(t,t === input)).asBits.orR + + //Decode 'input' b using an mapping[key, decoding] specification + def apply(input: Bits, mapping: Iterable[(Masked, Masked)],resultWidth : Int) : Bits = { + val addrWidth = widthOf(input) + (for(bitId <- 0 until resultWidth) yield{ + val trueTerm = mapping.filter { case (k,t) => (t.care.testBit(bitId) && t.value.testBit(bitId))}.map(_._1) + val falseTerm = mapping.filter { case (k,t) => (t.care.testBit(bitId) && !t.value.testBit(bitId))}.map(_._1) + val symplifiedTerms = SymplifyBit.getPrimeImplicantsByTrueAndFalse(trueTerm.toSeq, falseTerm.toSeq, addrWidth) + logicOf(input, symplifiedTerms) + }).asBits + } +} + +object SymplifyBit{ + + //Return a new term with only one bit difference with 'term' and not included in falseTerms. above => 0 to 1 dif, else 1 to 0 diff + def genImplicitDontCare(falseTerms: Seq[Masked], term: Masked, bits: Int, above: Boolean): Masked = { + for (i <- 0 until bits; if term.care.testBit(i)) { + var t: Masked = null + if(above) { + if (!term.value.testBit(i)) + t = Masked(term.value.setBit(i), term.care) + } else { + if (term.value.testBit(i)) + t = Masked(term.value.clearBit(i), term.care) + } + if (t != null && !falseTerms.exists(_.intersects(t))) { + t.isPrime = false + return t + } + } + null + } + + //Return primes implicants for the trueTerms, falseTerms spec. Default value is don't care + def getPrimeImplicantsByTrueAndFalse(trueTerms: Seq[Masked], falseTerms: Seq[Masked], inputWidth : Int): Seq[Masked] = { + val primes = mutable.LinkedHashSet[Masked]() + trueTerms.foreach(_.isPrime = true) + falseTerms.foreach(_.isPrime = true) + val trueTermByCareCount = (inputWidth to 0 by -1).map(b => trueTerms.filter(b == _.care.bitCount)) + //table[Vector[HashSet[Masked]]](careCount)(bitSetCount) + val table = trueTermByCareCount.map(c => (0 to inputWidth).map(b => collection.mutable.Set(c.filter(b == _.value.bitCount): _*))) + for (i <- 0 to inputWidth) { + //Expends explicit terms + for (j <- 0 until inputWidth - i){ + for(term <- table(i)(j)){ + table(i+1)(j) ++= table(i)(j+1).withFilter(_.isSimilarOneBitDifSmaller(term)).map(_.mergeOneBitDifSmaller(term)) + } + } + //Expends implicit don't care terms + for (j <- 0 until inputWidth-i) { + for (prime <- table(i)(j).withFilter(_.isPrime)) { + val dc = genImplicitDontCare(falseTerms, prime, inputWidth, true) + if (dc != null) + table(i+1)(j) += dc mergeOneBitDifSmaller prime + } + for (prime <- table(i)(j+1).withFilter(_.isPrime)) { + val dc = genImplicitDontCare(falseTerms, prime, inputWidth, false) + if (dc != null) + table(i+1)(j) += prime mergeOneBitDifSmaller dc + } + } + for (r <- table(i)) + for (p <- r; if p.isPrime) + primes += p + } + + def optimise() { + val duplicateds = primes.filter(prime => verifyTrueFalse(primes.filterNot(_ == prime), trueTerms, falseTerms)) + if(duplicateds.nonEmpty) { + primes -= duplicateds.maxBy(_.care.bitCount) + optimise() + } + } + + optimise() + + verifyTrueFalse(primes, trueTerms, falseTerms) + var duplication = 0 + for(prime <- primes){ + if(verifyTrueFalse(primes.filterNot(_ == prime), trueTerms, falseTerms)){ + duplication += 1 + } + } + if(duplication != 0){ + PendingError(s"Duplicated primes : $duplication") + } + primes.toSeq + } + + //Verify that the 'terms' doesn't violate the trueTerms ++ falseTerms spec + def verifyTrueFalse(terms : Iterable[Masked], trueTerms : Seq[Masked], falseTerms : Seq[Masked]): Boolean ={ + return (trueTerms.forall(trueTerm => terms.exists(_ covers trueTerm))) && (falseTerms.forall(falseTerm => !terms.exists(_ covers falseTerm))) + } + + def checkTrue(terms : Iterable[Masked], trueTerms : Seq[Masked]): Boolean ={ + return trueTerms.forall(trueTerm => terms.exists(_ covers trueTerm)) + } + + + def getPrimeImplicantsByTrue(trueTerms: Seq[Masked], inputWidth : Int) : Seq[Masked] = getPrimeImplicantsByTrueAndDontCare(trueTerms, Nil, inputWidth) + + // Return primes implicants for the trueTerms, default value is False. + // You can insert don't care values by adding non-prime implicants in the trueTerms + // Will simplify the trueTerms from the most constrained ones to the least constrained ones + def getPrimeImplicantsByTrueAndDontCare(trueTerms: Seq[Masked],dontCareTerms: Seq[Masked], inputWidth : Int): Seq[Masked] = { + val primes = mutable.LinkedHashSet[Masked]() + trueTerms.foreach(_.isPrime = true) + dontCareTerms.foreach(_.isPrime = false) + val termsByCareCount = (inputWidth to 0 by -1).map(b => (trueTerms ++ dontCareTerms).filter(b == _.care.bitCount)) + //table[Vector[HashSet[Masked]]](careCount)(bitSetCount) + val table = termsByCareCount.map(c => (0 to inputWidth).map(b => collection.mutable.Set(c.filter(m => b == m.value.bitCount): _*))) + for (i <- 0 to inputWidth) { + for (j <- 0 until inputWidth - i){ + for(term <- table(i)(j)){ + table(i+1)(j) ++= table(i)(j+1).withFilter(_.isSimilarOneBitDifSmaller(term)).map(_.mergeOneBitDifSmaller(term)) + } + } + for (r <- table(i)) + for (p <- r; if p.isPrime) + primes += p + } + + + def optimise() { + val duplicateds = primes.filter(prime => checkTrue(primes.filterNot(_ == prime), trueTerms)) + if(duplicateds.nonEmpty) { + primes -= duplicateds.maxBy(_.care.bitCount) + optimise() + } + } + + optimise() + + + var duplication = 0 + for(prime <- primes){ + if(checkTrue(primes.filterNot(_ == prime), trueTerms)){ + duplication += 1 + } + } + if(duplication != 0){ + PendingError(s"Duplicated primes : $duplication") + } + primes.toSeq + } + + def main(args: Array[String]) { + { + // val default = Masked(0, 0xF) + // val primeImplicants = List(4, 8, 10, 11, 12, 15).map(v => Masked(v, 0xF)) + // val dcImplicants = List(9, 14).map(v => Masked(v, 0xF).setPrime(false)) + // val reducedPrimeImplicants = getPrimeImplicantsByTrueAndDontCare(primeImplicants, dcImplicants, 4) + // println("UUT") + // println(reducedPrimeImplicants.map(_.toString(4)).mkString("\n")) + // println("REF") + // println("-100\n10--\n1--0\n1-1-") + } + + { + val primeImplicants = List(0).map(v => Masked(v, 0xF)) + val dcImplicants = (1 to 15).map(v => Masked(v, 0xF)) + val reducedPrimeImplicants = getPrimeImplicantsByTrueAndDontCare(primeImplicants, dcImplicants, 4) + println("UUT") + println(reducedPrimeImplicants.map(_.toString(4)).mkString("\n")) + } + { + val trueTerms = List(0, 15).map(v => Masked(v, 0xF)) + val falseTerms = List(3).map(v => Masked(v, 0xF)) + val primes = getPrimeImplicantsByTrueAndFalse(trueTerms, falseTerms, 4) + println(primes.map(_.toString(4)).mkString("\n")) + } + } +}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DivPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DivPlugin.scala new file mode 100644 index 0000000..c20dcb3 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DivPlugin.scala @@ -0,0 +1,75 @@ +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import spinal.core._ + +// DivPlugin was by the past a standalone plugin, but now it use the MulDivIterativePlugin implementation +class DivPlugin extends MulDivIterativePlugin(genMul = false, genDiv = true, mulUnrollFactor = 1, divUnrollFactor = 1) + +//import spinal.lib.math.MixedDivider +// +//class DivPlugin extends Plugin[VexRiscv]{ +// object IS_DIV extends Stageable(Bool) +// +// override def setup(pipeline: VexRiscv): Unit = { +// import Riscv._ +// import pipeline.config._ +// +// val actions = List[(Stageable[_ <: BaseType],Any)]( +// SRC1_CTRL -> Src1CtrlEnum.RS, +// SRC2_CTRL -> Src2CtrlEnum.RS, +// REGFILE_WRITE_VALID -> True, +// BYPASSABLE_EXECUTE_STAGE -> False, +// BYPASSABLE_MEMORY_STAGE -> True, +// RS1_USE -> True, +// RS2_USE -> True, +// IS_DIV -> True +// ) +// +// val decoderService = pipeline.service(classOf[DecoderService]) +// decoderService.addDefault(IS_DIV, False) +// decoderService.add(List( +// DIVX -> actions +// )) +// +// } +// +// override def build(pipeline: VexRiscv): Unit = { +// import pipeline._ +// import pipeline.config._ +// +// val divider = new MixedDivider(32, 32, true) //cmd >-> rsp +// +// //Send request to the divider component +// execute plug new Area { +// import execute._ +// +// divider.io.cmd.valid := False +// divider.io.cmd.numerator := input(SRC1) +// divider.io.cmd.denominator := input(SRC2) +// divider.io.cmd.signed := !input(INSTRUCTION)(12) +// +// when(arbitration.isValid && input(IS_DIV)) { +// divider.io.cmd.valid := !arbitration.isStuckByOthers && !arbitration.removeIt +// arbitration.haltItself := memory.arbitration.isValid && memory.input(IS_DIV) +// } +// } +// +// //Collect response from the divider component, REGFILE_WRITE_DATA overriding +// memory plug new Area{ +// import memory._ +// +// divider.io.flush := memory.arbitration.removeIt +// divider.io.rsp.ready := !arbitration.isStuckByOthers +// +// when(arbitration.isValid && input(IS_DIV)) { +// arbitration.haltItself := !divider.io.rsp.valid +// +// output(REGFILE_WRITE_DATA) := Mux(input(INSTRUCTION)(13), divider.io.rsp.remainder, divider.io.rsp.quotient).asBits +// } +// +// +// divider.io.rsp.payload.error.allowPruning +// } +// } +// } diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/DummyFencePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/DummyFencePlugin.scala new file mode 100644 index 0000000..7efbaac --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/DummyFencePlugin.scala @@ -0,0 +1,22 @@ +package vexriscv.plugin + +import spinal.core._ +import vexriscv.{VexRiscv, _} + +class DummyFencePlugin extends Plugin[VexRiscv]{ + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.add(FENCE_I, Nil) + decoderService.add(FENCE, Nil) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + //Dummy + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/ExternalInterruptArrayPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/ExternalInterruptArrayPlugin.scala new file mode 100644 index 0000000..43d32f0 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/ExternalInterruptArrayPlugin.scala @@ -0,0 +1,30 @@ +package vexriscv.plugin + +import spinal.core._ +import vexriscv.VexRiscv + +class ExternalInterruptArrayPlugin(arrayWidth : Int = 32, + machineMaskCsrId : Int = 0xBC0, + machinePendingsCsrId : Int = 0xFC0, + supervisorMaskCsrId : Int = 0x9C0, + supervisorPendingsCsrId : Int = 0xDC0) extends Plugin[VexRiscv]{ + var externalInterruptArray : Bits = null + + override def setup(pipeline: VexRiscv): Unit = { + externalInterruptArray = in(Bits(arrayWidth bits)).setName("externalInterruptArray") + } + + override def build(pipeline: VexRiscv): Unit = { + val csr = pipeline.service(classOf[CsrPlugin]) + val externalInterruptArrayBuffer = RegNext(externalInterruptArray) + def gen(maskCsrId : Int, pendingsCsrId : Int, interruptPin : Bool) = new Area { + val mask = Reg(Bits(arrayWidth bits)) init(0) + val pendings = mask & externalInterruptArrayBuffer + interruptPin.setAsDirectionLess() := pendings.orR + csr.rw(maskCsrId, mask) + csr.r(pendingsCsrId, pendings) + } + gen(machineMaskCsrId, machinePendingsCsrId, csr.externalInterrupt) + if(csr.config.supervisorGen) gen(supervisorMaskCsrId, supervisorPendingsCsrId, csr.externalInterruptS) + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/Fetcher.scala b/VexRiscv/src/main/scala/vexriscv/plugin/Fetcher.scala new file mode 100644 index 0000000..14450a1 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/Fetcher.scala @@ -0,0 +1,637 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ +import vexriscv.Riscv.IMM +import StreamVexPimper._ +import scala.collection.mutable.ArrayBuffer + + +//TODO val killLastStage = jump.pcLoad.valid || decode.arbitration.isRemoved +// DBUSSimple check memory halt execute optimization + +abstract class IBusFetcherImpl(val resetVector : BigInt, + val keepPcPlus4 : Boolean, + val decodePcGen : Boolean, + val compressedGen : Boolean, + val cmdToRspStageCount : Int, + val allowPcRegReusedForSecondStage : Boolean, + val injectorReadyCutGen : Boolean, + val prediction : BranchPrediction, + val historyRamSizeLog2 : Int, + val injectorStage : Boolean, + val relaxPredictorAddress : Boolean, + val fetchRedoGen : Boolean, + val predictionBuffer : Boolean = true) extends Plugin[VexRiscv] with JumpService with IBusFetcher{ + var prefetchExceptionPort : Flow[ExceptionCause] = null + var decodePrediction : DecodePredictionBus = null + var fetchPrediction : FetchPredictionBus = null + var dynamicTargetFailureCorrection : Flow[UInt] = null + var externalResetVector : UInt = null + assert(cmdToRspStageCount >= 1) +// assert(!(cmdToRspStageCount == 1 && !injectorStage)) + assert(!(compressedGen && !decodePcGen)) + var fetcherHalt : Bool = null + var forceNoDecodeCond : Bool = null + var pcValids : Vec[Bool] = null + def pcValid(stage : Stage) = pcValids(pipeline.indexOf(stage)) + var incomingInstruction : Bool = null + override def incoming() = incomingInstruction + + + override def withRvc(): Boolean = compressedGen + + var injectionPort : Stream[Bits] = null + override def getInjectionPort() = { + injectionPort = Stream(Bits(32 bits)) + injectionPort + } + def pcRegReusedForSecondStage = allowPcRegReusedForSecondStage && prediction != DYNAMIC_TARGET //TODO might not be required for DYNAMIC_TARGET + var predictionJumpInterface : Flow[UInt] = null + + override def haltIt(): Unit = fetcherHalt := True + override def forceNoDecode(): Unit = forceNoDecodeCond := True + case class JumpInfo(interface : Flow[UInt], stage: Stage, priority : Int) + val jumpInfos = ArrayBuffer[JumpInfo]() + override def createJumpInterface(stage: Stage, priority : Int = 0): Flow[UInt] = { + assert(stage != null) + val interface = Flow(UInt(32 bits)) + jumpInfos += JumpInfo(interface,stage, priority) + interface + } + + +// var decodeExceptionPort : Flow[ExceptionCause] = null + override def setup(pipeline: VexRiscv): Unit = { + fetcherHalt = False + forceNoDecodeCond = False + incomingInstruction = False + if(resetVector == null) externalResetVector = in(UInt(32 bits).setName("externalResetVector")) + + prediction match { + case NONE => + case STATIC | DYNAMIC => { + predictionJumpInterface = createJumpInterface(pipeline.decode) + decodePrediction = pipeline.service(classOf[PredictionInterface]).askDecodePrediction() + } + case DYNAMIC_TARGET => { + fetchPrediction = pipeline.service(classOf[PredictionInterface]).askFetchPrediction() + } + } + + pcValids = Vec(Bool, pipeline.stages.size) + } + + object IBUS_RSP + object DECOMPRESSOR + object INJECTOR_M2S + + def isDrivingDecode(s : Any): Boolean = { + if(injectorStage) return s == INJECTOR_M2S + s == IBUS_RSP || s == DECOMPRESSOR + } + + + + class FetchArea(pipeline : VexRiscv) extends Area { + import pipeline._ + import pipeline.config._ + val externalFlush = stages.map(_.arbitration.flushNext).orR + + def getFlushAt(s : Any, lastCond : Boolean = true): Bool = { + if(isDrivingDecode(s) && lastCond) pipeline.decode.arbitration.isRemoved else externalFlush + } + + //Arbitrate jump requests into pcLoad + val jump = new Area { + val sortedByStage = jumpInfos.sortWith((a, b) => { + (pipeline.indexOf(a.stage) > pipeline.indexOf(b.stage)) || + (pipeline.indexOf(a.stage) == pipeline.indexOf(b.stage) && a.priority > b.priority) + }) + val valids = sortedByStage.map(_.interface.valid) + val pcs = sortedByStage.map(_.interface.payload) + + val pcLoad = Flow(UInt(32 bits)) + pcLoad.valid := jumpInfos.map(_.interface.valid).orR + pcLoad.payload := MuxOH(OHMasking.first(valids.asBits), pcs) + } + + + + //The fetchPC pcReg can also be use for the second stage of the fetch + //When the fetcherHalt is set and the pipeline isn't stalled,, the pc is propagated to to the pcReg, which allow + //using the pc pipeline to get the next PC value for interrupts + val fetchPc = new Area{ + //PC calculation without Jump + val output = Stream(UInt(32 bits)) + val pcReg = Reg(UInt(32 bits)) init(if(resetVector != null) resetVector else externalResetVector) addAttribute(Verilator.public) + val correction = False + val correctionReg = RegInit(False) setWhen(correction) clearWhen(output.fire) + val corrected = correction || correctionReg + val pcRegPropagate = False + val booted = RegNext(True) init (False) + val inc = RegInit(False) clearWhen(correction || pcRegPropagate) setWhen(output.fire) clearWhen(!output.valid && output.ready) + val pc = pcReg + (inc ## B"00").asUInt + val predictionPcLoad = ifGen(prediction == DYNAMIC_TARGET) (Flow(UInt(32 bits))) + val redo = (fetchRedoGen || prediction == DYNAMIC_TARGET) generate Flow(UInt(32 bits)) + val flushed = False + + if(compressedGen) when(inc) { + pc(1) := False + } + + if(predictionPcLoad != null) { + when(predictionPcLoad.valid) { + correction := True + pc := predictionPcLoad.payload + } + } + if(redo != null) when(redo.valid){ + correction := True + pc := redo.payload + flushed := True + } + when(jump.pcLoad.valid) { + correction := True + pc := jump.pcLoad.payload + flushed := True + } + + when(booted && (output.ready || correction || pcRegPropagate)){ + pcReg := pc + } + + pc(0) := False + if(!compressedGen) pc(1) := False + + output.valid := !fetcherHalt && booted + output.payload := pc + } + + val decodePc = ifGen(decodePcGen)(new Area { + //PC calculation without Jump + val flushed = False + val pcReg = Reg(UInt(32 bits)) init(if(resetVector != null) resetVector else externalResetVector) addAttribute(Verilator.public) + val pcPlus = if(compressedGen) + pcReg + ((decode.input(IS_RVC)) ? U(2) | U(4)) + else + pcReg + 4 + + if (keepPcPlus4) KeepAttribute(pcPlus) + val injectedDecode = False + when(decode.arbitration.isFiring && !injectedDecode) { + pcReg := pcPlus + } + + val predictionPcLoad = ifGen(prediction == DYNAMIC_TARGET) (Flow(UInt(32 bits))) + if(prediction == DYNAMIC_TARGET) { + when(predictionPcLoad.valid && !forceNoDecodeCond) { + pcReg := predictionPcLoad.payload + } + } + + //application of the selected jump request + when(jump.pcLoad.valid && (!decode.arbitration.isStuck || decode.arbitration.isRemoved)) { + pcReg := jump.pcLoad.payload + flushed := True + } + }) + + + case class FetchRsp() extends Bundle { + val pc = UInt(32 bits) + val rsp = IBusSimpleRsp() + val isRvc = Bool() + } + + + val iBusRsp = new Area { + val redoFetch = False + val stages = Array.fill(cmdToRspStageCount + 1)(new Bundle { + val input = Stream(UInt(32 bits)) + val output = Stream(UInt(32 bits)) + val halt = Bool() + }) + + stages(0).input << fetchPc.output + for(s <- stages) { + s.halt := False + s.output << s.input.haltWhen(s.halt) + } + + if(fetchPc.redo != null) { + fetchPc.redo.valid := redoFetch + fetchPc.redo.payload := stages.last.input.payload + } + + val flush = (if(isDrivingDecode(IBUS_RSP)) pipeline.decode.arbitration.isRemoved || decode.arbitration.flushNext && !decode.arbitration.isStuck else externalFlush) || redoFetch + for((s,sNext) <- (stages, stages.tail).zipped) { + val sFlushed = if(s != stages.head) flush else False + val sNextFlushed = flush + if(s == stages.head && pcRegReusedForSecondStage) { + sNext.input.arbitrationFrom(s.output.toEvent().m2sPipeWithFlush(sNextFlushed, false, collapsBubble = false, flushInput = sFlushed)) + sNext.input.payload := fetchPc.pcReg + fetchPc.pcRegPropagate setWhen(sNext.input.ready) + } else { + sNext.input << s.output.m2sPipeWithFlush(sNextFlushed, false, collapsBubble = false, flushInput = sFlushed) + } + } + + val readyForError = True + val output = Stream(FetchRsp()) + incomingInstruction setWhen(stages.tail.map(_.input.valid).reduce(_ || _)) + } + + val decompressor = ifGen(decodePcGen)(new Area{ + val input = iBusRsp.output.clearValidWhen(iBusRsp.redoFetch) + val output = Stream(FetchRsp()) + val flush = getFlushAt(DECOMPRESSOR) + val flushNext = if(isDrivingDecode(DECOMPRESSOR)) decode.arbitration.flushNext else False + val consumeCurrent = if(isDrivingDecode(DECOMPRESSOR)) flushNext && output.ready else False + + val bufferValid = RegInit(False) + val bufferData = Reg(Bits(16 bits)) + + val isInputLowRvc = input.rsp.inst(1 downto 0) =/= 3 + val isInputHighRvc = input.rsp.inst(17 downto 16) =/= 3 + val throw2BytesReg = RegInit(False) + val throw2Bytes = throw2BytesReg || input.pc(1) + val unaligned = throw2Bytes || bufferValid + def aligned = !unaligned + + //Latch and patches are there to ensure that the decoded instruction do not mutate while being halted and unscheduled to ensure FpuPlugin cmd fork from consistancy + val bufferValidLatch = RegNextWhen(bufferValid, input.valid) + val throw2BytesLatch = RegNextWhen(throw2Bytes, input.valid) + val bufferValidPatched = input.valid ? bufferValid | bufferValidLatch + val throw2BytesPatched = input.valid ? throw2Bytes | throw2BytesLatch + + val raw = Mux( + sel = bufferValidPatched, + whenTrue = input.rsp.inst(15 downto 0) ## bufferData, + whenFalse = input.rsp.inst(31 downto 16) ## (throw2BytesPatched ? input.rsp.inst(31 downto 16) | input.rsp.inst(15 downto 0)) + ) + val isRvc = raw(1 downto 0) =/= 3 + val decompressed = RvcDecompressor(raw(15 downto 0), pipeline.config.withRvf, pipeline.config.withRvd) + output.valid := input.valid && !(throw2Bytes && !bufferValid && !isInputHighRvc) + output.pc := input.pc + output.isRvc := isRvc + output.rsp.inst := isRvc ? decompressed | raw + input.ready := output.ready && (!iBusRsp.stages.last.input.valid || flushNext || (!(bufferValid && isInputHighRvc) && !(aligned && isInputLowRvc && isInputHighRvc))) + + when(output.fire){ + throw2BytesReg := (aligned && isInputLowRvc && isInputHighRvc) || (bufferValid && isInputHighRvc) + } + val bufferFill = (aligned && isInputLowRvc && !isInputHighRvc) || (bufferValid && !isInputHighRvc) || (throw2Bytes && !isRvc && !isInputHighRvc) + when(output.ready && input.valid){ + bufferValid := False + } + when(output.ready && input.valid){ + bufferData := input.rsp.inst(31 downto 16) + bufferValid setWhen(bufferFill) + } + + when(flush || consumeCurrent){ + throw2BytesReg := False + bufferValid := False + } + + if(fetchPc.redo != null) { + fetchPc.redo.payload(1) setWhen(throw2BytesReg) + } + }) + + + def condApply[T](that : T, cond : Boolean)(func : (T) => T) = if(cond)func(that) else that + val injector = new Area { + val inputBeforeStage = condApply(if (decodePcGen) decompressor.output else iBusRsp.output, injectorReadyCutGen)(_.s2mPipe(externalFlush)) + if (injectorReadyCutGen) { + iBusRsp.readyForError.clearWhen(inputBeforeStage.valid) //Can't emit error if there is a instruction pending in the s2mPipe + incomingInstruction setWhen (inputBeforeStage.valid) + } + val decodeInput = (if (injectorStage) { + val flushStage = getFlushAt(INJECTOR_M2S) + val decodeInput = inputBeforeStage.m2sPipeWithFlush(flushStage, false, collapsBubble = false, flushInput = externalFlush) + decode.insert(INSTRUCTION_ANTICIPATED) := Mux(decode.arbitration.isStuck, decode.input(INSTRUCTION), inputBeforeStage.rsp.inst) + iBusRsp.readyForError.clearWhen(decodeInput.valid) //Can't emit error when there is a instruction pending in the injector stage buffer + incomingInstruction setWhen (decodeInput.valid) + decodeInput + } else { + inputBeforeStage + }) + + if(!decodePcGen) iBusRsp.readyForError.clearWhen(!pcValid(decode)) //Need to wait a valid PC on the decode stage, as it is use to fill CSR xEPC + + + def pcUpdatedGen(input : Bool, stucks : Seq[Bool], relaxedInput : Boolean, flush : Bool) : Seq[Bool] = { + stucks.scanLeft(input)((i, stuck) => { + val reg = RegInit(False) + if(!relaxedInput) when(flush) { + reg := False + } + when(!stuck) { + reg := i + } + if(relaxedInput || i != input) when(flush) { + reg := False + } + reg + }).tail + } + + val stagesFromExecute = stages.dropWhile(_ != execute).toList + val nextPcCalc = if (decodePcGen) new Area{ + val valids = pcUpdatedGen(True, False :: stagesFromExecute.map(_.arbitration.isStuck), true, decodePc.flushed) + pcValids := Vec(valids.takeRight(stages.size)) + } else new Area{ + val valids = pcUpdatedGen(True, iBusRsp.stages.tail.map(!_.input.ready) ++ (if (injectorStage) List(!decodeInput.ready) else Nil) ++ stagesFromExecute.map(_.arbitration.isStuck), false, fetchPc.flushed) + pcValids := Vec(valids.takeRight(stages.size)) + } + + decodeInput.ready := !decode.arbitration.isStuck + decode.arbitration.isValid := decodeInput.valid + decode.insert(PC) := (if (decodePcGen) decodePc.pcReg else decodeInput.pc) + decode.insert(INSTRUCTION) := decodeInput.rsp.inst + if (compressedGen) decode.insert(IS_RVC) := decodeInput.isRvc + + if (injectionPort != null) { + Component.current.addPrePopTask(() => { + val state = RegInit(U"000") + + injectionPort.ready := False + if(decodePcGen){ + decodePc.injectedDecode setWhen(state =/= 0) + } + switch(state) { + is(0) { //request pipelining + when(injectionPort.valid) { + state := 1 + } + } + is(1) { //Give time to propagate the payload + state := 2 + } + is(2){ //read regfile delay + decode.arbitration.isValid := True + decode.arbitration.haltItself := True + state := 3 + } + is(3){ //Do instruction + decode.arbitration.isValid := True + when(!decode.arbitration.isStuck) { + state := 4 + } + } + is(4){ //request pipelining + injectionPort.ready := True + state := 0 + } + } + + //Check if the decode instruction is driven by a register + val instructionDriver = try { + decode.input(INSTRUCTION).getDrivingReg + } catch { + case _: Throwable => null + } + if (instructionDriver != null) { //If yes => + //Insert the instruction by writing the "fetch to decode instruction register", + // Work even if it need to cross some hierarchy (caches) + instructionDriver.component.rework { + when(state.pull() =/= 0) { + instructionDriver := injectionPort.payload.pull() + } + } + } else { + //Insert the instruction via a mux in the decode stage + when(state =/= 0) { + decode.input(INSTRUCTION) := RegNext(injectionPort.payload) + } + } + }) + } + + Component.current.addPrePopTask(() => { + decode.arbitration.isValid clearWhen(forceNoDecodeCond) + }) + + //Formal verification signals generation, miss prediction stuff ? + val formal = new Area { + val raw = if(compressedGen) decompressor.raw else inputBeforeStage.rsp.inst + val rawInDecode = Delay(raw, if(injectorStage) 1 else 0, when = decodeInput.ready) + decode.insert(FORMAL_INSTRUCTION) := rawInDecode + + decode.insert(FORMAL_PC_NEXT) := (if (compressedGen) + decode.input(PC) + ((decode.input(IS_RVC)) ? U(2) | U(4)) + else + decode.input(PC) + 4) + + if(decodePc != null && decodePc.predictionPcLoad != null){ + when(decodePc.predictionPcLoad.valid){ + decode.insert(FORMAL_PC_NEXT) := decodePc.predictionPcLoad.payload + } + } + + jumpInfos.foreach(info => { + when(info.interface.valid) { + info.stage.output(FORMAL_PC_NEXT) := info.interface.payload + } + }) + } + } + + def stage1ToInjectorPipe[T <: Data](input : T): (T, T, T) ={ + val iBusRspContext = iBusRsp.stages.drop(1).dropRight(1).foldLeft(input)((data,stage) => RegNextWhen(data, stage.output.ready)) + + val iBusRspContextOutput = cloneOf(input) + iBusRspContextOutput := iBusRspContext + val injectorContext = Delay(iBusRspContextOutput, cycleCount=if(injectorStage) 1 else 0, when=injector.decodeInput.ready) + val injectorContextWire = cloneOf(input) //Allow combinatorial override + injectorContextWire := injectorContext + (iBusRspContext, iBusRspContextOutput, injectorContextWire) + } + + val predictor = prediction match { + case NONE => + case STATIC | DYNAMIC => { + def historyWidth = 2 + val dynamic = ifGen(prediction == DYNAMIC) (new Area { + case class BranchPredictorLine() extends Bundle{ + val history = SInt(historyWidth bits) + } + + val historyCache = Mem(BranchPredictorLine(), 1 << historyRamSizeLog2) + val historyWrite = historyCache.writePort + val historyWriteLast = RegNextWhen(historyWrite, iBusRsp.stages(0).output.ready) + val hazard = historyWriteLast.valid && historyWriteLast.address === (iBusRsp.stages(0).input.payload >> 2).resized + + case class DynamicContext() extends Bundle{ + val hazard = Bool + val line = BranchPredictorLine() + } + val fetchContext = DynamicContext() + fetchContext.hazard := hazard + fetchContext.line := historyCache.readSync((fetchPc.output.payload >> 2).resized, iBusRsp.stages(0).output.ready || externalFlush) + + object PREDICTION_CONTEXT extends Stageable(DynamicContext()) + decode.insert(PREDICTION_CONTEXT) := stage1ToInjectorPipe(fetchContext)._3 + val decodeContextPrediction = decode.input(PREDICTION_CONTEXT).line.history.msb + + val branchStage = decodePrediction.stage + val branchContext = branchStage.input(PREDICTION_CONTEXT) + val moreJump = decodePrediction.rsp.wasWrong ^ branchContext.line.history.msb + + historyWrite.address := branchStage.input(PC)(2, historyRamSizeLog2 bits) + (if(pipeline.config.withRvc) + ((!branchStage.input(IS_RVC) && branchStage.input(PC)(1)) ? U(1) | U(0)) + else + U(0)) + + historyWrite.data.history := branchContext.line.history + (moreJump ? S(-1) | S(1)) + val sat = (branchContext.line.history === (moreJump ? S(branchContext.line.history.minValue) | S(branchContext.line.history.maxValue))) + historyWrite.valid := !branchContext.hazard && branchStage.arbitration.isFiring && branchStage.input(BRANCH_CTRL) === BranchCtrlEnum.B && !sat + }) + + + val imm = IMM(decode.input(INSTRUCTION)) + + val conditionalBranchPrediction = prediction match { + case STATIC => imm.b_sext.msb + case DYNAMIC => dynamic.decodeContextPrediction + } + + decodePrediction.cmd.hadBranch := decode.input(BRANCH_CTRL) === BranchCtrlEnum.JAL || (decode.input(BRANCH_CTRL) === BranchCtrlEnum.B && conditionalBranchPrediction) + + val noPredictionOnMissaligned = (!pipeline.config.withRvc) generate new Area{ + val missaligned = decode.input(BRANCH_CTRL).mux( + BranchCtrlEnum.JAL -> imm.j_sext(1), + default -> imm.b_sext(1) + ) + decodePrediction.cmd.hadBranch clearWhen(missaligned) + } + + //TODO no more fireing depedancies + predictionJumpInterface.valid := decode.arbitration.isValid && decodePrediction.cmd.hadBranch + predictionJumpInterface.payload := decode.input(PC) + ((decode.input(BRANCH_CTRL) === BranchCtrlEnum.JAL) ? imm.j_sext | imm.b_sext).asUInt + decode.arbitration.flushNext setWhen(predictionJumpInterface.valid) + + if(relaxPredictorAddress) KeepAttribute(predictionJumpInterface.payload) + } + case DYNAMIC_TARGET => new Area{ +// assert(!compressedGen || cmdToRspStageCount == 1, "Can't combine DYNAMIC_TARGET and RVC as it could stop the instruction fetch mid-air") + + case class BranchPredictorLine() extends Bundle{ + val source = Bits(30 - historyRamSizeLog2 bits) + val branchWish = UInt(2 bits) + val last2Bytes = ifGen(compressedGen)(Bool) + val target = UInt(32 bits) + } + + val history = Mem(BranchPredictorLine(), 1 << historyRamSizeLog2) + val historyWriteDelayPatched = history.writePort + val historyWrite = cloneOf(historyWriteDelayPatched) + historyWriteDelayPatched.valid := historyWrite.valid + historyWriteDelayPatched.address := (if(predictionBuffer) historyWrite.address - 1 else historyWrite.address) + historyWriteDelayPatched.data := historyWrite.data + + + val writeLast = RegNextWhen(historyWriteDelayPatched, iBusRsp.stages(0).output.ready) + + //Avoid write to read hazard + val buffer = predictionBuffer generate new Area{ + val line = history.readSync((iBusRsp.stages(0).input.payload >> 2).resized, iBusRsp.stages(0).output.ready) + val pcCorrected = RegNextWhen(fetchPc.corrected, iBusRsp.stages(0).input.ready) + val hazard = (writeLast.valid && writeLast.address === (iBusRsp.stages(1).input.payload >> 2).resized) + } + + val (line, hazard) = predictionBuffer match { + case true => + (RegNextWhen(buffer.line, iBusRsp.stages(0).output.ready), + RegNextWhen(buffer.hazard, iBusRsp.stages(0).output.ready) || buffer.pcCorrected) + case false => + (history.readSync((iBusRsp.stages(0).input.payload >> 2).resized, + iBusRsp.stages(0).output.ready), writeLast.valid && writeLast.address === (iBusRsp.stages(1).input.payload >> 2).resized) + } + + val hit = line.source === (iBusRsp.stages(1).input.payload.asBits >> 2 + historyRamSizeLog2) + if(compressedGen) hit clearWhen(!line.last2Bytes && iBusRsp.stages(1).input.payload(1)) + + fetchPc.predictionPcLoad.valid := line.branchWish.msb && hit && !hazard && iBusRsp.stages(1).input.valid + fetchPc.predictionPcLoad.payload := line.target + + case class PredictionResult() extends Bundle{ + val hazard = Bool + val hit = Bool + val line = BranchPredictorLine() + } + + val fetchContext = PredictionResult() + fetchContext.hazard := hazard + fetchContext.hit := hit + fetchContext.line := line + + val (iBusRspContext, iBusRspContextOutput, injectorContext) = stage1ToInjectorPipe(fetchContext) + + object PREDICTION_CONTEXT extends Stageable(PredictionResult()) + pipeline.decode.insert(PREDICTION_CONTEXT) := injectorContext + val branchStage = fetchPrediction.stage + val branchContext = branchStage.input(PREDICTION_CONTEXT) + + fetchPrediction.cmd.hadBranch := branchContext.hit && !branchContext.hazard && branchContext.line.branchWish.msb + fetchPrediction.cmd.targetPc := branchContext.line.target + + + historyWrite.valid := False + historyWrite.address := fetchPrediction.rsp.sourceLastWord(2, historyRamSizeLog2 bits) + historyWrite.data.source := fetchPrediction.rsp.sourceLastWord.asBits >> 2 + historyRamSizeLog2 + historyWrite.data.target := fetchPrediction.rsp.finalPc + if(compressedGen) historyWrite.data.last2Bytes := fetchPrediction.stage.input(PC)(1) && fetchPrediction.stage.input(IS_RVC) + + when(fetchPrediction.rsp.wasRight) { + historyWrite.valid := branchContext.hit + historyWrite.data.branchWish := branchContext.line.branchWish + (branchContext.line.branchWish === 2).asUInt - (branchContext.line.branchWish === 1).asUInt + } otherwise { + when(branchContext.hit) { + historyWrite.valid := True + historyWrite.data.branchWish := branchContext.line.branchWish - (branchContext.line.branchWish.msb).asUInt + (!branchContext.line.branchWish.msb).asUInt + } otherwise { + historyWrite.valid := True + historyWrite.data.branchWish := "10" + } + } + + historyWrite.valid clearWhen(branchContext.hazard || !branchStage.arbitration.isFiring) + + val compressor = compressedGen generate new Area{ + val predictionBranch = iBusRspContext.hit && !iBusRspContext.hazard && iBusRspContext.line.branchWish(1) + val unalignedWordIssue = iBusRsp.output.valid && predictionBranch && iBusRspContext.line.last2Bytes && Mux(decompressor.unaligned, !decompressor.isInputHighRvc, decompressor.isInputLowRvc && !decompressor.isInputHighRvc) + + when(unalignedWordIssue){ + historyWrite.valid := True + historyWrite.address := (iBusRsp.stages(1).input.payload >> 2).resized + historyWrite.data.branchWish := 0 + + iBusRsp.redoFetch := True + } + + //Do not trigger prediction hit when it is one for the upper RVC word and we aren't there yet + iBusRspContextOutput.hit clearWhen(iBusRspContext.line.last2Bytes && (decompressor.bufferValid || (!decompressor.throw2Bytes && decompressor.isInputLowRvc))) + + decodePc.predictionPcLoad.valid := injectorContext.line.branchWish.msb && injectorContext.hit && !injectorContext.hazard && injector.decodeInput.fire + decodePc.predictionPcLoad.payload := injectorContext.line.target + + //Clean the RVC buffer when a prediction was made + when(iBusRspContext.line.branchWish.msb && iBusRspContextOutput.hit && !iBusRspContext.hazard && decompressor.output.fire){ + decompressor.bufferValid := False + decompressor.throw2BytesReg := False + decompressor.input.ready := True //Drop the remaining byte if any + } + } + } + } + + def stageXToIBusRsp[T <: Data](stage : Any, input : T): (T) ={ + iBusRsp.stages.dropWhile(_ != stage).tail.foldLeft(input)((data,stage) => RegNextWhen(data, stage.output.ready)) + } + + } +}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/FormalPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/FormalPlugin.scala new file mode 100644 index 0000000..2d70ebd --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/FormalPlugin.scala @@ -0,0 +1,135 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import vexriscv.VexRiscv + +case class RvfiPortRsRead() extends Bundle{ + val addr = UInt(5 bits) + val rdata = Bits(32 bits) +} + +case class RvfiPortRsWrite() extends Bundle{ + val addr = UInt(5 bits) + val wdata = Bits(32 bits) +} + +case class RvfiPortPc() extends Bundle{ + val rdata = UInt(32 bits) + val wdata = UInt(32 bits) +} + + +case class RvfiPortMem() extends Bundle{ + val addr = UInt(32 bits) + val rmask = Bits(4 bits) + val wmask = Bits(4 bits) + val rdata = Bits(32 bits) + val wdata = Bits(32 bits) +} + +case class RvfiPort() extends Bundle with IMasterSlave { + val valid = Bool + val order = UInt(64 bits) + val insn = Bits(32 bits) + val trap = Bool + val halt = Bool + val intr = Bool + val mode = Bits(2 bits) + val ixl = Bits(2 bits) + val rs1 = RvfiPortRsRead() + val rs2 = RvfiPortRsRead() + val rd = RvfiPortRsWrite() + val pc = RvfiPortPc() + val mem = RvfiPortMem() + + override def asMaster(): Unit = out(this) +} + + +//Tool stuff +//https://www.reddit.com/r/yosys/comments/77g5hn/unsupported_cell_type_error_adff/ +//rd_addr == 0 => no rd_wdata check +//instruction that doesn't use RSx have to force the formal port address to zero + +//feature added +//Halt CPU on decoding exception + +//VexRiscv changes +// + +//VexRiscv bug +//1) pcManagerService.createJumpInterface(pipeline.execute) +// pcManagerService.createJumpInterface(if(earlyBranch) pipeline.execute else pipeline.memory) +//2) JALR => clear PC(0) +//3) input(INSTRUCTION)(5) REGFILE_WRITE_VALID memory read with exception would not fire properly + +class FormalPlugin extends Plugin[VexRiscv]{ + + var rvfi : RvfiPort = null + + + override def setup(pipeline: VexRiscv): Unit = { + rvfi = master(RvfiPort()).setName("rvfi") + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + import vexriscv.Riscv._ + + writeBack plug new Area{ + import writeBack._ + + val order = Reg(UInt(64 bits)) init(0) + when(arbitration.isFiring){ + order := order + 1 + } + + + rvfi.valid := arbitration.isFiring + rvfi.order := order + rvfi.insn := output(FORMAL_INSTRUCTION) + rvfi.trap := False + rvfi.halt := False + rvfi.intr := False + rvfi.mode := 3 + rvfi.ixl := 1 +// rvfi.rs1.addr := output(INSTRUCTION)(rs1Range).asUInt +// rvfi.rs2.addr := output(INSTRUCTION)(rs2Range).asUInt +// rvfi.rs1.rdata := output(RS1) +// rvfi.rs2.rdata := output(RS2) + rvfi.rs1.addr := output(RS1_USE) ? output(INSTRUCTION)(rs1Range).asUInt | U(0) + rvfi.rs2.addr := output(RS2_USE) ? output(INSTRUCTION)(rs2Range).asUInt | U(0) + rvfi.rs1.rdata := output(RS1_USE) ? output(RS1) | B(0) + rvfi.rs2.rdata := output(RS2_USE) ? output(RS2) | B(0) + rvfi.rd.addr := output(REGFILE_WRITE_VALID) ? (output(INSTRUCTION)(rdRange).asUInt) | U(0) + rvfi.rd.wdata := output(REGFILE_WRITE_VALID) ? output(REGFILE_WRITE_DATA) | B(0) + rvfi.pc.rdata := output(PC) + rvfi.pc.wdata := output(FORMAL_PC_NEXT) + rvfi.mem.addr := output(FORMAL_MEM_ADDR) + rvfi.mem.rmask := output(FORMAL_MEM_RMASK) + rvfi.mem.wmask := output(FORMAL_MEM_WMASK) + rvfi.mem.rdata := output(FORMAL_MEM_RDATA) + rvfi.mem.wdata := output(FORMAL_MEM_WDATA) + + val haltRequest = False + stages.map(s => { + when(s.arbitration.isValid && s.output(FORMAL_HALT)){ //Stage is exception halted + when(stages.drop(indexOf(s) + 1).map(!_.arbitration.isValid).foldLeft(True)(_ && _)){ //There nothing in futher stages + haltRequest := True + } + } + }) + + when(Delay(haltRequest, 5, init=False)){ //Give time for value propagation from decode stage to writeback stage + rvfi.valid := True + rvfi.trap := True + rvfi.halt := True + } + + val haltFired = RegInit(False) setWhen(rvfi.valid && rvfi.halt) + rvfi.valid clearWhen(haltFired) + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/FpuPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/FpuPlugin.scala new file mode 100644 index 0000000..3e664f5 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/FpuPlugin.scala @@ -0,0 +1,314 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.core.internals.{BoolLiteral, Literal} +import spinal.lib._ +import vexriscv._ +import vexriscv.Riscv._ +import vexriscv.ip.fpu._ + +import scala.collection.mutable.ArrayBuffer + +class FpuPlugin(externalFpu : Boolean = false, + simHalt : Boolean = false, + val p : FpuParameter) extends Plugin[VexRiscv] with VexRiscvRegressionArg { + + object FPU_ENABLE extends Stageable(Bool()) + object FPU_COMMIT extends Stageable(Bool()) + object FPU_COMMIT_SYNC extends Stageable(Bool()) + object FPU_COMMIT_LOAD extends Stageable(Bool()) + object FPU_RSP extends Stageable(Bool()) + object FPU_FORKED extends Stageable(Bool()) + object FPU_OPCODE extends Stageable(FpuOpcode()) + object FPU_ARG extends Stageable(Bits(2 bits)) + object FPU_FORMAT extends Stageable(FpuFormat()) + + var port : FpuPort = null //Commit port is already isolated + + override def getVexRiscvRegressionArgs(): Seq[String] = { + var args = List[String]() + args :+= "RVF=yes" + if(p.withDouble) args :+= "RVD=yes" + args + } + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + + type ENC = (Stageable[_ <: BaseType],Any) + + val intRfWrite = List[ENC]( + FPU_ENABLE -> True, + FPU_COMMIT -> False, + FPU_RSP -> True, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False + ) + + val floatRfWrite = List[ENC]( + FPU_ENABLE -> True, + FPU_COMMIT -> True, + FPU_RSP -> False + ) + + val addSub = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.ADD + val mul = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.MUL + val fma = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.FMA + val div = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.DIV + val sqrt = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.SQRT + val fsgnj = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.SGNJ + val fminMax = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.MIN_MAX + val fmvWx = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.FMV_W_X :+ RS1_USE -> True + val fcvtI2f = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.I2F :+ RS1_USE -> True + val fcvtxx = floatRfWrite :+ FPU_OPCODE -> FpuOpcode.FCVT_X_X + + val fcmp = intRfWrite :+ FPU_OPCODE -> FpuOpcode.CMP + val fclass = intRfWrite :+ FPU_OPCODE -> FpuOpcode.FCLASS + val fmvXw = intRfWrite :+ FPU_OPCODE -> FpuOpcode.FMV_X_W + val fcvtF2i = intRfWrite :+ FPU_OPCODE -> FpuOpcode.F2I + + val fl = List[ENC]( + FPU_ENABLE -> True, + FPU_OPCODE -> FpuOpcode.LOAD, + FPU_COMMIT -> True, + FPU_RSP -> False + ) + + val fs = List[ENC]( + FPU_ENABLE -> True, + FPU_OPCODE -> FpuOpcode.STORE, + FPU_COMMIT -> False, + FPU_RSP -> True + ) + + + def arg(v : Int) = FPU_ARG -> B(v, 2 bits) + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(FPU_ENABLE, False) + + val f32 = FPU_FORMAT -> FpuFormat.FLOAT + val f64 = FPU_FORMAT -> FpuFormat.DOUBLE + + decoderService.add(List( + FADD_S -> (addSub :+ f32 :+ arg(0)), + FSUB_S -> (addSub :+ f32 :+ arg(1)), + FMADD_S -> (fma :+ f32 :+ arg(0)), + FMSUB_S -> (fma :+ f32 :+ arg(2)), + FNMADD_S -> (fma :+ f32 :+ arg(3)), + FNMSUB_S -> (fma :+ f32 :+ arg(1)), + FMUL_S -> (mul :+ f32 :+ arg(0)), + FDIV_S -> (div :+ f32 ), + FSQRT_S -> (sqrt :+ f32 ), + FLW -> (fl :+ f32 ), + FSW -> (fs :+ f32 ), + FCVT_S_WU -> (fcvtI2f :+ f32 :+ arg(0)), + FCVT_S_W -> (fcvtI2f :+ f32 :+ arg(1)), + FCVT_WU_S -> (fcvtF2i :+ f32 :+ arg(0)), + FCVT_W_S -> (fcvtF2i :+ f32 :+ arg(1)), + FCLASS_S -> (fclass :+ f32 ), + FLE_S -> (fcmp :+ f32 :+ arg(0)), + FEQ_S -> (fcmp :+ f32 :+ arg(2)), + FLT_S -> (fcmp :+ f32 :+ arg(1)), + FSGNJ_S -> (fsgnj :+ f32 :+ arg(0)), + FSGNJN_S -> (fsgnj :+ f32 :+ arg(1)), + FSGNJX_S -> (fsgnj :+ f32 :+ arg(2)), + FMIN_S -> (fminMax :+ f32 :+ arg(0)), + FMAX_S -> (fminMax :+ f32 :+ arg(1)), + FMV_X_W -> (fmvXw :+ f32 ), + FMV_W_X -> (fmvWx :+ f32 ) + )) + + if(p.withDouble){ + decoderService.add(List( + FADD_D -> (addSub :+ f64 :+ arg(0)), + FSUB_D -> (addSub :+ f64 :+ arg(1)), + FMADD_D -> (fma :+ f64 :+ arg(0)), + FMSUB_D -> (fma :+ f64 :+ arg(2)), + FNMADD_D -> (fma :+ f64 :+ arg(3)), + FNMSUB_D -> (fma :+ f64 :+ arg(1)), + FMUL_D -> (mul :+ f64 :+ arg(0)), + FDIV_D -> (div :+ f64 ), + FSQRT_D -> (sqrt :+ f64 ), + FLD -> (fl :+ f64 ), + FSD -> (fs :+ f64 ), + FCVT_D_WU -> (fcvtI2f :+ f64 :+ arg(0)), + FCVT_D_W -> (fcvtI2f :+ f64 :+ arg(1)), + FCVT_WU_D -> (fcvtF2i :+ f64 :+ arg(0)), + FCVT_W_D -> (fcvtF2i :+ f64 :+ arg(1)), + FCLASS_D -> (fclass :+ f64 ), + FLE_D -> (fcmp :+ f64 :+ arg(0)), + FEQ_D -> (fcmp :+ f64 :+ arg(2)), + FLT_D -> (fcmp :+ f64 :+ arg(1)), + FSGNJ_D -> (fsgnj :+ f64 :+ arg(0)), + FSGNJN_D -> (fsgnj :+ f64 :+ arg(1)), + FSGNJX_D -> (fsgnj :+ f64 :+ arg(2)), + FMIN_D -> (fminMax :+ f64 :+ arg(0)), + FMAX_D -> (fminMax :+ f64 :+ arg(1)), + FCVT_D_S -> (fcvtxx :+ f32), + FCVT_S_D -> (fcvtxx :+ f64) + )) + } + //TODO FMV_X_X + doubles + + port = FpuPort(p).addTag(Verilator.public) + if(externalFpu) master(port) + + val dBusEncoding = pipeline.service(classOf[DBusEncodingService]) + dBusEncoding.addLoadWordEncoding(FLW) + dBusEncoding.addStoreWordEncoding(FSW) + if(p.withDouble) { + dBusEncoding.addLoadWordEncoding(FLD) + dBusEncoding.addStoreWordEncoding(FSD) + } + +// exposeEncoding() + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + import Riscv._ + + val internal = (!externalFpu).generate (pipeline plug new Area{ + val fpu = FpuCore(1, p) + if(simHalt) { + val cmdHalt = in(Bool).setName("fpuCmdHalt").addAttribute(Verilator.public) + val commitHalt = in(Bool).setName("fpuCommitHalt").addAttribute(Verilator.public) + val rspHalt = in(Bool).setName("fpuRspHalt").addAttribute(Verilator.public) + fpu.io.port(0).cmd << port.cmd.haltWhen(cmdHalt) + fpu.io.port(0).commit << port.commit.haltWhen(commitHalt) + fpu.io.port(0).rsp.haltWhen(rspHalt) >> port.rsp + fpu.io.port(0).completion <> port.completion + } else { + fpu.io.port(0).cmd << port.cmd + fpu.io.port(0).commit << port.commit + fpu.io.port(0).rsp >> port.rsp + fpu.io.port(0).completion <> port.completion + } + }) + + + val csr = pipeline plug new Area{ + val pendings = Reg(UInt(6 bits)) init(0) + pendings := pendings + U(port.cmd.fire) - U(port.completion.fire) - U(port.rsp.fire) + + val hasPending = pendings =/= 0 + + val flags = Reg(FpuFlags()) + flags.NV init(False) setWhen(port.completion.fire && port.completion.flags.NV) + flags.DZ init(False) setWhen(port.completion.fire && port.completion.flags.DZ) + flags.OF init(False) setWhen(port.completion.fire && port.completion.flags.OF) + flags.UF init(False) setWhen(port.completion.fire && port.completion.flags.UF) + flags.NX init(False) setWhen(port.completion.fire && port.completion.flags.NX) + + val service = pipeline.service(classOf[CsrInterface]) + val rm = Reg(Bits(3 bits)) init(0) + + service.rw(CSR.FCSR, 5, rm) + service.rw(CSR.FCSR, 0, flags) + service.rw(CSR.FRM, 0, rm) + service.rw(CSR.FFLAGS, 0, flags) + + val csrActive = service.duringAny() + execute.arbitration.haltByOther setWhen(csrActive && hasPending) // pessimistic + + val fs = Reg(Bits(2 bits)) init(1) + val sd = fs === 3 + + when(stages.last.arbitration.isFiring && stages.last.input(FPU_ENABLE) && stages.last.input(FPU_OPCODE) =/= FpuOpcode.STORE){ + fs := 3 //DIRTY + } + + service.rw(CSR.SSTATUS, 13, fs) + service.rw(CSR.MSTATUS, 13, fs) + + service.r(CSR.SSTATUS, 31, sd) + service.r(CSR.MSTATUS, 31, sd) + } + + decode plug new Area{ + import decode._ + + //Maybe it might be better to not fork before fire to avoid RF stall on commits + val forked = Reg(Bool) setWhen(port.cmd.fire) clearWhen(!arbitration.isStuck) init(False) + + val hazard = csr.pendings.msb || csr.csrActive + + arbitration.haltItself setWhen(arbitration.isValid && input(FPU_ENABLE) && hazard) + arbitration.haltItself setWhen(port.cmd.isStall) + + val iRoundMode = input(INSTRUCTION)(funct3Range) + val roundMode = (input(INSTRUCTION)(funct3Range) === B"111") ? csr.rm | input(INSTRUCTION)(funct3Range) + + port.cmd.valid := arbitration.isValid && input(FPU_ENABLE) && !forked && !hazard + port.cmd.opcode := input(FPU_OPCODE) + port.cmd.arg := input(FPU_ARG) + port.cmd.rs1 := input(INSTRUCTION)(rs1Range).asUInt + port.cmd.rs2 := input(INSTRUCTION)(rs2Range).asUInt + port.cmd.rs3 := input(INSTRUCTION)(rs3Range).asUInt + port.cmd.rd := input(INSTRUCTION)(rdRange).asUInt + port.cmd.format := (if(p.withDouble) input(FPU_FORMAT) else FpuFormat.FLOAT()) + port.cmd.roundMode := roundMode.as(FpuRoundMode()) + + insert(FPU_FORKED) := forked || port.cmd.fire + + insert(FPU_COMMIT_SYNC) := List(FpuOpcode.LOAD, FpuOpcode.FMV_W_X, FpuOpcode.I2F).map(_ === input(FPU_OPCODE)).orR + insert(FPU_COMMIT_LOAD) := input(FPU_OPCODE) === FpuOpcode.LOAD + + if(serviceExist(classOf[IWake])) when(forked){ + service(classOf[IWake]).askWake() //Ensure that no WFI followed by a FPU stall the FPU interface for other CPU + } + } + + writeBack plug new Area{ //WARNING IF STAGE CHANGE, update the regression rsp capture filter for the golden model (top->VexRiscv->lastStageIsFiring) + import writeBack._ + + val dBusEncoding = pipeline.service(classOf[DBusEncodingService]) + val isRsp = input(FPU_FORKED) && input(FPU_RSP) + val isCommit = input(FPU_FORKED) && input(FPU_COMMIT) + val storeFormated = CombInit(port.rsp.value) + if(p.withDouble) when(!input(INSTRUCTION)(12)){ + storeFormated(32, 32 bits) := port.rsp.value(0, 32 bits) + } + //Manage $store and port.rsp + port.rsp.ready := False + when(isRsp){ + when(arbitration.isValid) { + dBusEncoding.bypassStore(storeFormated) + output(REGFILE_WRITE_DATA) := port.rsp.value(31 downto 0) + when(!arbitration.isStuck && !arbitration.isRemoved){ + csr.flags.NV setWhen(port.rsp.NV) + csr.flags.NX setWhen(port.rsp.NX) + } + } + when(!port.rsp.valid){ + arbitration.haltByOther := True + } elsewhen(!arbitration.haltItself){ + port.rsp.ready := True + } + } + + // Manage $load + val commit = Stream(FpuCommit(p)).addTag(Verilator.public) + commit.valid := isCommit && !arbitration.isStuck + commit.value(31 downto 0) := (input(FPU_COMMIT_LOAD) ? dBusEncoding.loadData()(31 downto 0) | input(RS1)) + if(p.withDouble) commit.value(63 downto 32) := dBusEncoding.loadData()(63 downto 32) + commit.write := arbitration.isValid && !arbitration.removeIt + commit.opcode := input(FPU_OPCODE) + commit.rd := input(INSTRUCTION)(rdRange).asUInt + + when(isCommit && !commit.ready){ + arbitration.haltByOther := True + } + + port.commit << commit.pipelined(s2m = true, m2s = false) + } + + pipeline.stages.dropRight(1).foreach(s => s.output(FPU_FORKED) clearWhen(s.arbitration.isStuck)) + + Component.current.afterElaboration{ + pipeline.stages.tail.foreach(_.input(FPU_FORKED).init(False)) + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/HaltOnExceptionPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/HaltOnExceptionPlugin.scala new file mode 100644 index 0000000..b104223 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/HaltOnExceptionPlugin.scala @@ -0,0 +1,44 @@ + +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import vexriscv._ +import vexriscv.Riscv._ + +import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable + + +class HaltOnExceptionPlugin() extends Plugin[VexRiscv] with ExceptionService { + def xlen = 32 + + //Mannage ExceptionService calls + val exceptionPortsInfos = ArrayBuffer[ExceptionPortInfo]() + def exceptionCodeWidth = 4 + override def newExceptionPort(stage : Stage, priority : Int = 0, codeWidth : Int = 4) = { + val interface = Flow(ExceptionCause(4)) + exceptionPortsInfos += ExceptionPortInfo(interface,stage,priority, codeWidth) + interface + } + override def isExceptionPending(stage : Stage): Bool = False + + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + stages.head.insert(FORMAL_HALT) := False + stages.foreach(stage => { + val stagePorts = exceptionPortsInfos.filter(_.stage == stage) + if(stagePorts.nonEmpty) { + when(stagePorts.map(info => info.port.valid).orR) { + stage.output(FORMAL_HALT) := True + stage.arbitration.haltItself := True + } + for(stage <- stages){ + stage.output(FORMAL_HALT) clearWhen(stage.arbitration.isFlushed) + } + } + }) + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/HazardPessimisticPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/HazardPessimisticPlugin.scala new file mode 100644 index 0000000..5a8f4d3 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/HazardPessimisticPlugin.scala @@ -0,0 +1,24 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ + + +class HazardPessimisticPlugin() extends Plugin[VexRiscv] { + import Riscv._ + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(HAS_SIDE_EFFECT, False) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val writesInPipeline = stages.dropWhile(_ != execute).map(s => s.arbitration.isValid && s.input(REGFILE_WRITE_VALID)) :+ RegNext(stages.last.arbitration.isValid && stages.last.input(REGFILE_WRITE_VALID)) + decode.arbitration.haltByOther.setWhen(decode.arbitration.isValid && writesInPipeline.orR) + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/HazardSimplePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/HazardSimplePlugin.scala new file mode 100644 index 0000000..1b650e3 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/HazardSimplePlugin.scala @@ -0,0 +1,125 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ + +trait HazardService{ + def hazardOnExecuteRS : Bool +} + +class HazardSimplePlugin(bypassExecute : Boolean = false, + bypassMemory: Boolean = false, + bypassWriteBack: Boolean = false, + bypassWriteBackBuffer : Boolean = false, + pessimisticUseSrc : Boolean = false, + pessimisticWriteRegFile : Boolean = false, + pessimisticAddressMatch : Boolean = false) extends Plugin[VexRiscv] with HazardService{ + import Riscv._ + + + def hazardOnExecuteRS = { + if(pipeline.service(classOf[RegFileService]).readStage() == pipeline.execute) pipeline.execute.arbitration.isStuckByOthers else False //TODO not so nice + } + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(HAS_SIDE_EFFECT, False) //TODO implement it in each plugin + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + pipeline plug new Area { + val src0Hazard = False + val src1Hazard = False + + val readStage = service(classOf[RegFileService]).readStage() + + def trackHazardWithStage(stage: Stage, bypassable: Boolean, runtimeBypassable: Stageable[Bool]): Unit = { + val runtimeBypassableValue = if (runtimeBypassable != null) stage.input(runtimeBypassable) else True + val addr0Match = if (pessimisticAddressMatch) True else stage.input(INSTRUCTION)(rdRange) === readStage.input(INSTRUCTION)(rs1Range) + val addr1Match = if (pessimisticAddressMatch) True else stage.input(INSTRUCTION)(rdRange) === readStage.input(INSTRUCTION)(rs2Range) + when(stage.arbitration.isValid && stage.input(REGFILE_WRITE_VALID)) { + if (bypassable) { + when(runtimeBypassableValue) { + when(addr0Match) { + readStage.input(RS1) := stage.output(REGFILE_WRITE_DATA) + } + when(addr1Match) { + readStage.input(RS2) := stage.output(REGFILE_WRITE_DATA) + } + } + } + } + when(stage.arbitration.isValid && (if (pessimisticWriteRegFile) True else stage.input(REGFILE_WRITE_VALID))) { + when((Bool(!bypassable) || !runtimeBypassableValue)) { + when(addr0Match) { + src0Hazard := True + } + when(addr1Match) { + src1Hazard := True + } + } + } + } + + + val writeBackWrites = Flow(cloneable(new Bundle { + val address = Bits(5 bits) + val data = Bits(32 bits) + })) + writeBackWrites.valid := stages.last.output(REGFILE_WRITE_VALID) && stages.last.arbitration.isFiring + writeBackWrites.address := stages.last.output(INSTRUCTION)(rdRange) + writeBackWrites.data := stages.last.output(REGFILE_WRITE_DATA) + val writeBackBuffer = writeBackWrites.stage() + + val addr0Match = if (pessimisticAddressMatch) True else writeBackBuffer.address === readStage.input(INSTRUCTION)(rs1Range) + val addr1Match = if (pessimisticAddressMatch) True else writeBackBuffer.address === readStage.input(INSTRUCTION)(rs2Range) + when(writeBackBuffer.valid) { + if (bypassWriteBackBuffer) { + when(addr0Match) { + readStage.input(RS1) := writeBackBuffer.data + } + when(addr1Match) { + readStage.input(RS2) := writeBackBuffer.data + } + } else { + when(addr0Match) { + src0Hazard := True + } + when(addr1Match) { + src1Hazard := True + } + } + } + + if (withWriteBackStage) trackHazardWithStage(writeBack, bypassWriteBack, null) + if (withMemoryStage) trackHazardWithStage(memory, bypassMemory, if (stages.last == memory) null else BYPASSABLE_MEMORY_STAGE) + if (readStage != execute) trackHazardWithStage(execute, bypassExecute, if (stages.last == execute) null else BYPASSABLE_EXECUTE_STAGE) + + + if (!pessimisticUseSrc) { + when(!readStage.input(RS1_USE)) { + src0Hazard := False + } + when(!readStage.input(RS2_USE)) { + src1Hazard := False + } + } + + when(readStage.arbitration.isValid && (src0Hazard || src1Hazard)) { + readStage.arbitration.haltByOther := True + } + } + } +} + + +class NoHazardPlugin extends Plugin[VexRiscv] with HazardService { + override def build(pipeline: VexRiscv): Unit = {} + + def hazardOnExecuteRS = False +}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/IBusCachedPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/IBusCachedPlugin.scala new file mode 100644 index 0000000..035c5dc --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/IBusCachedPlugin.scala @@ -0,0 +1,290 @@ +package vexriscv.plugin + +import vexriscv.{plugin, _} +import vexriscv.ip._ +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable.ArrayBuffer + +//class IBusCachedPlugin(config : InstructionCacheConfig, memoryTranslatorPortConfig : Any = null) extends Plugin[VexRiscv] { +// var iBus : InstructionCacheMemBus = null +// override def build(pipeline: VexRiscv): Unit = ??? +//} + +case class TightlyCoupledBus() extends Bundle with IMasterSlave { + val enable = Bool() + val address = UInt(32 bits) + val data = Bits(32 bits) + + override def asMaster(): Unit = { + out(enable, address) + in(data) + } +} + +case class TightlyCoupledPortParameter(name : String, hit : UInt => Bool) +case class TightlyCoupledPort(p : TightlyCoupledPortParameter, var bus : TightlyCoupledBus) +class IBusCachedPlugin(resetVector : BigInt = 0x80000000l, + relaxedPcCalculation : Boolean = false, + prediction : BranchPrediction = NONE, + historyRamSizeLog2 : Int = 10, + compressedGen : Boolean = false, + keepPcPlus4 : Boolean = false, + val config : InstructionCacheConfig, + memoryTranslatorPortConfig : Any = null, + injectorStage : Boolean = false, + withoutInjectorStage : Boolean = false, + relaxPredictorAddress : Boolean = true, + predictionBuffer : Boolean = true) extends IBusFetcherImpl( + resetVector = resetVector, + keepPcPlus4 = keepPcPlus4, + decodePcGen = compressedGen, + compressedGen = compressedGen, + cmdToRspStageCount = (if(config.twoCycleCache) 2 else 1) + (if(relaxedPcCalculation) 1 else 0), + allowPcRegReusedForSecondStage = true, + injectorReadyCutGen = false, + prediction = prediction, + historyRamSizeLog2 = historyRamSizeLog2, + injectorStage = (!config.twoCycleCache && !withoutInjectorStage) || injectorStage, + relaxPredictorAddress = relaxPredictorAddress, + fetchRedoGen = true, + predictionBuffer = predictionBuffer) with VexRiscvRegressionArg{ + import config._ + + + + assert(isPow2(cacheSize)) + assert(!(memoryTranslatorPortConfig != null && config.cacheSize/config.wayCount > 4096), "When the I$ is used with MMU, each way can't be bigger than a page (4096 bytes)") + + + assert(!(withoutInjectorStage && injectorStage)) + + + override def getVexRiscvRegressionArgs(): Seq[String] = { + var args = List[String]() + args :+= "IBUS=CACHED" + args :+= s"IBUS_DATA_WIDTH=$memDataWidth" + args :+= s"COMPRESSED=${if(compressedGen) "yes" else "no"}" + args + } + + var iBus : InstructionCacheMemBus = null + var mmuBus : MemoryTranslatorBus = null + var privilegeService : PrivilegeService = null + var decodeExceptionPort : Flow[ExceptionCause] = null + val tightlyCoupledPorts = ArrayBuffer[TightlyCoupledPort]() + def tightlyGen = tightlyCoupledPorts.nonEmpty + + def newTightlyCoupledPort(p : TightlyCoupledPortParameter) = { + val port = TightlyCoupledPort(p, null) + tightlyCoupledPorts += port + this + } + + + object FLUSH_ALL extends Stageable(Bool) + object IBUS_ACCESS_ERROR extends Stageable(Bool) + object IBUS_MMU_MISS extends Stageable(Bool) + object IBUS_ILLEGAL_ACCESS extends Stageable(Bool) + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + super.setup(pipeline) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(FLUSH_ALL, False) + decoderService.add(FENCE_I, List( + FLUSH_ALL -> True + )) + + if(catchSomething) { + val exceptionService = pipeline.service(classOf[ExceptionService]) + decodeExceptionPort = exceptionService.newExceptionPort(pipeline.decode,1) + } + + if(pipeline.serviceExist(classOf[MemoryTranslator])) + mmuBus = pipeline.service(classOf[MemoryTranslator]).newTranslationPort(MemoryTranslatorPort.PRIORITY_INSTRUCTION, memoryTranslatorPortConfig) + + privilegeService = pipeline.serviceElse(classOf[PrivilegeService], PrivilegeServiceDefault()) + + if(pipeline.serviceExist(classOf[ReportService])){ + val report = pipeline.service(classOf[ReportService]) + report.add("iBus" -> { + val e = new BusReport() + val c = new CacheReport() + e.kind = "cached" + e.flushInstructions.add(0x100F) //FENCE.I + e.flushInstructions.add(0x13) + e.flushInstructions.add(0x13) + e.flushInstructions.add(0x13) + + e.info = c + c.size = cacheSize + c.bytePerLine = bytePerLine + + e + }) + } + } + + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + pipeline plug new FetchArea(pipeline) { + val cache = new InstructionCache(IBusCachedPlugin.this.config.copy(bypassGen = tightlyGen), if(mmuBus != null) mmuBus.p else MemoryTranslatorBusParameter(0,0)) + iBus = master(new InstructionCacheMemBus(IBusCachedPlugin.this.config)).setName("iBus") + iBus <> cache.io.mem + iBus.cmd.address.allowOverride := cache.io.mem.cmd.address + + //Memory bandwidth counter + val rspCounter = Reg(UInt(32 bits)) init(0) + when(iBus.rsp.valid){ + rspCounter := rspCounter + 1 + } + + val stageOffset = if(relaxedPcCalculation) 1 else 0 + def stages = iBusRsp.stages.drop(stageOffset) + + tightlyCoupledPorts.foreach(p => p.bus = master(TightlyCoupledBus()).setName(p.p.name)) + + val s0 = new Area { + //address decoding + val tightlyCoupledHits = Vec(tightlyCoupledPorts.map(_.p.hit(stages(0).input.payload))) + val tightlyCoupledHit = tightlyCoupledHits.orR + + for((port, hit) <- (tightlyCoupledPorts, tightlyCoupledHits).zipped){ + port.bus.enable := stages(0).input.fire && hit + port.bus.address := stages(0).input.payload(31 downto 2) @@ U"00" + } + + //Connect prefetch cache side + cache.io.cpu.prefetch.isValid := stages(0).input.valid && !tightlyCoupledHit + cache.io.cpu.prefetch.pc := stages(0).input.payload + stages(0).halt setWhen (cache.io.cpu.prefetch.haltIt) + + if(mmuBus != null && mmuBus.p.latency == 1) { + stages(0).halt setWhen(mmuBus.busy) + mmuBus.cmd(0).isValid := cache.io.cpu.prefetch.isValid + mmuBus.cmd(0).isStuck := !stages(0).input.ready + mmuBus.cmd(0).virtualAddress := cache.io.cpu.prefetch.pc + mmuBus.cmd(0).bypassTranslation := False + } + } + + + val s1 = new Area { + val tightlyCoupledHits = RegNextWhen(s0.tightlyCoupledHits, stages(1).input.ready) + val tightlyCoupledHit = RegNextWhen(s0.tightlyCoupledHit, stages(1).input.ready) + + if(tightlyGen) cache.io.cpu.fetch.dataBypassValid := tightlyCoupledHit + if(tightlyGen) cache.io.cpu.fetch.dataBypass := MuxOH(tightlyCoupledHits, tightlyCoupledPorts.map(e => CombInit(e.bus.data))) + + //Connect fetch cache side + cache.io.cpu.fetch.isValid := stages(1).input.valid && !tightlyCoupledHit + cache.io.cpu.fetch.isStuck := !stages(1).input.ready + cache.io.cpu.fetch.pc := stages(1).input.payload + + if(mmuBus != null) { + mmuBus.cmd.last.isValid := cache.io.cpu.fetch.isValid + mmuBus.cmd.last.isStuck := !stages(1).input.ready + mmuBus.cmd.last.virtualAddress := cache.io.cpu.fetch.pc + mmuBus.cmd.last.bypassTranslation := False + mmuBus.end := stages(1).input.ready || externalFlush + if (mmuBus.p.latency == 0) stages(1).halt setWhen (mmuBus.busy) + } + + + if (!twoCycleCache) { + cache.io.cpu.fetch.isUser := privilegeService.isUser() + } + } + + val s2 = twoCycleCache generate new Area { + val tightlyCoupledHit = RegNextWhen(s1.tightlyCoupledHit, stages(2).input.ready) + cache.io.cpu.decode.isValid := stages(2).input.valid && !tightlyCoupledHit + cache.io.cpu.decode.isStuck := !stages(2).input.ready + cache.io.cpu.decode.pc := stages(2).input.payload + cache.io.cpu.decode.isUser := privilegeService.isUser() + + if ((!twoCycleRam || wayCount == 1) && !compressedGen && !injectorStage) { + decode.insert(INSTRUCTION_ANTICIPATED) := Mux(decode.arbitration.isStuck, decode.input(INSTRUCTION), cache.io.cpu.fetch.data) + } + } + + val rsp = new Area { + val iBusRspOutputHalt = False + + val cacheRsp = if (twoCycleCache) cache.io.cpu.decode else cache.io.cpu.fetch + val cacheRspArbitration = stages(if (twoCycleCache) 2 else 1) + var issueDetected = False + val redoFetch = False + + + //Refill / redo + assert(decodePcGen == compressedGen) + cache.io.cpu.fill.valid := redoFetch && !cacheRsp.mmuRefilling + cache.io.cpu.fill.payload := cacheRsp.physicalAddress + + + if (catchSomething) { + decodeExceptionPort.valid := False + decodeExceptionPort.code.assignDontCare() + decodeExceptionPort.badAddr := cacheRsp.pc(31 downto 2) @@ U"00" + } + + when(cacheRsp.isValid && cacheRsp.mmuRefilling && !issueDetected) { + issueDetected \= True + redoFetch := True + } + + if(catchIllegalAccess) when(cacheRsp.isValid && cacheRsp.mmuException && !issueDetected) { + issueDetected \= True + decodeExceptionPort.valid := iBusRsp.readyForError + decodeExceptionPort.code := 12 + } + + when(cacheRsp.isValid && cacheRsp.cacheMiss && !issueDetected) { + issueDetected \= True + cache.io.cpu.fill.valid := True + redoFetch := True + } + + if(catchAccessFault) when(cacheRsp.isValid && cacheRsp.error && !issueDetected) { + issueDetected \= True + decodeExceptionPort.valid := iBusRsp.readyForError + decodeExceptionPort.code := 1 + } + + when(redoFetch) { + iBusRsp.redoFetch := True + } + + + cacheRspArbitration.halt setWhen (issueDetected || iBusRspOutputHalt) + iBusRsp.output.valid := cacheRspArbitration.output.valid + cacheRspArbitration.output.ready := iBusRsp.output.ready + iBusRsp.output.rsp.inst := cacheRsp.data + iBusRsp.output.pc := cacheRspArbitration.output.payload + } + + if (mmuBus != null) { + cache.io.cpu.fetch.mmuRsp <> mmuBus.rsp + } else { + cache.io.cpu.fetch.mmuRsp.physicalAddress := cache.io.cpu.fetch.pc + cache.io.cpu.fetch.mmuRsp.allowExecute := True + cache.io.cpu.fetch.mmuRsp.allowRead := True + cache.io.cpu.fetch.mmuRsp.allowWrite := True + cache.io.cpu.fetch.mmuRsp.isIoAccess := False + cache.io.cpu.fetch.mmuRsp.exception := False + cache.io.cpu.fetch.mmuRsp.refilling := False + } + + val flushStage = decode + cache.io.flush := flushStage.arbitration.isValid && flushStage.input(FLUSH_ALL) + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/IBusSimplePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/IBusSimplePlugin.scala new file mode 100644 index 0000000..1bb02bf --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/IBusSimplePlugin.scala @@ -0,0 +1,418 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba3.ahblite.{AhbLite3, AhbLite3Config, AhbLite3Master} +import spinal.lib.bus.amba4.axi._ +import spinal.lib.bus.avalon.{AvalonMM, AvalonMMConfig} +import spinal.lib.bus.bmb.{Bmb, BmbParameter} +import spinal.lib.bus.wishbone.{Wishbone, WishboneConfig} +import spinal.lib.bus.simple._ +import vexriscv.Riscv.{FENCE, FENCE_I} + + +case class IBusSimpleCmd() extends Bundle{ + val pc = UInt(32 bits) +} + +case class IBusSimpleRsp() extends Bundle with IMasterSlave{ + val error = Bool + val inst = Bits(32 bits) + + override def asMaster(): Unit = { + out(error,inst) + } +} + + +object IBusSimpleBus{ + def getAxi4Config() = Axi4Config( + addressWidth = 32, + dataWidth = 32, + useId = false, + useRegion = false, + useBurst = false, + useLock = false, + useQos = false, + useLen = false, + useResp = true, + useSize = false + ) + + def getAvalonConfig() = AvalonMMConfig.pipelined( + addressWidth = 32, + dataWidth = 32 + ).getReadOnlyConfig.copy( + useResponse = true, + maximumPendingReadTransactions = 8 + ) + + def getWishboneConfig() = WishboneConfig( + addressWidth = 30, + dataWidth = 32, + selWidth = 4, + useSTALL = false, + useLOCK = false, + useERR = true, + useRTY = false, + tgaWidth = 0, + tgcWidth = 0, + tgdWidth = 0, + useBTE = true, + useCTI = true + ) + + def getPipelinedMemoryBusConfig() = PipelinedMemoryBusConfig( + addressWidth = 32, + dataWidth = 32 + ) + + + def getAhbLite3Config() = AhbLite3Config( + addressWidth = 32, + dataWidth = 32 + ) + + def getBmbParameter(plugin : IBusSimplePlugin = null) = BmbParameter( + addressWidth = 32, + dataWidth = 32, + lengthWidth = 2, + sourceWidth = 0, + contextWidth = 0, + canRead = true, + canWrite = false, + alignment = BmbParameter.BurstAlignement.LENGTH, + maximumPendingTransaction = if(plugin != null) plugin.pendingMax else Int.MaxValue + ) +} + + +case class IBusSimpleBus(plugin: IBusSimplePlugin) extends Bundle with IMasterSlave { + var cmd = Stream(IBusSimpleCmd()) + var rsp = Flow(IBusSimpleRsp()) + + override def asMaster(): Unit = { + master(cmd) + slave(rsp) + } + + + def cmdS2mPipe() : IBusSimpleBus = { + val s = IBusSimpleBus(plugin) + s.cmd << this.cmd.s2mPipe() + this.rsp << s.rsp + s + } + + + def toAxi4ReadOnly(): Axi4ReadOnly = { + assert(plugin.cmdForkPersistence) + val axi = Axi4ReadOnly(IBusSimpleBus.getAxi4Config()) + + axi.ar.valid := cmd.valid + axi.ar.addr := cmd.pc(axi.readCmd.addr.getWidth -1 downto 2) @@ U"00" + axi.ar.prot := "110" + axi.ar.cache := "1111" + cmd.ready := axi.ar.ready + + + rsp.valid := axi.r.valid + rsp.inst := axi.r.data + rsp.error := !axi.r.isOKAY() + axi.r.ready := True + + axi + } + + def toAvalon(): AvalonMM = { + assert(plugin.cmdForkPersistence) + val avalonConfig = IBusSimpleBus.getAvalonConfig() + val mm = AvalonMM(avalonConfig) + + mm.read := cmd.valid + mm.address := (cmd.pc >> 2) @@ U"00" + cmd.ready := mm.waitRequestn + + rsp.valid := mm.readDataValid + rsp.inst := mm.readData + rsp.error := mm.response =/= AvalonMM.Response.OKAY + + mm + } + + def toWishbone(): Wishbone = { + val wishboneConfig = IBusSimpleBus.getWishboneConfig() + val bus = Wishbone(wishboneConfig) + val cmdPipe = cmd.stage() + + bus.ADR := (cmdPipe.pc >> 2) + bus.CTI := B"000" + bus.BTE := "00" + bus.SEL := "1111" + bus.WE := False + bus.DAT_MOSI.assignDontCare() + bus.CYC := cmdPipe.valid + bus.STB := cmdPipe.valid + + + cmdPipe.ready := cmdPipe.valid && bus.ACK + rsp.valid := bus.CYC && bus.ACK + rsp.inst := bus.DAT_MISO + rsp.error := False //TODO + bus + } + + def toPipelinedMemoryBus(): PipelinedMemoryBus = { + val pipelinedMemoryBusConfig = IBusSimpleBus.getPipelinedMemoryBusConfig() + val bus = PipelinedMemoryBus(pipelinedMemoryBusConfig) + bus.cmd.arbitrationFrom(cmd) + bus.cmd.address := cmd.pc.resized + bus.cmd.write := False + bus.cmd.mask.assignDontCare() + bus.cmd.data.assignDontCare() + rsp.valid := bus.rsp.valid + rsp.inst := bus.rsp.payload.data + rsp.error := False + bus + } + + + //cmdForkPersistence need to bet set + def toAhbLite3Master(): AhbLite3Master = { + assert(plugin.cmdForkPersistence) + val bus = AhbLite3Master(IBusSimpleBus.getAhbLite3Config()) + bus.HADDR := this.cmd.pc + bus.HWRITE := False + bus.HSIZE := 2 + bus.HBURST := 0 + bus.HPROT := "1110" + bus.HTRANS := this.cmd.valid ## B"0" + bus.HMASTLOCK := False + bus.HWDATA.assignDontCare() + this.cmd.ready := bus.HREADY + + val pending = RegInit(False) clearWhen(bus.HREADY) setWhen(this.cmd.fire) + this.rsp.valid := bus.HREADY && pending + this.rsp.inst := bus.HRDATA + this.rsp.error := bus.HRESP + bus + } + + def toBmb() : Bmb = { + val pipelinedMemoryBusConfig = IBusSimpleBus.getBmbParameter(plugin) + val bus = Bmb(pipelinedMemoryBusConfig) + bus.cmd.arbitrationFrom(cmd) + bus.cmd.opcode := Bmb.Cmd.Opcode.READ + bus.cmd.address := cmd.pc.resized + bus.cmd.length := 3 + bus.cmd.last := True + rsp.valid := bus.rsp.valid + rsp.inst := bus.rsp.data + rsp.error := bus.rsp.isError + bus.rsp.ready := True + bus + } +} + + + + + + +class IBusSimplePlugin( resetVector : BigInt, + val cmdForkOnSecondStage : Boolean, + val cmdForkPersistence : Boolean, + val catchAccessFault : Boolean = false, + prediction : BranchPrediction = NONE, + historyRamSizeLog2 : Int = 10, + keepPcPlus4 : Boolean = false, + compressedGen : Boolean = false, + val busLatencyMin : Int = 1, + val pendingMax : Int = 7, + injectorStage : Boolean = true, + val rspHoldValue : Boolean = false, + val singleInstructionPipeline : Boolean = false, + val memoryTranslatorPortConfig : Any = null, + relaxPredictorAddress : Boolean = true, + predictionBuffer : Boolean = true, + bigEndian : Boolean = false, + vecRspBuffer : Boolean = false + ) extends IBusFetcherImpl( + resetVector = resetVector, + keepPcPlus4 = keepPcPlus4, + decodePcGen = compressedGen, + compressedGen = compressedGen, + cmdToRspStageCount = busLatencyMin + (if(cmdForkOnSecondStage) 1 else 0), + allowPcRegReusedForSecondStage = !(cmdForkOnSecondStage && cmdForkPersistence), + injectorReadyCutGen = false, + prediction = prediction, + historyRamSizeLog2 = historyRamSizeLog2, + injectorStage = injectorStage, + relaxPredictorAddress = relaxPredictorAddress, + fetchRedoGen = memoryTranslatorPortConfig != null, + predictionBuffer = predictionBuffer){ + + var iBus : IBusSimpleBus = null + var decodeExceptionPort : Flow[ExceptionCause] = null + val catchSomething = memoryTranslatorPortConfig != null || catchAccessFault + var mmuBus : MemoryTranslatorBus = null + +// if(rspHoldValue) assert(busLatencyMin <= 1) + assert(!rspHoldValue, "rspHoldValue not supported yet") + assert(!singleInstructionPipeline) + + override def setup(pipeline: VexRiscv): Unit = { + super.setup(pipeline) + iBus = master(IBusSimpleBus(this)).setName("iBus") + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.add(FENCE_I, Nil) + + if(catchSomething) { + decodeExceptionPort = pipeline.service(classOf[ExceptionService]).newExceptionPort(pipeline.decode,1) + } + + if(memoryTranslatorPortConfig != null) { + mmuBus = pipeline.service(classOf[MemoryTranslator]).newTranslationPort(MemoryTranslatorPort.PRIORITY_INSTRUCTION, memoryTranslatorPortConfig) + } + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + pipeline plug new FetchArea(pipeline) { + var cmd = Stream(IBusSimpleCmd()) + val cmdWithS2mPipe = cmdForkPersistence && (!cmdForkOnSecondStage || mmuBus != null) + iBus.cmd << (if(cmdWithS2mPipe) cmd.s2mPipe() else cmd) + + //Avoid sending to many iBus cmd + val pending = new Area{ + val inc, dec = Bool() + val value = Reg(UInt(log2Up(pendingMax + 1) bits)) init (0) + val next = value + U(inc) - U(dec) + value := next + } + + val secondStagePersistence = cmdForkPersistence && cmdForkOnSecondStage && !cmdWithS2mPipe + def cmdForkStage = if(!secondStagePersistence) iBusRsp.stages(if(cmdForkOnSecondStage) 1 else 0) else iBusRsp.stages(1) + + val cmdFork = if(!secondStagePersistence) new Area { + //This implementation keep the cmd on the bus until it's executed or the the pipeline is flushed + def stage = cmdForkStage + val canEmit = stage.output.ready && pending.value =/= pendingMax + stage.halt setWhen(stage.input.valid && (!canEmit || !cmd.ready)) + cmd.valid := stage.input.valid && canEmit + pending.inc := cmd.fire + } else new Area{ + //This implementation keep the cmd on the bus until it's executed, even if the pipeline is flushed + def stage = cmdForkStage + val pendingFull = pending.value === pendingMax + val enterTheMarket = Bool() + val cmdKeep = RegInit(False) setWhen(enterTheMarket) clearWhen(cmd.ready) + val cmdFired = RegInit(False) setWhen(cmd.fire) clearWhen(stage.input.ready) + enterTheMarket := stage.input.valid && !pendingFull && !cmdFired && !cmdKeep +// stage.halt setWhen(cmd.isStall || (pendingFull && !cmdFired)) //(cmd.isStall) + stage.halt setWhen(pendingFull && !cmdFired && !cmdKeep) + stage.halt setWhen(!cmd.ready && !cmdFired) + cmd.valid := enterTheMarket || cmdKeep + pending.inc := enterTheMarket + } + + val mmu = (mmuBus != null) generate new Area { + mmuBus.cmd.last.isValid := cmdForkStage.input.valid + mmuBus.cmd.last.virtualAddress := cmdForkStage.input.payload + mmuBus.cmd.last.bypassTranslation := False + mmuBus.end := cmdForkStage.output.fire || externalFlush + + cmd.pc := mmuBus.rsp.physicalAddress(31 downto 2) @@ U"00" + + //do not emit memory request if MMU had issues + when(cmdForkStage.input.valid) { + when(mmuBus.rsp.refilling) { + cmdForkStage.halt := True + cmd.valid := False + } + when(mmuBus.rsp.exception) { + cmdForkStage.halt := False + cmd.valid := False + } + } + + val joinCtx = stageXToIBusRsp(cmdForkStage, mmuBus.rsp) + } + + val mmuLess = (mmuBus == null) generate new Area{ + cmd.pc := cmdForkStage.input.payload(31 downto 2) @@ U"00" + } + + val rspJoin = new Area { + import iBusRsp._ + //Manage flush for iBus transactions in flight + val rspBuffer = new Area { + val output = Stream(IBusSimpleRsp()) + val c = new StreamFifoLowLatency(IBusSimpleRsp(), busLatencyMin + (if(cmdForkOnSecondStage && cmdForkPersistence) 1 else 0), useVec = vecRspBuffer) + val discardCounter = Reg(UInt(log2Up(pendingMax + 1) bits)) init (0) + discardCounter := discardCounter - (c.io.pop.valid && discardCounter =/= 0).asUInt + when(iBusRsp.flush) { + discardCounter := (if(cmdForkOnSecondStage) pending.next else pending.value - U(pending.dec)) + } + + c.io.push << iBus.rsp.toStream +// if(compressedGen) c.io.flush setWhen(decompressor.consumeCurrent) +// if(!compressedGen && isDrivingDecode(IBUS_RSP)) c.io.flush setWhen(decode.arbitration.flushNext && iBusRsp.output.ready) + val flush = discardCounter =/= 0 || iBusRsp.flush + output.valid := c.io.pop.valid && discardCounter === 0 + output.payload := c.io.pop.payload + c.io.pop.ready := output.ready || flush + + pending.dec := c.io.pop.fire // iBus.rsp.valid && flush || c.io.pop.valid && output.ready instead to avoid unecessary dependancies ? + } + + val fetchRsp = FetchRsp() + fetchRsp.pc := stages.last.output.payload + fetchRsp.rsp := rspBuffer.output.payload + fetchRsp.rsp.error.clearWhen(!rspBuffer.output.valid) //Avoid interference with instruction injection from the debug plugin + if(bigEndian){ + // instructions are stored in little endian byteorder + fetchRsp.rsp.inst.allowOverride + fetchRsp.rsp.inst := EndiannessSwap(rspBuffer.output.payload.inst) + } + + val join = Stream(FetchRsp()) + val exceptionDetected = False + join.valid := stages.last.output.valid && rspBuffer.output.valid + join.payload := fetchRsp + stages.last.output.ready := stages.last.output.valid ? join.fire | join.ready + rspBuffer.output.ready := join.fire + output << join.haltWhen(exceptionDetected) + + if(memoryTranslatorPortConfig != null){ + when(stages.last.input.valid && mmu.joinCtx.refilling) { + iBusRsp.redoFetch := True + } + } + + + if(catchSomething){ + decodeExceptionPort.code.assignDontCare() + decodeExceptionPort.badAddr := join.pc(31 downto 2) @@ U"00" + + if(catchAccessFault) when(join.valid && join.rsp.error){ + decodeExceptionPort.code := 1 + exceptionDetected := True + } + if(memoryTranslatorPortConfig != null) { + val privilegeService = pipeline.serviceElse(classOf[PrivilegeService], PrivilegeServiceDefault()) + when(stages.last.input.valid && !mmu.joinCtx.refilling && (mmu.joinCtx.exception || !mmu.joinCtx.allowExecute)){ + decodeExceptionPort.code := 12 + exceptionDetected := True + } + } + decodeExceptionPort.valid := exceptionDetected && iBusRsp.readyForError + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/IntAluPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/IntAluPlugin.scala new file mode 100644 index 0000000..0520c2f --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/IntAluPlugin.scala @@ -0,0 +1,100 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +object IntAluPlugin{ + object AluBitwiseCtrlEnum extends SpinalEnum(binarySequential){ + val XOR, OR, AND = newElement() + } + object AluCtrlEnum extends SpinalEnum(binarySequential){ + val ADD_SUB, SLT_SLTU, BITWISE = newElement() + } + + object ALU_BITWISE_CTRL extends Stageable(AluBitwiseCtrlEnum()) + object ALU_CTRL extends Stageable(AluCtrlEnum()) +} + +class IntAluPlugin extends Plugin[VexRiscv]{ + import IntAluPlugin._ + + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + val immediateActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.IMI, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> True, + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True + ) + + val nonImmediateActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> True, + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True, + RS2_USE -> True + ) + + val otherAction = List[(Stageable[_ <: BaseType],Any)]( + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> True, + BYPASSABLE_MEMORY_STAGE -> True + ) + + + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.add(List( + ADD -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.ADD_SUB, SRC_USE_SUB_LESS -> False)), + SUB -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.ADD_SUB, SRC_USE_SUB_LESS -> True)), + SLT -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.SLT_SLTU, SRC_USE_SUB_LESS -> True, SRC_LESS_UNSIGNED -> False)), + SLTU -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.SLT_SLTU, SRC_USE_SUB_LESS -> True, SRC_LESS_UNSIGNED -> True)), + XOR -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.BITWISE, ALU_BITWISE_CTRL -> AluBitwiseCtrlEnum.XOR)), + OR -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.BITWISE, ALU_BITWISE_CTRL -> AluBitwiseCtrlEnum.OR)), + AND -> (nonImmediateActions ++ List(ALU_CTRL -> AluCtrlEnum.BITWISE, ALU_BITWISE_CTRL -> AluBitwiseCtrlEnum.AND)) + )) + + decoderService.add(List( + ADDI -> (immediateActions ++ List(ALU_CTRL -> AluCtrlEnum.ADD_SUB, SRC_USE_SUB_LESS -> False)), + SLTI -> (immediateActions ++ List(ALU_CTRL -> AluCtrlEnum.SLT_SLTU, SRC_USE_SUB_LESS -> True, SRC_LESS_UNSIGNED -> False)), + SLTIU -> (immediateActions ++ List(ALU_CTRL -> AluCtrlEnum.SLT_SLTU, SRC_USE_SUB_LESS -> True, SRC_LESS_UNSIGNED -> True)), + XORI -> (immediateActions ++ List(ALU_CTRL -> AluCtrlEnum.BITWISE, ALU_BITWISE_CTRL -> AluBitwiseCtrlEnum.XOR)), + ORI -> (immediateActions ++ List(ALU_CTRL -> AluCtrlEnum.BITWISE, ALU_BITWISE_CTRL -> AluBitwiseCtrlEnum.OR)), + ANDI -> (immediateActions ++ List(ALU_CTRL -> AluCtrlEnum.BITWISE, ALU_BITWISE_CTRL -> AluBitwiseCtrlEnum.AND)) + )) + + decoderService.add(List( + LUI -> (otherAction ++ List(ALU_CTRL -> AluCtrlEnum.ADD_SUB, SRC1_CTRL -> Src1CtrlEnum.IMU, SRC_USE_SUB_LESS -> False, SRC_ADD_ZERO -> True)), + AUIPC -> (otherAction ++ List(ALU_CTRL -> AluCtrlEnum.ADD_SUB, SRC_USE_SUB_LESS -> False, SRC1_CTRL -> Src1CtrlEnum.IMU, SRC2_CTRL -> Src2CtrlEnum.PC)) + )) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + + execute plug new Area{ + import execute._ + + val bitwise = input(ALU_BITWISE_CTRL).mux( + AluBitwiseCtrlEnum.AND -> (input(SRC1) & input(SRC2)), + AluBitwiseCtrlEnum.OR -> (input(SRC1) | input(SRC2)), + AluBitwiseCtrlEnum.XOR -> (input(SRC1) ^ input(SRC2)) + ) + + // mux results + insert(REGFILE_WRITE_DATA) := input(ALU_CTRL).mux( + AluCtrlEnum.BITWISE -> bitwise, + AluCtrlEnum.SLT_SLTU -> input(SRC_LESS).asBits(32 bit), + AluCtrlEnum.ADD_SUB -> input(SRC_ADD_SUB) + ) + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/MemoryTranslatorPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/MemoryTranslatorPlugin.scala new file mode 100644 index 0000000..081b11d --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/MemoryTranslatorPlugin.scala @@ -0,0 +1,159 @@ +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable.ArrayBuffer + +object MemoryTranslatorPort{ + val PRIORITY_DATA = 1 + val PRIORITY_INSTRUCTION = 0 +} +case class MemoryTranslatorPort(bus : MemoryTranslatorBus, priority : Int, args : MemoryTranslatorPortConfig/*, exceptionBus: Flow[ExceptionCause]*/) + +case class MemoryTranslatorPortConfig(portTlbSize : Int) + +class MemoryTranslatorPlugin(tlbSize : Int, + virtualRange : UInt => Bool, + ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator { + assert(isPow2(tlbSize)) + + val portsInfo = ArrayBuffer[MemoryTranslatorPort]() + + override def newTranslationPort(priority : Int,args : Any): MemoryTranslatorBus = { + val config = args.asInstanceOf[MemoryTranslatorPortConfig] + val port = MemoryTranslatorPort(MemoryTranslatorBus(MemoryTranslatorBusParameter(wayCount = 0)),priority, config/*,exceptionBus*/) + portsInfo += port + port.bus + } + + object IS_TLB extends Stageable(Bool) + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + def TLBW0 = M"0000000----------111-----0001111" + def TLBW1 = M"0000001----------111-----0001111" + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(IS_TLB, False) + decoderService.add(TLBW0, List(IS_TLB -> True, RS1_USE -> True, SRC1_CTRL -> Src1CtrlEnum.RS)) + decoderService.add(TLBW1, List(IS_TLB -> True, RS1_USE -> True, RS2_USE -> True, SRC1_CTRL -> Src1CtrlEnum.RS)) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + import Riscv._ + + //Sorted by priority + val sortedPortsInfo = portsInfo.sortWith((a,b) => a.priority > b.priority) + + case class CacheLine() extends Bundle { + val valid = Bool + val virtualAddress = UInt(20 bits) + val physicalAddress = UInt(20 bits) + val allowRead, allowWrite, allowExecute, allowUser = Bool + + def init = { + valid init (False) + this + } + } + + val core = pipeline plug new Area { + val shared = new Area { + val cache = Mem(CacheLine(), tlbSize) + var free = True + val readAddr = cache.addressType().assignDontCare() + val readData = RegNext(cache.readSync(readAddr)) + } + + val ports = for ((port, portId) <- sortedPortsInfo.zipWithIndex) yield new Area { + val cache = Vec(Reg(CacheLine()) init, port.args.portTlbSize) + val cacheHits = cache.map(line => line.valid && line.virtualAddress === port.bus.cmd.last.virtualAddress(31 downto 12)) + val cacheHit = cacheHits.asBits.orR + val cacheLine = MuxOH(cacheHits, cache) + val isInMmuRange = virtualRange(port.bus.cmd.last.virtualAddress) && !port.bus.cmd.last.bypassTranslation + + val sharedMiss = RegInit(False) + val sharedIterator = Reg(UInt(log2Up(tlbSize + 1) bits)) + val sharedAccessed = RegInit(B"00") + val entryToReplace = Counter(port.args.portTlbSize) + + val sharedAccessAsked = RegNext(port.bus.cmd.last.isValid && !cacheHit && sharedIterator < tlbSize && isInMmuRange) + val sharedAccessGranted = sharedAccessAsked && shared.free + when(sharedAccessGranted) { + shared.readAddr := sharedIterator.resized + sharedIterator := sharedIterator + 1 + } + sharedAccessed := (sharedAccessed ## sharedAccessGranted).resized + when(sharedAccessAsked){ + shared.free \= False + } + + when(sharedAccessed.msb){ + when(shared.readData.virtualAddress === port.bus.cmd.last.virtualAddress(31 downto 12)){ + cache(entryToReplace) := shared.readData + entryToReplace.increment() + } + } + + sharedMiss.setWhen(sharedIterator >= tlbSize && sharedAccessed === B"00") + when(port.bus.end){ + sharedIterator := 0 + sharedMiss.clear() + sharedAccessAsked.clear() + sharedAccessed := 0 + } + + + when(isInMmuRange) { + port.bus.rsp.physicalAddress := cacheLine.physicalAddress @@ port.bus.cmd.last.virtualAddress(11 downto 0) + port.bus.rsp.allowRead := cacheLine.allowRead + port.bus.rsp.allowWrite := cacheLine.allowWrite + port.bus.rsp.allowExecute := cacheLine.allowExecute + ??? +// port.bus.rsp.hit := cacheHit +// port.stage.arbitration.haltItself setWhen (port.bus.cmd.isValid && !cacheHit && !sharedMiss) + } otherwise { + port.bus.rsp.physicalAddress := port.bus.cmd.last.virtualAddress + port.bus.rsp.allowRead := True + port.bus.rsp.allowWrite := True + port.bus.rsp.allowExecute := True + ??? +// port.bus.rsp.hit := True + } + port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress) + ??? +// port.bus.rsp.miss := sharedMiss + } + } + + //Manage TLBW0 and TLBW1 instructions + //TODO not exception safe (sideeffect) + execute plug new Area{ + import execute._ + val tlbWriteBuffer = Reg(UInt(20 bits)) + when(arbitration.isFiring && input(IS_TLB)){ + switch(input(INSTRUCTION)(25 downto 25)){ + is(0){ + tlbWriteBuffer := input(SRC1).asUInt.resized + } + is(1){ + val line = CacheLine() + line.virtualAddress := tlbWriteBuffer + line.physicalAddress := input(RS2)(19 downto 0).asUInt + line.allowUser := input(RS2)(27) + line.allowRead := input(RS2)(28) + line.allowWrite := input(RS2)(29) + line.allowExecute := input(RS2)(30) + line.valid := input(RS2)(31) + core.shared.cache(input(SRC1)(log2Up(tlbSize)-1 downto 0).asUInt) := line + + core.ports.foreach(_.cache.foreach(_.valid := False)) //Invalidate all ports caches + } + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/Misc.scala b/VexRiscv/src/main/scala/vexriscv/plugin/Misc.scala new file mode 100644 index 0000000..979d246 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/Misc.scala @@ -0,0 +1,214 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ + +object RvcDecompressor{ + + def main(args: Array[String]): Unit = { + SpinalVerilog(new Component{ + out(Delay((apply(Delay(in Bits(16 bits),2), false, false)),2)) + }.setDefinitionName("Decompressor")) + } + + def apply(i : Bits, rvf : Boolean, rvd : Boolean): Bits ={ + val ret = Bits(32 bits).assignDontCare() + + val rch = B"01" ## i(9 downto 7) + val rcl = B"01" ## i(4 downto 2) + + val addi5spnImm = B"00" ## i(10 downto 7) ## i(12 downto 11) ## i(5) ## i(6) ## B"00" + val lwImm = B"00000" ## i(5) ## i(12 downto 10) ## i(6) ## B"00" + def swImm = lwImm + val ldImm = B"0000" ## i(6 downto 5) ## i(12 downto 10) ## B"000" + def sdImm = ldImm + val addImm = B((11 downto 5) -> i(12), (4 downto 0) -> i(6 downto 2)) + def lImm = addImm + val jalImm = B((9 downto 0) -> i(12)) ## i(8) ## i(10 downto 9) ## i(6) ## i(7) ## i(2) ## i(11) ## i(5 downto 3) ## B"0" + val luiImm = B((14 downto 0) -> i(12)) ## i(6 downto 2) ## B"0000_0000_0000" + val shiftImm = i(6 downto 2) + val addi16spImm = B((2 downto 0) -> i(12)) ## i(4 downto 3) ## i(5) ## i(2) ## i(6) ## B"0000" + val jImm = B((9 downto 0) -> i(12)) ## i(8) ## i(10 downto 9) ## i(6) ## i(7) ## i(2) ## i(11) ## i(5 downto 3) ## B"0" + val bImm = B((4 downto 0) -> i(12)) ## i(6 downto 5) ## i(2) ## i(11 downto 10) ## i(4 downto 3) ## B"0" + + def lwspImm = B"0000" ## i(3 downto 2) ## i(12) ## i(6 downto 4) ## B"00" + def swspImm = B"0000" ## i(8 downto 7) ## i(12 downto 9) ## B"00" + def ldspImm = B"000" ## i(4 downto 2) ## i(12) ## i(6 downto 5) ## B"000" + def sdspImm = B"000" ## i(9 downto 7) ## i(12 downto 10) ## B"000" + + + val x0 = B"00000" + val x1 = B"00001" + val x2 = B"00010" + + switch(i(1 downto 0) ## i(15 downto 13)){ + is(0){ret := addi5spnImm ## B"00010" ## B"000" ## rcl ## B"0010011"} //C.ADDI4SPN -> addi rd0, x2, nzuimm[9:2]. + if(rvd) is(1){ret := ldImm ## rch ## B"011" ## rcl ## B"0000111"} // C.FLD + is(2){ret := lwImm ## rch ## B"010" ## rcl ## B"0000011"} //C.LW -> lw rd', offset[6:2](rs1') + if(rvf) is(3){ret := lwImm ## rch ## B"010" ## rcl ## B"0000111"} // C.FLW + if(rvd) is(5){ret := sdImm(11 downto 5) ## rcl ## rch ## B"011" ## sdImm(4 downto 0) ## B"0100111"} // C.FSD + is(6){ret := swImm(11 downto 5) ## rcl ## rch ## B"010" ## swImm(4 downto 0) ## B"0100011"} //C.SW -> sw rs2',offset[6:2](rs1') + if(rvf) is(7){ret := swImm(11 downto 5) ## rcl ## rch ## B"010" ## swImm(4 downto 0) ## B"0100111"} // C.FSW + is(8){ret := addImm ## i(11 downto 7) ## B"000" ## i(11 downto 7) ## B"0010011"} //C.ADDI -> addi rd, rd, nzimm[5:0]. + is(9){ret := jalImm(20) ## jalImm(10 downto 1) ## jalImm(11) ## jalImm(19 downto 12) ## x1 ## B"1101111"} //C.JAL -> jalr x1, rs1, 0. + is(10){ret := lImm ## B"00000" ## B"000" ## i(11 downto 7) ## B"0010011"} //C.LI -> addi rd, x0, imm[5:0]. + is(11){ //C.ADDI16SP C.LUI -> + val addi16sp = addi16spImm ## i(11 downto 7) ## B"000" ## i(11 downto 7) ## B"0010011" + val lui = luiImm(31 downto 12) ## i(11 downto 7) ## B"0110111" + ret := (i(11 downto 7) === 2) ? addi16sp | lui + } + is(12){ + val isImmediate = i(11 downto 10) =/= B"11" + val isShift = !i(11) + val func3 = i(11 downto 10).mux( + 0 -> B"101", + 1 -> B"101", + 2 -> B"111", + 3 -> i(6 downto 5).mux( + 0 -> B"000", + 1 -> B"100", + 2 -> B"110", + 3 -> B"111" + ) + ) + val msbs = Mux( + sel = i(11 downto 10) === B"10", + whenTrue = B((6 downto 0) -> i(12)), //andi + whenFalse = B"0" ## (i(11 downto 10) === B"01" || (i(11 downto 10) === B"11" && i(6 downto 5) === B"00")) ## B"00000" + ) + val rs2Shift = (isShift || isImmediate) ? shiftImm | rcl + val opc = (isImmediate ? B"0010011" | B"0110011") + ret := msbs ## rs2Shift ## rch ## func3 ## rch ## opc + } + is(13){ ret := jImm(20) ## jImm(10 downto 1) ## jImm(11) ## jImm(19 downto 12) ## x0 ## B"1101111"} + is(14){ ret := bImm(12) ## bImm(10 downto 5) ## x0 ## rch ## B"000" ## bImm(4 downto 1) ## bImm(11) ## B"1100011" } + is(15){ ret := bImm(12) ## bImm(10 downto 5) ## x0 ## rch ## B"001" ## bImm(4 downto 1) ## bImm(11) ## B"1100011" } + is(16){ ret := B"0000000" ## i(6 downto 2) ## i(11 downto 7) ## B"001" ## i(11 downto 7) ## B"0010011" } + if(rvd) is(17){ret := ldspImm ## x2 ## B"011" ## i(11 downto 7) ## B"0000111" } // C.FLDSP + is(18){ ret := lwspImm ## x2 ## B"010" ## i(11 downto 7) ## B"0000011" } + if(rvf) is(19){ret := lwspImm ## x2 ## B"010" ## i(11 downto 7) ## B"0000111" } // C.FLWSP + is(20) { + val add = B"000_0000" ## i(6 downto 2) ## (i(12) ? i(11 downto 7) | x0) ## B"000" ## i(11 downto 7) ## B"0110011" //add => add rd, rd, rs2 mv => add rd, x0, rs2 + val j = B"0000_0000_0000" ## i(11 downto 7) ## B"000" ## (i(12) ? x1 | x0) ## B"1100111" //jr => jalr x0, rs1, 0. jalr => jalr x1, rs1, 0. + val ebreak = B"000000000001_00000_000_00000_1110011" //EBREAK + val addJ = (i(6 downto 2) === 0) ? j | add + ret := (i(12 downto 2) === B"100_0000_0000") ? ebreak | addJ + } + + if(rvd) is(21){ret := sdspImm(11 downto 5) ## i(6 downto 2) ## x2 ## B"011" ## sdspImm(4 downto 0) ## B"0100111" } // C.FSDSP + is(22){ ret := swspImm(11 downto 5) ## i(6 downto 2) ## x2 ## B"010" ## swspImm(4 downto 0) ## B"0100011" } + if(rvf) is(23){ret := swspImm(11 downto 5) ## i(6 downto 2) ## x2 ## B"010" ## swspImm(4 downto 0) ## B"0100111" } // C.FSwSP + } + + ret + } +} + + +object StreamForkVex{ + def apply[T <: Data](input : Stream[T], portCount: Int, flush : Bool/*, flushDiscardInput : Boolean*/) : Vec[Stream[T]] = { + val outputs = Vec(cloneOf(input), portCount) + val linkEnable = Vec(RegInit(True), portCount) + + input.ready := True + for (i <- 0 until portCount) { + when(!outputs(i).ready && linkEnable(i)) { + input.ready := False + } + } + + for (i <- 0 until portCount) { + outputs(i).valid := input.valid && linkEnable(i) + outputs(i).payload := input.payload + when(outputs(i).fire) { + linkEnable(i) := False + } + } + + when(input.ready || flush) { + linkEnable.foreach(_ := True) + } + outputs + } +} + + +object StreamVexPimper{ + implicit class StreamFlushPimper[T <: Data](pimped : Stream[T]){ + def m2sPipeWithFlush(flush : Bool, discardInput : Boolean = true, collapsBubble : Boolean = true, flushInput : Bool = null): Stream[T] = { + val ret = cloneOf(pimped).setCompositeName(pimped, "m2sPipe", true) + + val rValid = RegInit(False) + val rData = Reg(pimped.payloadType) + if(!discardInput) rValid.clearWhen(flush) + + pimped.ready := (Bool(collapsBubble) && !ret.valid) || ret.ready + + when(pimped.ready) { + if(flushInput == null) + rValid := pimped.valid + else + rValid := pimped.valid && !flushInput + rData := pimped.payload + } + + ret.valid := rValid + ret.payload := rData + + if(discardInput) rValid.clearWhen(flush) + + ret + } + + def s2mPipe(flush : Bool): Stream[T] = { + val ret = cloneOf(pimped) + + val rValid = RegInit(False) + val rBits = Reg(pimped.payloadType) + + ret.valid := pimped.valid || rValid + pimped.ready := !rValid + ret.payload := Mux(rValid, rBits, pimped.payload) + + when(ret.ready) { + rValid := False + } + + when(pimped.ready && (!ret.ready)) { + rValid := pimped.valid + rBits := pimped.payload + } + + rValid.clearWhen(flush) + + ret + } + } + +} + + + +//case class FlowFifoLowLatency[T <: Data](dataType: T, depth: Int) extends Component { +// require(depth >= 1) +// val io = new Bundle { +// val push = slave Flow (dataType) +// val pop = master Stream (dataType) +// val flush = in Bool() +// } +// +// +// val mem = Vec(Reg(dataType), depth) +// val rPtr, wPtr = Counter(depth + 1) +// when(io.push.valid){ +// mem(wPtr) := io.push.payload +// wPtr.increment() +// } +// +// when(io.pop.fire){ +// rPtr.increment() +// } +// io.pop.valid := rPtr =/= wPtr +// +// +//}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/MmuPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/MmuPlugin.scala new file mode 100644 index 0000000..093f59a --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/MmuPlugin.scala @@ -0,0 +1,313 @@ +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable.ArrayBuffer + +trait DBusAccessService{ + def newDBusAccess() : DBusAccess +} + +case class DBusAccessCmd() extends Bundle { + val address = UInt(32 bits) + val size = UInt(2 bits) + val write = Bool + val data = Bits(32 bits) + val writeMask = Bits(4 bits) +} + +case class DBusAccessRsp() extends Bundle { + val data = Bits(32 bits) + val error = Bool() + val redo = Bool() +} + +case class DBusAccess() extends Bundle { + val cmd = Stream(DBusAccessCmd()) + val rsp = Flow(DBusAccessRsp()) +} + + +object MmuPort{ + val PRIORITY_DATA = 1 + val PRIORITY_INSTRUCTION = 0 +} +case class MmuPort(bus : MemoryTranslatorBus, priority : Int, args : MmuPortConfig, id : Int) + +case class MmuPortConfig(portTlbSize : Int, latency : Int = 0, earlyRequireMmuLockup : Boolean = false, earlyCacheHits : Boolean = false) + +class MmuPlugin(ioRange : UInt => Bool, + virtualRange : UInt => Bool = address => True, +// allowUserIo : Boolean = false, + enableMmuInMachineMode : Boolean = false) extends Plugin[VexRiscv] with MemoryTranslator { + + var dBusAccess : DBusAccess = null + val portsInfo = ArrayBuffer[MmuPort]() + + override def newTranslationPort(priority : Int,args : Any): MemoryTranslatorBus = { + val config = args.asInstanceOf[MmuPortConfig] + val port = MmuPort(MemoryTranslatorBus(MemoryTranslatorBusParameter(wayCount = config.portTlbSize, latency = config.latency)),priority, config, portsInfo.length) + portsInfo += port + port.bus + } + + object IS_SFENCE_VMA2 extends Stageable(Bool) + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(IS_SFENCE_VMA2, False) + decoderService.add(SFENCE_VMA, List(IS_SFENCE_VMA2 -> True)) + + + dBusAccess = pipeline.service(classOf[DBusAccessService]).newDBusAccess() + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + import Riscv._ + val csrService = pipeline.service(classOf[CsrInterface]) + + //Sorted by priority + val sortedPortsInfo = portsInfo.sortBy(_.priority) + + case class CacheLine() extends Bundle { + val valid, exception, superPage = Bool + val virtualAddress = Vec(UInt(10 bits), UInt(10 bits)) + val physicalAddress = Vec(UInt(10 bits), UInt(10 bits)) + val allowRead, allowWrite, allowExecute, allowUser = Bool + + def init = { + valid init (False) + this + } + } + + val csr = pipeline plug new Area{ + val status = new Area{ + val sum, mxr, mprv = RegInit(False) + } + val satp = new Area { + val mode = RegInit(False) + val asid = Reg(Bits(9 bits)) + val ppn = Reg(UInt(20 bits)) + } + + for(offset <- List(CSR.MSTATUS, CSR.SSTATUS)) csrService.rw(offset, 19 -> status.mxr, 18 -> status.sum, 17 -> status.mprv) + csrService.rw(CSR.SATP, 31 -> satp.mode, 22 -> satp.asid, 0 -> satp.ppn) + } + + val core = pipeline plug new Area { + val ports = for (port <- sortedPortsInfo) yield new Area { + val handle = port + val id = port.id + val privilegeService = pipeline.serviceElse(classOf[PrivilegeService], PrivilegeServiceDefault()) + val cache = Vec(Reg(CacheLine()) init, port.args.portTlbSize) + val dirty = RegInit(False).allowUnsetRegToAvoidLatch + if(port.args.earlyRequireMmuLockup){ + dirty clearWhen(!port.bus.cmd.last.isStuck) + } + + def toRsp[T <: Data](data : T, from : MemoryTranslatorCmd) : T = from match { + case _ if from == port.bus.cmd.last => data + case _ => { + val next = port.bus.cmd.dropWhile(_ != from)(1) + toRsp(RegNextWhen(data, !next.isStuck), next) + } + } + val requireMmuLockupCmd = port.bus.cmd.takeRight(if(port.args.earlyRequireMmuLockup) 2 else 1).head + + val requireMmuLockupCalc = virtualRange(requireMmuLockupCmd.virtualAddress) && !requireMmuLockupCmd.bypassTranslation && csr.satp.mode + if(!enableMmuInMachineMode) { + requireMmuLockupCalc clearWhen(!csr.status.mprv && privilegeService.isMachine()) + when(privilegeService.isMachine()) { + if (port.priority == MmuPort.PRIORITY_DATA) { + requireMmuLockupCalc clearWhen (!csr.status.mprv || pipeline(MPP) === 3) + } else { + requireMmuLockupCalc := False + } + } + } + + val cacheHitsCmd = port.bus.cmd.takeRight(if(port.args.earlyCacheHits) 2 else 1).head + val cacheHitsCalc = B(cache.map(line => line.valid && line.virtualAddress(1) === cacheHitsCmd.virtualAddress(31 downto 22) && (line.superPage || line.virtualAddress(0) === cacheHitsCmd.virtualAddress(21 downto 12)))) + + + val requireMmuLockup = toRsp(requireMmuLockupCalc, requireMmuLockupCmd) + val cacheHits = toRsp(cacheHitsCalc, cacheHitsCmd) + + val cacheHit = cacheHits.asBits.orR + val cacheLine = MuxOH(cacheHits, cache) + val entryToReplace = Counter(port.args.portTlbSize) + + + when(requireMmuLockup) { + port.bus.rsp.physicalAddress := cacheLine.physicalAddress(1) @@ (cacheLine.superPage ? port.bus.cmd.last.virtualAddress(21 downto 12) | cacheLine.physicalAddress(0)) @@ port.bus.cmd.last.virtualAddress(11 downto 0) + port.bus.rsp.allowRead := cacheLine.allowRead || csr.status.mxr && cacheLine.allowExecute + port.bus.rsp.allowWrite := cacheLine.allowWrite + port.bus.rsp.allowExecute := cacheLine.allowExecute + port.bus.rsp.exception := !dirty && cacheHit && (cacheLine.exception || cacheLine.allowUser && privilegeService.isSupervisor() && !csr.status.sum || !cacheLine.allowUser && privilegeService.isUser()) + port.bus.rsp.refilling := dirty || !cacheHit + port.bus.rsp.isPaging := True + } otherwise { + port.bus.rsp.physicalAddress := port.bus.cmd.last.virtualAddress + port.bus.rsp.allowRead := True + port.bus.rsp.allowWrite := True + port.bus.rsp.allowExecute := True + port.bus.rsp.exception := False + port.bus.rsp.refilling := False + port.bus.rsp.isPaging := False + } + port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress) + + port.bus.rsp.bypassTranslation := !requireMmuLockup + for(wayId <- 0 until port.args.portTlbSize){ + port.bus.rsp.ways(wayId).sel := cacheHits(wayId) + port.bus.rsp.ways(wayId).physical := cache(wayId).physicalAddress(1) @@ (cache(wayId).superPage ? port.bus.cmd.last.virtualAddress(21 downto 12) | cache(wayId).physicalAddress(0)) @@ port.bus.cmd.last.virtualAddress(11 downto 0) + } + + // Avoid keeping any invalid line in the cache after an exception. + // https://github.com/riscv/riscv-linux/blob/8fe28cb58bcb235034b64cbbb7550a8a43fd88be/arch/riscv/include/asm/pgtable.h#L276 + when(service(classOf[IContextSwitching]).isContextSwitching) { + for (line <- cache) { + when(line.exception) { + line.valid := False + } + } + } + } + + val shared = new Area { + val State = new SpinalEnum{ + val IDLE, L1_CMD, L1_RSP, L0_CMD, L0_RSP = newElement() + } + val state = RegInit(State.IDLE) + val vpn = Reg(Vec(UInt(10 bits), UInt(10 bits))) + val portSortedOh = Reg(Bits(portsInfo.length bits)) + case class PTE() extends Bundle { + val V, R, W ,X, U, G, A, D = Bool() + val RSW = Bits(2 bits) + val PPN0 = UInt(10 bits) + val PPN1 = UInt(12 bits) + } + + val dBusRspStaged = dBusAccess.rsp.stage() + val dBusRsp = new Area{ + val pte = PTE() + pte.assignFromBits(dBusRspStaged.data) + val exception = !pte.V || (!pte.R && pte.W) || dBusRspStaged.error + val leaf = pte.R || pte.X + } + + val pteBuffer = RegNextWhen(dBusRsp.pte, dBusRspStaged.valid && !dBusRspStaged.redo) + + dBusAccess.cmd.valid := False + dBusAccess.cmd.write := False + dBusAccess.cmd.size := 2 + dBusAccess.cmd.address.assignDontCare() + dBusAccess.cmd.data.assignDontCare() + dBusAccess.cmd.writeMask.assignDontCare() + + val refills = OHMasking.last(B(ports.map(port => port.handle.bus.cmd.last.isValid && port.requireMmuLockup && !port.dirty && !port.cacheHit))) + switch(state){ + is(State.IDLE){ + when(refills.orR){ + portSortedOh := refills + state := State.L1_CMD + val address = MuxOH(refills, sortedPortsInfo.map(_.bus.cmd.last.virtualAddress)) + vpn(1) := address(31 downto 22) + vpn(0) := address(21 downto 12) + } +// for(port <- portsInfo.sortBy(_.priority)){ +// when(port.bus.cmd.isValid && port.bus.rsp.refilling){ +// vpn(1) := port.bus.cmd.virtualAddress(31 downto 22) +// vpn(0) := port.bus.cmd.virtualAddress(21 downto 12) +// portId := port.id +// state := State.L1_CMD +// } +// } + } + is(State.L1_CMD){ + dBusAccess.cmd.valid := True + dBusAccess.cmd.address := csr.satp.ppn @@ vpn(1) @@ U"00" + when(dBusAccess.cmd.ready){ + state := State.L1_RSP + } + } + is(State.L1_RSP){ + when(dBusRspStaged.valid){ + state := State.L0_CMD + when(dBusRsp.leaf || dBusRsp.exception){ + state := State.IDLE + } + when(dBusRspStaged.redo){ + state := State.L1_CMD + } + } + } + is(State.L0_CMD){ + dBusAccess.cmd.valid := True + dBusAccess.cmd.address := pteBuffer.PPN1(9 downto 0) @@ pteBuffer.PPN0 @@ vpn(0) @@ U"00" + when(dBusAccess.cmd.ready){ + state := State.L0_RSP + } + } + is(State.L0_RSP){ + when(dBusRspStaged.valid) { + state := State.IDLE + when(dBusRspStaged.redo){ + state := State.L0_CMD + } + } + } + } + + for((port, id) <- sortedPortsInfo.zipWithIndex) { + port.bus.busy := state =/= State.IDLE && portSortedOh(id) + } + + when(dBusRspStaged.valid && !dBusRspStaged.redo && (dBusRsp.leaf || dBusRsp.exception)){ + for((port, id) <- ports.zipWithIndex) { + when(portSortedOh(id)) { + port.entryToReplace.increment() + if(port.handle.args.earlyRequireMmuLockup) { + port.dirty := True + } //Avoid having non coherent TLB lookup + for ((line, lineId) <- port.cache.zipWithIndex) { + when(port.entryToReplace === lineId){ + val superPage = state === State.L1_RSP + line.valid := True + line.exception := dBusRsp.exception || (superPage && dBusRsp.pte.PPN0 =/= 0) + line.virtualAddress := vpn + line.physicalAddress := Vec(dBusRsp.pte.PPN0, dBusRsp.pte.PPN1(9 downto 0)) + line.allowRead := dBusRsp.pte.R + line.allowWrite := dBusRsp.pte.W + line.allowExecute := dBusRsp.pte.X + line.allowUser := dBusRsp.pte.U + line.superPage := state === State.L1_RSP + } + } + } + } + } + } + } + + val fenceStage = execute + + //Both SFENCE_VMA and SATP reschedule the next instruction in the CsrPlugin itself with one extra cycle to ensure side effect propagation. + fenceStage plug new Area{ + import fenceStage._ + when(arbitration.isValid && arbitration.isFiring && input(IS_SFENCE_VMA2)){ + for(port <- core.ports; line <- port.cache) line.valid := False + } + + csrService.onWrite(CSR.SATP){ + for(port <- core.ports; line <- port.cache) line.valid := False + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/Mul16Plugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/Mul16Plugin.scala new file mode 100644 index 0000000..f2a63c3 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/Mul16Plugin.scala @@ -0,0 +1,119 @@ +package vexriscv.plugin + +import vexriscv._ +import vexriscv.plugin._ +import spinal.core._ + +/** + * A multiplication plugin using only 16-bit multiplications + */ +class Mul16Plugin extends Plugin[VexRiscv]{ + + object MUL_LL extends Stageable(UInt(32 bits)) + object MUL_LH extends Stageable(UInt(32 bits)) + object MUL_HL extends Stageable(UInt(32 bits)) + object MUL_HH extends Stageable(UInt(32 bits)) + + object MUL extends Stageable(Bits(64 bits)) + + object IS_MUL extends Stageable(Bool) + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + + val actions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + RS1_USE -> True, + RS2_USE -> True, + IS_MUL -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(IS_MUL, False) + decoderService.add(List( + MULX -> actions + )) + + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + // Prepare signed inputs for the multiplier in the next stage. + // This will map them best to an FPGA DSP. + execute plug new Area { + import execute._ + val a,b = Bits(32 bit) + + a := input(SRC1) + b := input(SRC2) + + val aLow = a(15 downto 0).asUInt + val bLow = b(15 downto 0).asUInt + val aHigh = a(31 downto 16).asUInt + val bHigh = b(31 downto 16).asUInt + + insert(MUL_LL) := aLow * bLow + insert(MUL_LH) := aLow * bHigh + insert(MUL_HL) := aHigh * bLow + insert(MUL_HH) := aHigh * bHigh + } + + memory plug new Area { + import memory._ + + val ll = UInt(32 bits) + val lh = UInt(33 bits) + val hl = UInt(32 bits) + val hh = UInt(32 bits) + + ll := input(MUL_LL) + lh := input(MUL_LH).resized + hl := input(MUL_HL) + hh := input(MUL_HH) + + val hllh = lh + hl + insert(MUL) := ((hh ## ll(31 downto 16)).asUInt + hllh) ## ll(15 downto 0) + } + + writeBack plug new Area { + import writeBack._ + val aSigned,bSigned = Bool + switch(input(INSTRUCTION)(13 downto 12)) { + is(B"01") { + aSigned := True + bSigned := True + } + is(B"10") { + aSigned := True + bSigned := False + } + default { + aSigned := False + bSigned := False + } + } + + val a = (aSigned && input(SRC1).msb) ? input(SRC2).asUInt | U(0) + val b = (bSigned && input(SRC2).msb) ? input(SRC1).asUInt | U(0) + + when(arbitration.isValid && input(IS_MUL)){ + switch(input(INSTRUCTION)(13 downto 12)){ + is(B"00"){ + output(REGFILE_WRITE_DATA) := input(MUL)(31 downto 0) + } + is(B"01",B"10",B"11"){ + output(REGFILE_WRITE_DATA) := (((input(MUL)(63 downto 32)).asUInt + ~a) + (~b + 2)).asBits + } + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/MulDivIterativePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/MulDivIterativePlugin.scala new file mode 100644 index 0000000..fff12ef --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/MulDivIterativePlugin.scala @@ -0,0 +1,188 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import vexriscv.{VexRiscv, _} + +object MulDivIterativePlugin{ + object IS_MUL extends Stageable(Bool) + object IS_DIV extends Stageable(Bool) + object IS_REM extends Stageable(Bool) + object IS_RS1_SIGNED extends Stageable(Bool) + object IS_RS2_SIGNED extends Stageable(Bool) + object FAST_DIV_VALID extends Stageable(Bool) + object FAST_DIV_VALUE extends Stageable(UInt(4 bits)) +} + +class MulDivIterativePlugin(genMul : Boolean = true, + genDiv : Boolean = true, + mulUnrollFactor : Int = 1, + divUnrollFactor : Int = 1, + dhrystoneOpt : Boolean = false, + customMul : (UInt, UInt, Stage, VexRiscv) => Area = null) extends Plugin[VexRiscv] with VexRiscvRegressionArg { + import MulDivIterativePlugin._ + + override def getVexRiscvRegressionArgs(): Seq[String] = { + var args = List[String]() + if(genMul) args :+= "MUL=yes" + if(genDiv) args :+= "DIV=yes" + args + } + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + + val commonActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> Bool(pipeline.stages.last == pipeline.execute), + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True, + RS2_USE -> True + ) + + + val decoderService = pipeline.service(classOf[DecoderService]) + + if(genMul) { + val mulActions = commonActions ++ List(IS_MUL -> True) + decoderService.addDefault(IS_MUL, False) + decoderService.add(List( + MUL -> (mulActions ++ List(IS_RS1_SIGNED -> False, IS_RS2_SIGNED -> False)), + MULH -> (mulActions ++ List(IS_RS1_SIGNED -> True, IS_RS2_SIGNED -> True)), + MULHSU -> (mulActions ++ List(IS_RS1_SIGNED -> True, IS_RS2_SIGNED -> False)), + MULHU -> (mulActions ++ List(IS_RS1_SIGNED -> False, IS_RS2_SIGNED -> False)) + )) + } + + if(genDiv) { + val divActions = commonActions ++ List(IS_DIV -> True) + decoderService.addDefault(IS_DIV, False) + decoderService.add(List( + DIV -> (divActions ++ List(IS_RS1_SIGNED -> True, IS_RS2_SIGNED -> True)), + DIVU -> (divActions ++ List(IS_RS1_SIGNED -> False, IS_RS2_SIGNED -> False)), + REM -> (divActions ++ List(IS_RS1_SIGNED -> True, IS_RS2_SIGNED -> True)), + REMU -> (divActions ++ List(IS_RS1_SIGNED -> False, IS_RS2_SIGNED -> False)) + )) + } + + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + if(!genMul && !genDiv) return + + val flushStage = if(memory != null) memory else execute + flushStage plug new Area { + import flushStage._ + + //Shared ressources + val rs1 = Reg(UInt(33 bits)) + val rs2 = Reg(UInt(32 bits)) + val accumulator = Reg(UInt(65 bits)) + + //FrontendOK is only used for CPU configs without memory/writeback stages, were it is required to wait one extra cycle + // to let's the frontend process rs1 rs2 registers + val frontendOk = if(flushStage != execute) True else RegInit(False) setWhen(arbitration.isValid && !pipeline.service(classOf[HazardService]).hazardOnExecuteRS && ((if(genDiv) input(IS_DIV) else False) || (if(genMul) input(IS_MUL) else False))) clearWhen(arbitration.isMoving) + + val mul = ifGen(genMul) (if(customMul != null) customMul(rs1,rs2,memory,pipeline) else new Area{ + assert(isPow2(mulUnrollFactor)) + val counter = Counter(32 / mulUnrollFactor + 1) + val done = counter.willOverflowIfInc + when(arbitration.isValid && input(IS_MUL)){ + when(!frontendOk || !done){ + arbitration.haltItself := True + } + when(frontendOk && !done){ + arbitration.haltItself := True + counter.increment() + rs2 := rs2 |>> mulUnrollFactor + val sumElements = ((0 until mulUnrollFactor).map(i => rs2(i) ? (rs1 << i) | U(0)) :+ (accumulator >> 32)) + val sumResult = sumElements.map(_.asSInt.resize(32 + mulUnrollFactor + 1).asUInt).reduceBalancedTree(_ + _) + accumulator := (sumResult @@ accumulator(31 downto 0)) >> mulUnrollFactor + } + output(REGFILE_WRITE_DATA) := ((input(INSTRUCTION)(13 downto 12) === B"00") ? accumulator(31 downto 0) | accumulator(63 downto 32)).asBits + } + when(!arbitration.isStuck) { + counter.clear() + } + }) + + + val div = ifGen(genDiv) (new Area{ + assert(isPow2(divUnrollFactor)) + def area = this + //register allocation + def numerator = rs1(31 downto 0) + def denominator = rs2 + def remainder = accumulator(31 downto 0) + + val needRevert = Reg(Bool) + val counter = Counter(32 / divUnrollFactor + 2) + val done = Reg(Bool) setWhen(counter === counter.end-1) clearWhen(!arbitration.isStuck) + val result = Reg(Bits(32 bits)) + when(arbitration.isValid && input(IS_DIV)){ + when(!frontendOk || !done){ + arbitration.haltItself := True + } + when(frontendOk && !done){ + counter.increment() + + def stages(inNumerator: UInt, inRemainder: UInt, stage: Int): Unit = stage match { + case 0 => { + numerator := inNumerator + remainder := inRemainder + } + case _ => new Area { + val remainderShifted = (inRemainder ## inNumerator.msb).asUInt + val remainderMinusDenominator = remainderShifted - denominator + val outRemainder = !remainderMinusDenominator.msb ? remainderMinusDenominator.resize(32 bits) | remainderShifted.resize(32 bits) + val outNumerator = (inNumerator ## !remainderMinusDenominator.msb).asUInt.resize(32 bits) + stages(outNumerator, outRemainder, stage - 1) + }.setCompositeName(area, "stage_" + (divUnrollFactor-stage)) + } + + stages(numerator, remainder, divUnrollFactor) + + when(counter === 32 / divUnrollFactor){ + val selectedResult = (input(INSTRUCTION)(13) ? remainder | numerator) + result := selectedResult.twoComplement(needRevert).asBits.resized + } + } + + output(REGFILE_WRITE_DATA) := result + } + }) + + //Execute stage logic to drive memory stage's input regs + when(if(flushStage != execute) !arbitration.isStuck else !frontendOk){ + accumulator := 0 + def twoComplement(that : Bits, enable: Bool): UInt = (Mux(enable, ~that, that).asUInt + enable.asUInt) + val rs2NeedRevert = execute.input(RS2).msb && execute.input(IS_RS2_SIGNED) + val rs1NeedRevert = (if(genMul)(execute.input(IS_MUL) && rs2NeedRevert) else False) || + (if(genDiv)(execute.input(IS_DIV) && execute.input(RS1).msb && execute.input(IS_RS1_SIGNED)) else False) + val rs1Extended = B((32 downto 32) -> (execute.input(IS_RS1_SIGNED) && execute.input(RS1).msb), (31 downto 0) -> execute.input(RS1)) + + rs1 := twoComplement(rs1Extended, rs1NeedRevert).resized + rs2 := twoComplement(execute.input(RS2), rs2NeedRevert) + if(genDiv) div.needRevert := (rs1NeedRevert ^ (rs2NeedRevert && !execute.input(INSTRUCTION)(13))) && !(execute.input(RS2) === 0 && execute.input(IS_RS2_SIGNED) && !execute.input(INSTRUCTION)(13)) + if(genDiv) div.counter.clear() + } + + if(dhrystoneOpt) { + execute.insert(FAST_DIV_VALID) := execute.input(IS_DIV) && execute.input(INSTRUCTION)(13 downto 12) === B"00" && !execute.input(RS1).msb && !execute.input(RS2).msb && execute.input(RS1).asUInt < 16 && execute.input(RS2).asUInt < 16 && execute.input(RS2) =/= 0 + execute.insert(FAST_DIV_VALUE) := (0 to 15).flatMap(n => (0 to 15).map(d => U(if (d == 0) 0 else n / d, 4 bits))).read(U(execute.input(RS1)(3 downto 0)) @@ U(execute.input(RS2)(3 downto 0))) //(U(execute.input(RS1)(3 downto 0)) / U(execute.input(RS2)(3 downto 0)) + when(execute.input(FAST_DIV_VALID)) { + execute.output(IS_DIV) := False + } + when(input(FAST_DIV_VALID)) { + output(REGFILE_WRITE_DATA) := B(0, 28 bits) ## input(FAST_DIV_VALUE) + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/MulPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/MulPlugin.scala new file mode 100644 index 0000000..3e909a0 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/MulPlugin.scala @@ -0,0 +1,159 @@ +package vexriscv.plugin +import vexriscv._ +import vexriscv.VexRiscv +import spinal.core._ +import spinal.lib.KeepAttribute + +//Input buffer generaly avoid the FPGA synthesis to duplicate reg inside the DSP cell, which could stress timings quite much. +class MulPlugin(var inputBuffer : Boolean = false, + var outputBuffer : Boolean = false) extends Plugin[VexRiscv] with VexRiscvRegressionArg { + object MUL_LL extends Stageable(UInt(32 bits)) + object MUL_LH extends Stageable(SInt(34 bits)) + object MUL_HL extends Stageable(SInt(34 bits)) + object MUL_HH extends Stageable(SInt(34 bits)) + + object MUL_LOW extends Stageable(SInt(34+16+2 bits)) + + object IS_MUL extends Stageable(Bool) + + override def getVexRiscvRegressionArgs(): Seq[String] = { + List("MUL=yes") + } + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + + val actions = List[(Stageable[_ <: BaseType],Any)]( +// SRC1_CTRL -> Src1CtrlEnum.RS, +// SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + RS1_USE -> True, + RS2_USE -> True, + IS_MUL -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(IS_MUL, False) + decoderService.add(List( + MULX -> actions + )) + + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + + //Do partial multiplication, four times 16 bits * 16 bits + execute plug new Area { + import execute._ + val aSigned,bSigned = Bool + val a,b = Bits(32 bit) + +// a := input(SRC1) +// b := input(SRC2) + + val delay = (if(inputBuffer) 1 else 0) + (if(outputBuffer) 1 else 0) + + val delayLogic = (delay != 0) generate new Area{ + val counter = Reg(UInt(log2Up(delay+1) bits)) + when(arbitration.isValid && input(IS_MUL) && counter =/= delay){ + arbitration.haltItself := True + } + + counter := counter + 1 + when(!arbitration.isStuck || arbitration.isStuckByOthers){ + counter := 0 + } + } + + val withInputBuffer = inputBuffer generate new Area{ + val rs1 = RegNext(input(RS1)) + val rs2 = RegNext(input(RS2)) + a := rs1 + b := rs2 + } + + val noInputBuffer = (!inputBuffer) generate new Area{ + a := input(RS1) + b := input(RS2) + } + + switch(input(INSTRUCTION)(13 downto 12)) { + is(B"01") { + aSigned := True + bSigned := True + } + is(B"10") { + aSigned := True + bSigned := False + } + default { + aSigned := False + bSigned := False + } + } + + val aULow = a(15 downto 0).asUInt + val bULow = b(15 downto 0).asUInt + val aSLow = (False ## a(15 downto 0)).asSInt + val bSLow = (False ## b(15 downto 0)).asSInt + val aHigh = (((aSigned && a.msb) ## a(31 downto 16))).asSInt + val bHigh = (((bSigned && b.msb) ## b(31 downto 16))).asSInt + + val withOuputBuffer = outputBuffer generate new Area{ + val mul_ll = RegNext(aULow * bULow) + val mul_lh = RegNext(aSLow * bHigh) + val mul_hl = RegNext(aHigh * bSLow) + val mul_hh = RegNext(aHigh * bHigh) + + insert(MUL_LL) := mul_ll + insert(MUL_LH) := mul_lh + insert(MUL_HL) := mul_hl + insert(MUL_HH) := mul_hh + } + + val noOutputBuffer = (!outputBuffer) generate new Area{ + insert(MUL_LL) := aULow * bULow + insert(MUL_LH) := aSLow * bHigh + insert(MUL_HL) := aHigh * bSLow + insert(MUL_HH) := aHigh * bHigh + } + + Component.current.afterElaboration{ + //Avoid synthesis tools to retime RS1 RS2 from execute stage to decode stage leading to bad timings (ex : Vivado, even if retiming is disabled) + KeepAttribute(input(RS1)) + KeepAttribute(input(RS2)) + } + } + + //First aggregation of partial multiplication + memory plug new Area { + import memory._ + insert(MUL_LOW) := S(0, MUL_HL.dataType.getWidth + 16 + 2 bit) + (False ## input(MUL_LL)).asSInt + (input(MUL_LH) << 16) + (input(MUL_HL) << 16) + } + + //Final aggregation of partial multiplications, REGFILE_WRITE_DATA overriding + writeBack plug new Area { + import writeBack._ + val result = input(MUL_LOW) + (input(MUL_HH) << 32) + + + when(arbitration.isValid && input(IS_MUL)){ + switch(input(INSTRUCTION)(13 downto 12)){ + is(B"00"){ + output(REGFILE_WRITE_DATA) := input(MUL_LOW)(31 downto 0).asBits + } + is(B"01",B"10",B"11"){ + output(REGFILE_WRITE_DATA) := result(63 downto 32).asBits + } + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/MulSimplePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/MulSimplePlugin.scala new file mode 100644 index 0000000..3b407e1 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/MulSimplePlugin.scala @@ -0,0 +1,92 @@ +package vexriscv.plugin +import vexriscv._ +import vexriscv.VexRiscv +import spinal.core._ + +class MulSimplePlugin extends Plugin[VexRiscv]{ + object MUL_OPA extends Stageable(SInt(33 bits)) + object MUL_OPB extends Stageable(SInt(33 bits)) + object MUL extends Stageable(Bits(64 bits)) + + object IS_MUL extends Stageable(Bool) + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + + val actions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> Bool(pipeline.stages.last == pipeline.execute), + BYPASSABLE_MEMORY_STAGE -> Bool(pipeline.stages.last == pipeline.memory), + RS1_USE -> True, + RS2_USE -> True, + IS_MUL -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(IS_MUL, False) + decoderService.add(List( + MULX -> actions + )) + + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + // Prepare signed inputs for the multiplier in the next stage. + // This will map them best to an FPGA DSP. + execute plug new Area { + import execute._ + val aSigned,bSigned = Bool + val a,b = Bits(32 bit) + + a := input(SRC1) + b := input(SRC2) + switch(input(INSTRUCTION)(13 downto 12)) { + is(B"01") { + aSigned := True + bSigned := True + } + is(B"10") { + aSigned := True + bSigned := False + } + default { + aSigned := False + bSigned := False + } + } + + insert(MUL_OPA) := ((aSigned ? a.msb | False) ## a).asSInt + insert(MUL_OPB) := ((bSigned ? b.msb | False) ## b).asSInt + } + + val injectionStage = if(pipeline.memory != null) pipeline.memory else pipeline.execute + injectionStage plug new Area { + import injectionStage._ + + insert(MUL) := (input(MUL_OPA) * input(MUL_OPB))(63 downto 0).asBits + } + + val memStage = stages.last + memStage plug new Area { + import memStage._ + + when(arbitration.isValid && input(IS_MUL)){ + switch(input(INSTRUCTION)(13 downto 12)){ + is(B"00"){ + output(REGFILE_WRITE_DATA) := input(MUL)(31 downto 0) + } + is(B"01",B"10",B"11"){ + output(REGFILE_WRITE_DATA) := input(MUL)(63 downto 32) + } + } + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/NoPipeliningPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/NoPipeliningPlugin.scala new file mode 100644 index 0000000..b4ad22b --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/NoPipeliningPlugin.scala @@ -0,0 +1,23 @@ +package vexriscv.plugin + +import spinal.core._ +import spinal.lib._ +import vexriscv._ + + +class NoPipeliningPlugin() extends Plugin[VexRiscv] { + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(HAS_SIDE_EFFECT, False) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val writesInPipeline = stages.dropWhile(_ != execute).map(s => s.arbitration.isValid && s.input(REGFILE_WRITE_VALID)) :+ RegNext(stages.last.arbitration.isValid && stages.last.input(REGFILE_WRITE_VALID)) + decode.arbitration.haltByOther.setWhen(stagesFromExecute.map(_.arbitration.isValid).orR) + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/PcManagerSimplePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/PcManagerSimplePlugin.scala new file mode 100644 index 0000000..5b1226a --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/PcManagerSimplePlugin.scala @@ -0,0 +1,145 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable.ArrayBuffer + + + + +class PcManagerSimplePlugin(resetVector : BigInt, + relaxedPcCalculation : Boolean = false, + keepPcPlus4 : Boolean = true) extends Plugin[VexRiscv]{ + override def build(pipeline: VexRiscv): Unit = {println("PcManagerSimplePlugin is now useless")} +} + + +//class PcManagerSimplePlugin(resetVector : BigInt, +// relaxedPcCalculation : Boolean = false, +// keepPcPlus4 : Boolean = true) extends Plugin[VexRiscv] with JumpService{ +// //FetchService interface +// case class JumpInfo(interface : Flow[UInt], stage: Stage, priority : Int) +// val jumpInfos = ArrayBuffer[JumpInfo]() +// override def createJumpInterface(stage: Stage, priority : Int = 0): Flow[UInt] = { +// val interface = Flow(UInt(32 bits)) +// jumpInfos += JumpInfo(interface,stage, priority) +// interface +// } +// var prefetchExceptionPort : Flow[ExceptionCause] = null +// +// override def setup(pipeline: VexRiscv): Unit = { +// if(!relaxedPcCalculation) pipeline.unremovableStages += pipeline.prefetch +// } +// +// +// override def build(pipeline: VexRiscv): Unit = { +// import pipeline.config._ +// import pipeline._ +// +// if(relaxedPcCalculation) +// relaxedImpl(pipeline) +// else +// cycleEffectiveImpl(pipeline) +// +// //Formal verification signals generation +// prefetch.insert(FORMAL_PC_NEXT) := prefetch.input(PC) + 4 +// jumpInfos.foreach(info => { +// when(info.interface.valid){ +// info.stage.output(FORMAL_PC_NEXT) := info.interface.payload +// } +// }) +// } +// +// //reduce combinatorial path, and expose the PC to the pipeline as a register +// def relaxedImpl(pipeline: VexRiscv): Unit = { +// import pipeline.config._ +// import pipeline._ +// +// prefetch plug new Area { +// import prefetch._ +// //Stage always valid +// arbitration.isValid := True +// +// //PC calculation without Jump +// val pcReg = Reg(UInt(32 bits)) init(resetVector) addAttribute(Verilator.public) +// val pcPlus4 = pcReg + 4 +// if(keepPcPlus4) KeepAttribute(pcPlus4) +// when(arbitration.isFiring){ +// pcReg := pcPlus4 +// } +// +// //JumpService hardware implementation +// val jump = if(jumpInfos.length != 0) new Area { +// val sortedByStage = jumpInfos.sortWith((a, b) => { +// (pipeline.indexOf(a.stage) > pipeline.indexOf(b.stage)) || +// (pipeline.indexOf(a.stage) == pipeline.indexOf(b.stage) && a.priority > b.priority) +// }) +// val valids = sortedByStage.map(_.interface.valid) +// val pcs = sortedByStage.map(_.interface.payload) +// +// val pcLoad = Flow(UInt(32 bits)) +// pcLoad.valid := jumpInfos.map(_.interface.valid).orR +// pcLoad.payload := MuxOH(OHMasking.first(valids.asBits), pcs) +// +// //application of the selected jump request +// when(pcLoad.valid) { +// pcReg := pcLoad.payload +// } +// } +// +// insert(PC_CALC_WITHOUT_JUMP) := pcReg +// insert(PC) := pcReg +// } +// } +// +// //Jump take effect instantly (save one cycle), but expose the PC to the pipeline as a 'long' combinatorial path +// def cycleEffectiveImpl(pipeline: VexRiscv): Unit = { +// import pipeline.config._ +// import pipeline.prefetch +// +// prefetch plug new Area { +// import prefetch._ +// //Stage always valid +// arbitration.isValid := True +// +// //PC calculation without Jump +// val pcReg = Reg(UInt(32 bits)) init(resetVector) addAttribute(Verilator.public) +// val inc = RegInit(False) +// val pcBeforeJumps = pcReg + (inc ## B"00").asUInt +// insert(PC_CALC_WITHOUT_JUMP) := pcBeforeJumps +// val pc = UInt(32 bits) +// pc := input(PC_CALC_WITHOUT_JUMP) +// +// val samplePcNext = False +// +// //JumpService hardware implementation +// val jump = if(jumpInfos.length != 0) new Area { +// val sortedByStage = jumpInfos.sortWith((a, b) => pipeline.indexOf(a.stage) > pipeline.indexOf(b.stage)) +// val valids = sortedByStage.map(_.interface.valid) +// val pcs = sortedByStage.map(_.interface.payload) +// +// val pcLoad = Flow(UInt(32 bits)) +// pcLoad.valid := jumpInfos.map(_.interface.valid).orR +// pcLoad.payload := MuxOH(OHMasking.first(valids.asBits), pcs) +// +// //application of the selected jump request +// when(pcLoad.valid) { +// inc := False +// samplePcNext := True +// pc := pcLoad.payload +// } +// } +// +// when(arbitration.isFiring){ +// inc := True +// samplePcNext := True +// } +// +// when(samplePcNext) { pcReg := pc } +// +// insert(PC) := pc +// } +// } +//}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/Plugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/Plugin.scala new file mode 100644 index 0000000..96d2bc6 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/Plugin.scala @@ -0,0 +1,25 @@ +package vexriscv.plugin + +import vexriscv.{Pipeline, Stage} +import spinal.core.{Area, Nameable} + +/** + * Created by PIC32F_USER on 03/03/2017. + */ +trait Plugin[T <: Pipeline] extends Nameable{ + var pipeline : T = null.asInstanceOf[T] + setName(this.getClass.getSimpleName.replace("$","")) + + // Used to setup things with other plugins + def setup(pipeline: T) : Unit = {} + + //Used to flush out the required hardware (called after setup) + def build(pipeline: T) : Unit + + implicit class implicitsStage(stage: Stage){ + def plug[T <: Area](area : T) : T = {area.setCompositeName(stage,getName()).reflectNames();area} + } + implicit class implicitsPipeline(stage: Pipeline){ + def plug[T <: Area](area : T) = {area.setName(getName()).reflectNames();area} + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/PmpPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/PmpPlugin.scala new file mode 100644 index 0000000..35951e5 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/PmpPlugin.scala @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2021 Samuel Lindemer <samuel.lindemer@ri.se> + * + * SPDX-License-Identifier: MIT + */ + +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import vexriscv.plugin.MemoryTranslatorPort.{_} +import spinal.core._ +import spinal.lib._ +import spinal.lib.fsm._ + +/* Each 32-bit pmpcfg# register contains four 8-bit configuration sections. + * These section numbers contain flags which apply to regions defined by the + * corresponding pmpaddr# register. + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pmp3cfg | pmp2cfg | pmp1cfg | pmp0cfg | pmpcfg0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pmp7cfg | pmp6cfg | pmp5cfg | pmp4cfg | pmpcfg2 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 7 6 5 4 3 2 1 0 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | L | 0 | A | X | W | R | pmp#cfg + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + * L: locks configuration until system reset (including M-mode) + * 0: hardwired to zero + * A: 0 = OFF (null region / disabled) + * 1 = TOR (top of range) + * 2 = NA4 (naturally aligned four-byte region) + * 3 = NAPOT (naturally aligned power-of-two region, > 7 bytes) + * X: execute + * W: write + * R: read + * + * TOR: Each 32-bit pmpaddr# register defines the upper bound of the pmp region + * right-shifted by two bits. The lower bound of the region is the previous + * pmpaddr# register. In the case of pmpaddr0, the lower bound is address 0x0. + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | address[33:2] | pmpaddr# + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NAPOT: Each 32-bit pmpaddr# register defines the region address and the size + * of the pmp region. The number of concurrent 1s begging at the LSB indicates + * the size of the region as a power of two (e.g. 0x...0 = 8-byte, 0x...1 = + * 16-byte, 0x...11 = 32-byte, etc.). + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | address[33:2] |0|1|1|1|1| pmpaddr# + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NA4: This is essentially an edge case of NAPOT where the entire pmpaddr# + * register defines a 4-byte wide region. + * + * N.B. THIS IMPLEMENTATION ONLY SUPPORTS NAPOT ADDRESSING. REGIONS ARE NOT + * ORDERED BY PRIORITY. A PERMISSION IS GRANTED TO AN ACCESS IF ANY MATCHING + * PMP REGION HAS THAT PERMISSION ENABLED. + */ + +trait Pmp { + def OFF = 0 + def TOR = 1 + def NA4 = 2 + def NAPOT = 3 + + def xlen = 32 + def rBit = 0 + def wBit = 1 + def xBit = 2 + def aBits = 4 downto 3 + def lBit = 7 +} + +class PmpSetter(cutoff : Int) extends Component with Pmp { + val io = new Bundle { + val addr = in UInt(xlen bits) + val base, mask = out UInt(xlen - cutoff bits) + } + + val ones = io.addr & ~(io.addr + 1) + io.base := io.addr(xlen - 3 downto cutoff - 2) ^ ones(xlen - 3 downto cutoff - 2) + io.mask := ~(ones(xlen - 4 downto cutoff - 2) @@ U"1") +} + +case class ProtectedMemoryTranslatorPort(bus : MemoryTranslatorBus) + +class PmpPlugin(regions : Int, granularity : Int, ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator with Pmp { + assert(regions % 4 == 0 & regions <= 16) + assert(granularity >= 8) + + var setter : PmpSetter = null + var dPort, iPort : ProtectedMemoryTranslatorPort = null + val cutoff = log2Up(granularity) - 1 + + override def newTranslationPort(priority : Int, args : Any): MemoryTranslatorBus = { + val port = ProtectedMemoryTranslatorPort(MemoryTranslatorBus(new MemoryTranslatorBusParameter(0, 0))) + priority match { + case PRIORITY_INSTRUCTION => iPort = port + case PRIORITY_DATA => dPort = port + } + port.bus + } + + override def setup(pipeline: VexRiscv): Unit = { + setter = new PmpSetter(cutoff) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline.config._ + import pipeline._ + import Riscv._ + + val csrService = pipeline.service(classOf[CsrInterface]) + val privilegeService = pipeline.service(classOf[PrivilegeService]) + + val state = pipeline plug new Area { + val pmpaddr = Mem(UInt(xlen bits), regions) + val pmpcfg = Vector.fill(regions)(Reg(Bits(8 bits)) init (0)) + val base, mask = Vector.fill(regions)(Reg(UInt(xlen - cutoff bits))) + } + + def machineMode : Bool = privilegeService.isMachine() + + execute plug new Area { + import execute._ + + val fsmPending = RegInit(False) clearWhen(!arbitration.isStuck) + val fsmComplete = False + val hazardFree = csrService.isHazardFree() + + val csrAddress = input(INSTRUCTION)(csrRange) + val pmpNcfg = csrAddress(log2Up(regions) - 1 downto 0).asUInt + val pmpcfgN = pmpNcfg(log2Up(regions) - 3 downto 0) + val pmpcfgCsr = input(INSTRUCTION)(31 downto 24) === 0x3a + val pmpaddrCsr = input(INSTRUCTION)(31 downto 24) === 0x3b + + val pmpNcfg_ = Reg(UInt(log2Up(regions) bits)) + val pmpcfgN_ = Reg(UInt(log2Up(regions) - 2 bits)) + val pmpcfgCsr_ = RegInit(False) + val pmpaddrCsr_ = RegInit(False) + val writeData_ = Reg(Bits(xlen bits)) + + csrService.duringAnyRead { + when (machineMode) { + when (pmpcfgCsr) { + csrService.allowCsr() + csrService.readData() := + state.pmpcfg(pmpcfgN @@ U(3, 2 bits)) ## + state.pmpcfg(pmpcfgN @@ U(2, 2 bits)) ## + state.pmpcfg(pmpcfgN @@ U(1, 2 bits)) ## + state.pmpcfg(pmpcfgN @@ U(0, 2 bits)) + } + when (pmpaddrCsr) { + csrService.allowCsr() + csrService.readData() := state.pmpaddr(pmpNcfg).asBits + } + } + } + + csrService.duringAnyWrite { + when ((pmpcfgCsr | pmpaddrCsr) & machineMode) { + csrService.allowCsr() + arbitration.haltItself := !fsmComplete + when (!fsmPending && hazardFree) { + fsmPending := True + writeData_ := csrService.writeData() + pmpNcfg_ := pmpNcfg + pmpcfgN_ := pmpcfgN + pmpcfgCsr_ := pmpcfgCsr + pmpaddrCsr_ := pmpaddrCsr + } + } + } + + val fsm = new StateMachine { + val fsmEnable = RegInit(False) + val fsmCounter = Reg(UInt(log2Up(regions) bits)) init(0) + + val stateIdle : State = new State with EntryPoint { + onEntry { + fsmPending := False + fsmEnable := False + fsmComplete := True + fsmCounter := 0 + } + whenIsActive { + when (fsmPending) { + goto(stateWrite) + } + } + } + + val stateWrite : State = new State { + whenIsActive { + when (pmpcfgCsr_) { + val overwrite = writeData_.subdivideIn(8 bits) + for (i <- 0 until 4) { + when (~state.pmpcfg(pmpcfgN_ @@ U(i, 2 bits))(lBit)) { + state.pmpcfg(pmpcfgN_ @@ U(i, 2 bits)).assignFromBits(overwrite(i)) + } + } + goto(stateCfg) + } + when (pmpaddrCsr_) { + when (~state.pmpcfg(pmpNcfg_)(lBit)) { + state.pmpaddr(pmpNcfg_) := writeData_.asUInt + } + goto(stateAddr) + } + } + onExit (fsmEnable := True) + } + + val stateCfg : State = new State { + onEntry (fsmCounter := pmpcfgN_ @@ U(0, 2 bits)) + whenIsActive { + fsmCounter := fsmCounter + 1 + when (fsmCounter(1 downto 0) === 3) { + goto(stateIdle) + } + } + } + + val stateAddr : State = new State { + onEntry (fsmCounter := pmpNcfg_) + whenIsActive (goto(stateIdle)) + } + + when (pmpaddrCsr_) { + setter.io.addr := writeData_.asUInt + } otherwise { + setter.io.addr := state.pmpaddr(fsmCounter) + } + + when (fsmEnable & ~state.pmpcfg(fsmCounter)(lBit)) { + state.base(fsmCounter) := setter.io.base + state.mask(fsmCounter) := setter.io.mask + } + } + } + + pipeline plug new Area { + def getHits(address : UInt) = { + (0 until regions).map(i => + ((address & state.mask(U(i, log2Up(regions) bits))) === state.base(U(i, log2Up(regions) bits))) & + (state.pmpcfg(i)(lBit) | ~machineMode) & (state.pmpcfg(i)(aBits) === NAPOT) + ) + } + + def getPermission(hits : IndexedSeq[Bool], bit : Int) = { + MuxOH(OHMasking.first(hits), state.pmpcfg.map(_(bit))) + } + + val dGuard = new Area { + val address = dPort.bus.cmd(0).virtualAddress + dPort.bus.rsp.physicalAddress := address + dPort.bus.rsp.isIoAccess := ioRange(address) + dPort.bus.rsp.isPaging := False + dPort.bus.rsp.exception := False + dPort.bus.rsp.refilling := False + dPort.bus.rsp.allowExecute := False + dPort.bus.busy := False + + val hits = getHits(address(31 downto cutoff)) + + when(~hits.orR) { + dPort.bus.rsp.allowRead := machineMode + dPort.bus.rsp.allowWrite := machineMode + } otherwise { + dPort.bus.rsp.allowRead := getPermission(hits, rBit) + dPort.bus.rsp.allowWrite := getPermission(hits, wBit) + } + } + + val iGuard = new Area { + val address = iPort.bus.cmd(0).virtualAddress + iPort.bus.rsp.physicalAddress := address + iPort.bus.rsp.isIoAccess := ioRange(address) + iPort.bus.rsp.isPaging := False + iPort.bus.rsp.exception := False + iPort.bus.rsp.refilling := False + iPort.bus.rsp.allowRead := False + iPort.bus.rsp.allowWrite := False + iPort.bus.busy := False + + val hits = getHits(address(31 downto cutoff)) + + when(~hits.orR) { + iPort.bus.rsp.allowExecute := machineMode + } otherwise { + iPort.bus.rsp.allowExecute := getPermission(hits, xBit) + } + } + } + } +}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/PmpPluginOld.scala b/VexRiscv/src/main/scala/vexriscv/plugin/PmpPluginOld.scala new file mode 100644 index 0000000..0426902 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/PmpPluginOld.scala @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2020 Samuel Lindemer <samuel.lindemer@ri.se> + * + * SPDX-License-Identifier: MIT + */ + +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import spinal.core._ +import spinal.lib._ +import scala.collection.mutable.ArrayBuffer + +/* Each 32-bit pmpcfg# register contains four 8-bit configuration sections. + * These section numbers contain flags which apply to regions defined by the + * corresponding pmpaddr# register. + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pmp3cfg | pmp2cfg | pmp1cfg | pmp0cfg | pmpcfg0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | pmp7cfg | pmp6cfg | pmp5cfg | pmp4cfg | pmpcfg2 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 7 6 5 4 3 2 1 0 + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * | L | 0 | A | X | W | R | pmp#cfg + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + * L: locks configuration until system reset (including M-mode) + * 0: hardwired to zero + * A: 0 = OFF (null region / disabled) + * 1 = TOR (top of range) + * 2 = NA4 (naturally aligned four-byte region) + * 3 = NAPOT (naturally aligned power-of-two region, > 7 bytes) + * X: execute + * W: write + * R: read + * + * TOR: Each 32-bit pmpaddr# register defines the upper bound of the pmp region + * right-shifted by two bits. The lower bound of the region is the previous + * pmpaddr# register. In the case of pmpaddr0, the lower bound is address 0x0. + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | address[33:2] | pmpaddr# + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NAPOT: Each 32-bit pmpaddr# register defines the region address and the size + * of the pmp region. The number of concurrent 1s begging at the LSB indicates + * the size of the region as a power of two (e.g. 0x...0 = 8-byte, 0x...1 = + * 16-byte, 0x...11 = 32-byte, etc.). + * + * 3 2 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | address[33:2] |0|1|1|1|1| pmpaddr# + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * NA4: This is essentially an edge case of NAPOT where the entire pmpaddr# + * register defines a 4-byte wide region. + */ + +case class PmpRegister(previous : PmpRegister) extends Area { + + def OFF = 0 + def TOR = 1 + def NA4 = 2 + def NAPOT = 3 + + val state = new Area { + val r, w, x = Reg(Bool) + val l = RegInit(False) + val a = Reg(UInt(2 bits)) init(0) + val addr = Reg(UInt(32 bits)) + } + + // CSR writes connect to these signals rather than the internal state + // registers. This makes locking and WARL possible. + val csr = new Area { + val r, w, x = Bool + val l = Bool + val a = UInt(2 bits) + val addr = UInt(32 bits) + } + + // Last valid assignment wins; nothing happens if a user-initiated write did + // not occur on this clock cycle. + csr.r := state.r + csr.w := state.w + csr.x := state.x + csr.l := state.l + csr.a := state.a + csr.addr := state.addr + + // Computed PMP region bounds + val region = new Area { + val valid, locked = Bool + val start, end = UInt(32 bits) + } + + when(~state.l) { + state.r := csr.r + state.w := csr.w + state.x := csr.x + state.l := csr.l + state.a := csr.a + state.addr := csr.addr + + if (csr.l == True & csr.a == TOR) { + previous.state.l := True + } + } + + val shifted = state.addr |<< 2 + val mask = state.addr & ~(state.addr + 1) + val masked = (state.addr & ~mask) |<< 2 + + // PMP changes take effect two clock cycles after the initial CSR write (i.e., + // settings propagate from csr -> state -> region). + region.locked := state.l + region.valid := True + + switch(csr.a) { + is(TOR) { + if (previous == null) region.start := 0 + else region.start := previous.region.end + region.end := shifted + } + is(NA4) { + region.start := shifted + region.end := shifted + 4 + } + is(NAPOT) { + region.start := masked + region.end := masked + ((mask + 1) |<< 3) + } + default { + region.start := 0 + region.end := shifted + region.valid := False + } + } +} + + +class PmpPluginOld(regions : Int, ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator { + + // Each pmpcfg# CSR configures four regions. + assert((regions % 4) == 0) + + val pmps = ArrayBuffer[PmpRegister]() + val portsInfo = ArrayBuffer[ProtectedMemoryTranslatorPort]() + + override def newTranslationPort(priority : Int, args : Any): MemoryTranslatorBus = { + val port = ProtectedMemoryTranslatorPort(MemoryTranslatorBus(new MemoryTranslatorBusParameter(0, 0))) + portsInfo += port + port.bus + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline.config._ + import pipeline._ + import Riscv._ + + val csrService = pipeline.service(classOf[CsrInterface]) + val privilegeService = pipeline.service(classOf[PrivilegeService]) + + val core = pipeline plug new Area { + + // Instantiate pmpaddr0 ... pmpaddr# CSRs. + for (i <- 0 until regions) { + if (i == 0) { + pmps += PmpRegister(null) + } else { + pmps += PmpRegister(pmps.last) + } + csrService.r(0x3b0 + i, pmps(i).state.addr) + csrService.w(0x3b0 + i, pmps(i).csr.addr) + } + + // Instantiate pmpcfg0 ... pmpcfg# CSRs. + for (i <- 0 until (regions / 4)) { + csrService.r(0x3a0 + i, + 31 -> pmps((i * 4) + 3).state.l, 23 -> pmps((i * 4) + 2).state.l, + 15 -> pmps((i * 4) + 1).state.l, 7 -> pmps((i * 4) ).state.l, + 27 -> pmps((i * 4) + 3).state.a, 26 -> pmps((i * 4) + 3).state.x, + 25 -> pmps((i * 4) + 3).state.w, 24 -> pmps((i * 4) + 3).state.r, + 19 -> pmps((i * 4) + 2).state.a, 18 -> pmps((i * 4) + 2).state.x, + 17 -> pmps((i * 4) + 2).state.w, 16 -> pmps((i * 4) + 2).state.r, + 11 -> pmps((i * 4) + 1).state.a, 10 -> pmps((i * 4) + 1).state.x, + 9 -> pmps((i * 4) + 1).state.w, 8 -> pmps((i * 4) + 1).state.r, + 3 -> pmps((i * 4) ).state.a, 2 -> pmps((i * 4) ).state.x, + 1 -> pmps((i * 4) ).state.w, 0 -> pmps((i * 4) ).state.r + ) + csrService.w(0x3a0 + i, + 31 -> pmps((i * 4) + 3).csr.l, 23 -> pmps((i * 4) + 2).csr.l, + 15 -> pmps((i * 4) + 1).csr.l, 7 -> pmps((i * 4) ).csr.l, + 27 -> pmps((i * 4) + 3).csr.a, 26 -> pmps((i * 4) + 3).csr.x, + 25 -> pmps((i * 4) + 3).csr.w, 24 -> pmps((i * 4) + 3).csr.r, + 19 -> pmps((i * 4) + 2).csr.a, 18 -> pmps((i * 4) + 2).csr.x, + 17 -> pmps((i * 4) + 2).csr.w, 16 -> pmps((i * 4) + 2).csr.r, + 11 -> pmps((i * 4) + 1).csr.a, 10 -> pmps((i * 4) + 1).csr.x, + 9 -> pmps((i * 4) + 1).csr.w, 8 -> pmps((i * 4) + 1).csr.r, + 3 -> pmps((i * 4) ).csr.a, 2 -> pmps((i * 4) ).csr.x, + 1 -> pmps((i * 4) ).csr.w, 0 -> pmps((i * 4) ).csr.r + ) + } + + // Connect memory ports to PMP logic. + val ports = for ((port, portId) <- portsInfo.zipWithIndex) yield new Area { + + val address = port.bus.cmd(0).virtualAddress + port.bus.rsp.physicalAddress := address + + // Only the first matching PMP region applies. + val hits = pmps.map(pmp => pmp.region.valid & + pmp.region.start <= address & + pmp.region.end > address & + (pmp.region.locked | ~privilegeService.isMachine())) + + // M-mode has full access by default, others have none. + when(CountOne(hits) === 0) { + port.bus.rsp.allowRead := privilegeService.isMachine() + port.bus.rsp.allowWrite := privilegeService.isMachine() + port.bus.rsp.allowExecute := privilegeService.isMachine() + } otherwise { + port.bus.rsp.allowRead := MuxOH(OHMasking.first(hits), pmps.map(_.state.r)) + port.bus.rsp.allowWrite := MuxOH(OHMasking.first(hits), pmps.map(_.state.w)) + port.bus.rsp.allowExecute := MuxOH(OHMasking.first(hits), pmps.map(_.state.x)) + } + + port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress) + port.bus.rsp.isPaging := False + port.bus.rsp.exception := False + port.bus.rsp.refilling := False + port.bus.busy := False + + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/RegFilePlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/RegFilePlugin.scala new file mode 100644 index 0000000..94a3f32 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/RegFilePlugin.scala @@ -0,0 +1,122 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable + + +trait RegFileReadKind +object ASYNC extends RegFileReadKind +object SYNC extends RegFileReadKind + + +class RegFilePlugin(regFileReadyKind : RegFileReadKind, + zeroBoot : Boolean = false, + x0Init : Boolean = true, + writeRfInMemoryStage : Boolean = false, + readInExecute : Boolean = false, + syncUpdateOnStall : Boolean = true, + rv32e : Boolean = false, + withShadow : Boolean = false //shadow registers aren't transition hazard free + ) extends Plugin[VexRiscv] with RegFileService{ + import Riscv._ + + override def readStage(): Stage = if(readInExecute) pipeline.execute else pipeline.decode + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(RS1_USE,False) + decoderService.addDefault(RS2_USE,False) + decoderService.addDefault(REGFILE_WRITE_VALID,False) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val readStage = if(readInExecute) execute else decode + val writeStage = if(writeRfInMemoryStage) memory else stages.last + + val numRegisters = if(rv32e) 16 else 32 + def clipRange(that : Range) = if(rv32e) that.tail else that + + val global = pipeline plug new Area{ + val regFileSize = if(withShadow) numRegisters * 2 else numRegisters + val regFile = Mem(Bits(32 bits),regFileSize) addAttribute(Verilator.public) + if(zeroBoot) regFile.init(List.fill(regFileSize)(B(0, 32 bits))) + + val shadow = ifGen(withShadow)(new Area{ + val write, read, clear = RegInit(False) + + read clearWhen(clear && !readStage.arbitration.isStuck) + write clearWhen(clear && !writeStage.arbitration.isStuck) + + val csrService = pipeline.service(classOf[CsrInterface]) + csrService.w(0x7C0,2 -> clear, 1 -> read, 0 -> write) + }) + } + + //Disable rd0 write in decoding stage + when(decode.input(INSTRUCTION)(rdRange) === 0) { + decode.input(REGFILE_WRITE_VALID) := False + } + if(rv32e) when(decode.input(INSTRUCTION)(rdRange.head)) { + decode.input(REGFILE_WRITE_VALID) := False + } + + //Read register file + readStage plug new Area{ + import readStage._ + + //read register file + val srcInstruction = regFileReadyKind match{ + case `ASYNC` => input(INSTRUCTION) + case `SYNC` if !readInExecute => input(INSTRUCTION_ANTICIPATED) + case `SYNC` if readInExecute => if(syncUpdateOnStall) Mux(execute.arbitration.isStuck, execute.input(INSTRUCTION), decode.input(INSTRUCTION)) else decode.input(INSTRUCTION) + } + + def shadowPrefix(that : Bits) = if(withShadow) global.shadow.read ## that else that + val regFileReadAddress1 = U(shadowPrefix(srcInstruction(clipRange(Riscv.rs1Range)))) + val regFileReadAddress2 = U(shadowPrefix(srcInstruction(clipRange(Riscv.rs2Range)))) + + val (rs1Data,rs2Data) = regFileReadyKind match{ + case `ASYNC` => (global.regFile.readAsync(regFileReadAddress1),global.regFile.readAsync(regFileReadAddress2)) + case `SYNC` => + val enable = if(!syncUpdateOnStall) !readStage.arbitration.isStuck else null + (global.regFile.readSync(regFileReadAddress1, enable),global.regFile.readSync(regFileReadAddress2, enable)) + } + + insert(RS1) := rs1Data + insert(RS2) := rs2Data + } + + //Write register file + writeStage plug new Area { + import writeStage._ + + def shadowPrefix(that : Bits) = if(withShadow) global.shadow.write ## that else that + val regFileWrite = global.regFile.writePort.addAttribute(Verilator.public).setName("lastStageRegFileWrite") + regFileWrite.valid := output(REGFILE_WRITE_VALID) && arbitration.isFiring + regFileWrite.address := U(shadowPrefix(output(INSTRUCTION)(clipRange(rdRange)))) + regFileWrite.data := output(REGFILE_WRITE_DATA) + + //Ensure no boot glitches modify X0 + if(!x0Init && zeroBoot) when(regFileWrite.address === 0){ + regFileWrite.valid := False + } + + //CPU will initialise constant register zero in the first cycle + if(x0Init) { + val boot = RegNext(False) init (True) + regFileWrite.valid setWhen (boot) + when(boot) { + regFileWrite.address := 0 + regFileWrite.data := 0 + } + } + } + } +}
\ No newline at end of file diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/ShiftPlugins.scala b/VexRiscv/src/main/scala/vexriscv/plugin/ShiftPlugins.scala new file mode 100644 index 0000000..a4ae716 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/ShiftPlugins.scala @@ -0,0 +1,193 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib.Reverse + + + +class FullBarrelShifterPlugin(earlyInjection : Boolean = false) extends Plugin[VexRiscv]{ + object ShiftCtrlEnum extends SpinalEnum(binarySequential){ + val DISABLE, SLL, SRL, SRA = newElement() + } + + object SHIFT_CTRL extends Stageable(ShiftCtrlEnum()) + object SHIFT_RIGHT extends Stageable(Bits(32 bits)) + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + + + val immediateActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.IMI, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> Bool(earlyInjection), + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True + ) + + val nonImmediateActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> Bool(earlyInjection), + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True, + RS2_USE -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(SHIFT_CTRL, ShiftCtrlEnum.DISABLE) + decoderService.add(List( + SLL -> (nonImmediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SLL)), + SRL -> (nonImmediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRL)), + SRA -> (nonImmediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRA)) + )) + + decoderService.add(List( + SLLI -> (immediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SLL)), + SRLI -> (immediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRL)), + SRAI -> (immediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRA)) + )) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + + execute plug new Area{ + import execute._ + val amplitude = input(SRC2)(4 downto 0).asUInt + val reversed = Mux(input(SHIFT_CTRL) === ShiftCtrlEnum.SLL, Reverse(input(SRC1)), input(SRC1)) + insert(SHIFT_RIGHT) := (Cat(input(SHIFT_CTRL) === ShiftCtrlEnum.SRA & reversed.msb, reversed).asSInt >> amplitude)(31 downto 0).asBits + } + + val injectionStage = if(earlyInjection) execute else memory + injectionStage plug new Area{ + import injectionStage._ + when(arbitration.isValid){ + switch(input(SHIFT_CTRL)) { + is(ShiftCtrlEnum.SLL) { + output(REGFILE_WRITE_DATA) := Reverse(input(SHIFT_RIGHT)) + } + is(ShiftCtrlEnum.SRL, ShiftCtrlEnum.SRA) { + output(REGFILE_WRITE_DATA) := input(SHIFT_RIGHT) + } + } + } + } + } +} + + + + + + + + + + +class LightShifterPlugin extends Plugin[VexRiscv]{ + object ShiftCtrlEnum extends SpinalEnum(binarySequential){ + val DISABLE, SLL, SRL, SRA = newElement() + } + + object SHIFT_CTRL extends Stageable(ShiftCtrlEnum()) + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + import IntAluPlugin._ + + val immediateActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.IMI, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> True, + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True, + + //Get SRC1 through the MMU to the RF write path + ALU_CTRL -> AluCtrlEnum.ADD_SUB, + SRC_USE_SUB_LESS -> False, + SRC_ADD_ZERO -> True + ) + + val nonImmediateActions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> True, + BYPASSABLE_MEMORY_STAGE -> True, + RS1_USE -> True, + RS2_USE -> True, + + //Get SRC1 through the MMU to the RF write path + ALU_CTRL -> AluCtrlEnum.ADD_SUB, + SRC_USE_SUB_LESS -> False, + SRC_ADD_ZERO -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(SHIFT_CTRL, ShiftCtrlEnum.DISABLE) + decoderService.add(List( + SLL -> (nonImmediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SLL)), + SRL -> (nonImmediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRL)), + SRA -> (nonImmediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRA)) + )) + + decoderService.add(List( + SLLI -> (immediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SLL)), + SRLI -> (immediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRL)), + SRAI -> (immediateActions ++ List(SHIFT_CTRL -> ShiftCtrlEnum.SRA)) + )) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + + execute plug new Area{ + import execute._ + + val isActive = RegInit(False) + val isShift = input(SHIFT_CTRL) =/= ShiftCtrlEnum.DISABLE + val amplitudeReg = Reg(UInt(5 bits)) + val amplitude = isActive ? amplitudeReg | input(SRC2)(4 downto 0).asUInt + val shiftReg = ifGen(!withMemoryStage) (RegNextWhen(execute.output(REGFILE_WRITE_DATA), !arbitration.isStuckByOthers)) + val shiftInput = isActive ? (if(withMemoryStage) memory.input(REGFILE_WRITE_DATA) else shiftReg) | input(SRC1) + val done = amplitude(4 downto 1) === 0 + + if(withMemoryStage) memory.dontSampleStageable(REGFILE_WRITE_DATA, arbitration.isStuckByOthers) + + when(arbitration.isValid && isShift && input(SRC2)(4 downto 0) =/= 0){ + output(REGFILE_WRITE_DATA) := input(SHIFT_CTRL).mux( + ShiftCtrlEnum.SLL -> (shiftInput |<< 1), + default -> (((input(SHIFT_CTRL) === ShiftCtrlEnum.SRA && shiftInput.msb) ## shiftInput).asSInt >> 1).asBits //ALU.SRL,ALU.SRA + ) + + when(!arbitration.isStuckByOthers){ + isActive := True + amplitudeReg := amplitude - 1 + + when(done){ + isActive := False + } + } + + when(!done){ + arbitration.haltItself := True + } + } + when(arbitration.removeIt){ + isActive := False + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/SingleInstructionLimiterPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/SingleInstructionLimiterPlugin.scala new file mode 100644 index 0000000..c6c9706 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/SingleInstructionLimiterPlugin.scala @@ -0,0 +1,17 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib._ + + +class SingleInstructionLimiterPlugin() extends Plugin[VexRiscv] { + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + val fetcher = pipeline.service(classOf[IBusFetcher]) + when(fetcher.incoming() || List(decode,execute,memory,writeBack).map(_.arbitration.isValid).orR) { + fetcher.haltIt() + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/SrcPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/SrcPlugin.scala new file mode 100644 index 0000000..d67e7cc --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/SrcPlugin.scala @@ -0,0 +1,83 @@ +package vexriscv.plugin + +import vexriscv._ +import spinal.core._ +import spinal.lib.KeepAttribute + + +class SrcPlugin(separatedAddSub : Boolean = false, executeInsertion : Boolean = false, decodeAddSub : Boolean = false) extends Plugin[VexRiscv]{ + object SRC2_FORCE_ZERO extends Stageable(Bool) + + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline.config._ + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(SRC_ADD_ZERO, False) //TODO avoid this default to simplify decoding ? + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + decode.insert(SRC2_FORCE_ZERO) := decode.input(SRC_ADD_ZERO) && !decode.input(SRC_USE_SUB_LESS) + + val insertionStage = if(executeInsertion) execute else decode + insertionStage plug new Area{ + import insertionStage._ + + val imm = Riscv.IMM(input(INSTRUCTION)) + insert(SRC1) := input(SRC1_CTRL).mux( + Src1CtrlEnum.RS -> output(RS1), + Src1CtrlEnum.PC_INCREMENT -> (if(pipeline.config.withRvc) Mux(input(IS_RVC), B(2), B(4)) else B(4)).resized, + Src1CtrlEnum.IMU -> imm.u.resized, + Src1CtrlEnum.URS1 -> input(INSTRUCTION)(Riscv.rs1Range).resized + ) + insert(SRC2) := input(SRC2_CTRL).mux( + Src2CtrlEnum.RS -> output(RS2), + Src2CtrlEnum.IMI -> imm.i_sext.resized, + Src2CtrlEnum.IMS -> imm.s_sext.resized, + Src2CtrlEnum.PC -> output(PC).asBits + ) + } + + val addSubStage = if(decodeAddSub) decode else execute + if(separatedAddSub) { + addSubStage plug new Area { + import addSubStage._ + + // ADD, SUB + val add = (U(input(SRC1)) + U(input(SRC2))).asBits.addAttribute("keep") + val sub = (U(input(SRC1)) - U(input(SRC2))).asBits.addAttribute("keep") + when(input(SRC_ADD_ZERO)){ add := input(SRC1) } + + // SLT, SLTU + val less = Mux(input(SRC1).msb === input(SRC2).msb, sub.msb, + Mux(input(SRC_LESS_UNSIGNED), input(SRC2).msb, input(SRC1).msb)) + + insert(SRC_ADD_SUB) := input(SRC_USE_SUB_LESS) ? sub | add + insert(SRC_ADD) := add + insert(SRC_SUB) := sub + insert(SRC_LESS) := less + } + }else{ + addSubStage plug new Area { + import addSubStage._ + + // ADD, SUB + val addSub = (input(SRC1).asSInt + Mux(input(SRC_USE_SUB_LESS), ~input(SRC2), input(SRC2)).asSInt + Mux(input(SRC_USE_SUB_LESS), S(1, 32 bits), S(0, 32 bits))).asBits + when(input(SRC2_FORCE_ZERO)){ addSub := input(SRC1) } + + + // SLT, SLTU + val less = Mux(input(SRC1).msb === input(SRC2).msb, addSub.msb, + Mux(input(SRC_LESS_UNSIGNED), input(SRC2).msb, input(SRC1).msb)) + + insert(SRC_ADD_SUB) := addSub + insert(SRC_ADD) := addSub + insert(SRC_SUB) := addSub + insert(SRC_LESS) := less + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/StaticMemoryTranslatorPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/StaticMemoryTranslatorPlugin.scala new file mode 100644 index 0000000..cafd8de --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/StaticMemoryTranslatorPlugin.scala @@ -0,0 +1,41 @@ +package vexriscv.plugin + +import vexriscv.{VexRiscv, _} +import spinal.core._ +import spinal.lib._ + +import scala.collection.mutable.ArrayBuffer +case class StaticMemoryTranslatorPort(bus : MemoryTranslatorBus, priority : Int) + +class StaticMemoryTranslatorPlugin(ioRange : UInt => Bool) extends Plugin[VexRiscv] with MemoryTranslator { + val portsInfo = ArrayBuffer[StaticMemoryTranslatorPort]() + + override def newTranslationPort(priority : Int,args : Any): MemoryTranslatorBus = { + val port = StaticMemoryTranslatorPort(MemoryTranslatorBus(MemoryTranslatorBusParameter(wayCount = 0)),priority) + portsInfo += port + port.bus + } + + override def setup(pipeline: VexRiscv): Unit = { + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + import Riscv._ + + val core = pipeline plug new Area { + val ports = for ((port, portId) <- portsInfo.zipWithIndex) yield new Area { + port.bus.rsp.physicalAddress := port.bus.cmd.last.virtualAddress + port.bus.rsp.allowRead := True + port.bus.rsp.allowWrite := True + port.bus.rsp.allowExecute := True + port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress) + port.bus.rsp.isPaging := False + port.bus.rsp.exception := False + port.bus.rsp.refilling := False + port.bus.busy := False + } + } + } +} diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/VfuPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/VfuPlugin.scala new file mode 100644 index 0000000..a2c0930 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/VfuPlugin.scala @@ -0,0 +1,136 @@ +package vexriscv.plugin + +import vexriscv.{DecoderService, ExceptionCause, ExceptionService, Stage, Stageable, VexRiscv} +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.bmb.WeakConnector +import spinal.lib.bus.misc.{AddressMapping, DefaultMapping} +import vexriscv.Riscv.IMM + + +object VfuPlugin{ + val ROUND_MODE_WIDTH = 3 + +} + + +case class VfuParameter() //Empty for now + +case class VfuCmd( p : VfuParameter ) extends Bundle{ + val instruction = Bits(32 bits) + val inputs = Vec(Bits(32 bits), 2) + val rounding = Bits(VfuPlugin.ROUND_MODE_WIDTH bits) +} + +case class VfuRsp(p : VfuParameter) extends Bundle{ + val output = Bits(32 bits) +} + +case class VfuBus(p : VfuParameter) extends Bundle with IMasterSlave{ + val cmd = Stream(VfuCmd(p)) + val rsp = Stream(VfuRsp(p)) + + def <<(m : VfuBus) : Unit = { + val s = this + s.cmd << m.cmd + m.rsp << s.rsp + } + + override def asMaster(): Unit = { + master(cmd) + slave(rsp) + } +} + + + +class VfuPlugin(val stageCount : Int, + val allowZeroLatency : Boolean, + val parameter : VfuParameter) extends Plugin[VexRiscv]{ + def p = parameter + + var bus : VfuBus = null + + lazy val forkStage = pipeline.execute + lazy val joinStage = pipeline.stages(Math.min(pipeline.stages.length - 1, pipeline.indexOf(forkStage) + stageCount)) + + + object VFU_ENABLE extends Stageable(Bool()) + object VFU_IN_FLIGHT extends Stageable(Bool()) + + override def setup(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + bus = master(VfuBus(p)) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(VFU_ENABLE, False) + + decoderService.add( + key = M"-------------------------0001011", + values = List( + VFU_ENABLE -> True, + REGFILE_WRITE_VALID -> True, //If you want to write something back into the integer register file + BYPASSABLE_EXECUTE_STAGE -> Bool(stageCount == 0), + BYPASSABLE_MEMORY_STAGE -> Bool(stageCount <= 1), + RS1_USE -> True, + RS2_USE -> True + ) + ) + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + val csr = pipeline plug new Area{ + val factory = pipeline.service(classOf[CsrInterface]) + val rounding = Reg(Bits(VfuPlugin.ROUND_MODE_WIDTH bits)) + + factory.rw(csrAddress = 0xBC0, bitOffset = 0, that = rounding) + } + + + forkStage plug new Area{ + import forkStage._ + val hazard = stages.dropWhile(_ != forkStage).tail.map(s => s.arbitration.isValid && s.input(HAS_SIDE_EFFECT)).orR + val scheduleWish = arbitration.isValid && input(VFU_ENABLE) + val schedule = scheduleWish && !hazard + arbitration.haltItself setWhen(scheduleWish && hazard) + + val hold = RegInit(False) setWhen(schedule) clearWhen(bus.cmd.ready) + val fired = RegInit(False) setWhen(bus.cmd.fire) clearWhen(!arbitration.isStuck) + insert(VFU_IN_FLIGHT) := schedule || hold || fired + + bus.cmd.valid := (schedule || hold) && !fired + arbitration.haltItself setWhen(bus.cmd.valid && !bus.cmd.ready) + + bus.cmd.instruction := input(INSTRUCTION) + bus.cmd.inputs(0) := input(RS1) + bus.cmd.inputs(1) := input(RS2) + bus.cmd.rounding := csr.rounding + } + + joinStage plug new Area{ + import joinStage._ + + val rsp = if(forkStage != joinStage && allowZeroLatency) { + bus.rsp.s2mPipe() + } else { + bus.rsp.combStage() + } + + rsp.ready := False + when(input(VFU_IN_FLIGHT) && input(REGFILE_WRITE_VALID)){ + arbitration.haltItself setWhen(!bus.rsp.valid) + rsp.ready := !arbitration.isStuckByOthers + output(REGFILE_WRITE_DATA) := bus.rsp.output + } + } + + pipeline.stages.drop(1).foreach(s => s.output(VFU_IN_FLIGHT) clearWhen(s.arbitration.isStuck)) + addPrePopTask(() => stages.dropWhile(_ != memory).reverse.dropWhile(_ != joinStage).foreach(s => s.input(VFU_IN_FLIGHT).init(False))) + } +} + diff --git a/VexRiscv/src/main/scala/vexriscv/plugin/YamlPlugin.scala b/VexRiscv/src/main/scala/vexriscv/plugin/YamlPlugin.scala new file mode 100644 index 0000000..ca53e42 --- /dev/null +++ b/VexRiscv/src/main/scala/vexriscv/plugin/YamlPlugin.scala @@ -0,0 +1,32 @@ +package vexriscv.plugin + +import java.util + +import vexriscv.{ReportService, VexRiscv} +import org.yaml.snakeyaml.{DumperOptions, Yaml} + + +/** + * Created by spinalvm on 09.06.17. + */ +class YamlPlugin(path : String) extends Plugin[VexRiscv] with ReportService{ + + val content = new util.HashMap[String, Object]() + + def add(that : (String,Object)) : Unit = content.put(that._1,that._2) + + override def setup(pipeline: VexRiscv): Unit = { + + } + + override def build(pipeline: VexRiscv): Unit = { + val options = new DumperOptions() + options.setWidth(50) + options.setIndent(4) + options.setCanonical(true) + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK) + + val yaml = new Yaml() + yaml.dump(content, new java.io.FileWriter(path)) + } +} |