-- yasep/VHDL/microYASEP.vhdl -- créé le 19 février 2012 par Yann Guidon / whygee@f-cpu.org -- -- This file belongs to the YASEP project and is distributed under the -- AGPLv3 (or later) license. See http://yasep.org/license/agpl.txt -- -- version jeu. févr. 23 03:01:31 CET 2012 -- version mar. mars 13 12:22:20 CET 2012 : ajout du flag INV -- version jeudi 15 mars 2012, 01:02:24 : found that damned Field_SI4/condition bug !!! -- version dim. mars 18 16:39:34 CET 2012 : EQ OK, manquent MIN/MAX -- version mar. mars 20 17:10:09 CET 2012 : reboot, now can execute slower -- version ven. août 10 13:16:43 CEST 2012 : FlagChangeZero added => renamed as FlagChangeEqual -- version mer. nov. 21 22:30:42 CET 2012 : MOVH, data memory, all registers externally visible -- version dim. août 4 11:34:20 CEST 2013 : inhibit SI4 sign extension if YASEP32 and group_shl and Imm4 + support Imm20 flags now -- version lun. août 5 03:55:49 CEST 2013 : adding Imm4 adjustment when writing to PC in extended mode, some cleanup as well -- version mer. févr. 12 02:34:48 CET 2014 : changed Zero to Equal Library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library work; use work.yasep_definitions.all; use work.yasep_utils.all; entity microYASEP is port( -- predivider ExecPrediv: in SLV4 := "0000"; -- increase to slow down the core -- invalid opcode flag : INV : inout std_logic; -- sticky flag of invalid instruction -- Instruction memory : ProgRdEn: inout std_logic; -- ='0' when read is active RAM_out: in SLV16; -- read port of the instruction memory PC, -- address of the currently executed instruction NPC: inout SLVAI; -- nouvelle valeur du compteur d'instructions /!\ LSB=0 -- so we can spy on the core : Condition2, condition, WR_en, WB_en, phase, Carry, FlagEqual : inout std_logic; R1, R2, R3, R4, R5, A1, A2, A3, A4, A5, Imm4, Result: inout SLVY; -- data to write back Imm16, SI4sll16: inout SLV32; -- temporary results, MSB are dropped in YASEP16 D1, D2, D3, D4, D5: in SLVY; -- data coming from memory. -- can be emulated as a simple register by external code. DST, Field_cond, Field_SI4, Field_SND : inout SLV4; -- GET, PUT : GetReadPort: in SLVY; PutWritePort, SRaddress: out SLVY; GET_en, PUT_en: out std_logic := '0'; clk, reset : in std_logic); -- reset must be resynchronised end microYASEP; architecture reboot of microYASEP is signal ROP2_X2, Addsub, Carry_out, compare_signed, equal_out, CondZero : std_logic := '0'; signal opcode: SLV6 := "000000"; signal cond_func, form: SLV2 := "00"; signal PC1 : SLVAI := (SLVAI'range=>'0'); -- speculatively incremented PC signal CPU_prediv : SLV4; -- counter for the wait states signal SI4, RSI4_out, Reg_SI4, SND, RSND_out, -- registers and operands ActualA, ActualB, ROP2_RS1, ROP2_RS2, ROP2_xor, -- temporary results ASU, ROP2 : -- outputs of execution units SLVY := (SLVY'range=>'0'); signal sumAux : unsigned(YASEP_SIZE+1 downto 0); signal int_opcode: integer := 0; function opcode_defined(o:SLV6; p:SLV6; i:integer) return boolean is begin if o=p and FlagValidInstruction(i)='1' then return true; end if; return false; end; begin ProgRdEn <= '0' when CPU_prediv="0000" and (RAM_out(0)='1' or phase='1') else '1'; -- read the registers RSND_out <= SLVAI_to_SLVY(PC) when RAM_out(11 downto 8)=REG_PC else R1 when RAM_out(11 downto 8)=REG_R1 else R2 when RAM_out(11 downto 8)=REG_R2 else R3 when RAM_out(11 downto 8)=REG_R3 else R4 when RAM_out(11 downto 8)=REG_R4 else R5 when RAM_out(11 downto 8)=REG_R5 else D1 when RAM_out(11 downto 8)=REG_D1 else A1 when RAM_out(11 downto 8)=REG_A1 else D2 when RAM_out(11 downto 8)=REG_D2 else A2 when RAM_out(11 downto 8)=REG_A2 else D3 when RAM_out(11 downto 8)=REG_D3 else A3 when RAM_out(11 downto 8)=REG_A3 else D4 when RAM_out(11 downto 8)=REG_D4 else A4 when RAM_out(11 downto 8)=REG_A4 else D5 when RAM_out(11 downto 8)=REG_D5 else A5;--when RAM_out(11 downto 8)=REG_A5 RSI4_out <= SLVAI_to_SLVY(PC) when RAM_out(15 downto 12)=REG_PC else R1 when RAM_out(15 downto 12)=REG_R1 else R2 when RAM_out(15 downto 12)=REG_R2 else R3 when RAM_out(15 downto 12)=REG_R3 else R4 when RAM_out(15 downto 12)=REG_R4 else R5 when RAM_out(15 downto 12)=REG_R5 else D1 when RAM_out(15 downto 12)=REG_D1 else A1 when RAM_out(15 downto 12)=REG_A1 else D2 when RAM_out(15 downto 12)=REG_D2 else A2 when RAM_out(15 downto 12)=REG_A2 else D3 when RAM_out(15 downto 12)=REG_D3 else A3 when RAM_out(15 downto 12)=REG_A3 else D4 when RAM_out(15 downto 12)=REG_D4 else A4 when RAM_out(15 downto 12)=REG_A4 else D5 when RAM_out(15 downto 12)=REG_D5 else A5;--when RAM_out(15 downto 12)=REG_A5 -- Control path : control the write back -- the instruction can write but the opcode is not checked. WB_en <= '1' when FlagValidInstruction(int_opcode)='1' and condition='1' and phase='1' and CPU_prediv="0000" else '0'; -- enable the write back of registers WR_en <= '1' when WB_en='1' and FlagNoWriteBack(int_opcode)='0' else '0'; condition <= FlagNoCondition(int_opcode) -- is this instruction abortable ? or (RAM_out(5) xor Condition2) -- condition (computed below) when form = FORM_EXT -- extended / conditional instruction else '1'; -- true otherwise Condition2 <= R1(safe_to_integer(Field_cond)) when cond_func=COND_BIT and Enable_BIT0=1 else '1' when cond_func=COND_ZERO and Field_cond="0000" -- always else FlagEqual when cond_func=COND_MSB and Field_cond="0000" -- EQ else Carry when cond_func=COND_LSB and Field_cond="0000" -- carry else CondZero when cond_func=COND_ZERO -- add an enable flag ? else RSI4_out(0) when cond_func=COND_LSB else RSI4_out(YASEP_SIZE-1); -- when cond_func=COND_MSB Field_cond <= RAM_out(15 downto 12); -- not to be mistaken with Field_SI4 ! (different phase) -- when phase='1' and long instruction ? -- latch cond_func <= RAM_out(7 downto 6); -- latch ? CondZero <= '1' when RSI4_out=(RSI4_out'range=>'0') else '0'; -- Address path : --to be latched DST <= Field_SND when form(0) = '0' -- Short else Field_SI4 when form(1) = '0' -- Long else RAM_out(R_SND'range); -- Extended (matches SND but it's the 2nd halfword) -- create the address of the next instruction (on both phases) PC1 <= std_logic_vector(unsigned(PC)+1); NPC <= Result(NPC'range) when WR_en='1' and DST=REG_PC else SI4(NPC'range) when WB_en='1' and opcode_defined(Op_CALL,opcode,int_opcode) -- should be simplified and routed through result? No because result is == PC1... -- else ASU(NPC'range) when WB_en='1' and opcode_defined(Op_CALL2,opcode,int_opcode) else PC1; -- Datapath : select the data to write to a register Result <= ASU when opcode(2 downto 0)=GROUP_ASU else ROP2 when opcode(2 downto 0)=GROUP_ROP2 else SLVAI_to_SLVY(PC1) when opcode_defined(Op_CALL,opcode,int_opcode) or opcode_defined(Op_CALL2,opcode,int_opcode) else GetReadPort when opcode_defined(Op_GET,opcode,int_opcode) else SI4sll16(SLVY'range) when opcode_defined(Op_MOVH,opcode,int_opcode) else SI4; -- the remaining instructions, such as MOV -- a few 16/32 bits issues. Passing through a (forced) 32 bits temporary signal. SI4sll16 <= SI4(15 downto 0) & SND(15 downto 0); -- MSB are dropped in YASEP16 Imm16 <= (11 downto 0 => Field_SND(3)) & Field_SND & RAM_out when FlagImm20(int_opcode)='1' else (31 downto 16 =>RAM_out(15)) & RAM_out; -- normal sign-extension. -- Datapath : Select SI4 ; latch ? InhibitImm4SignExt <= '0' when YASEP_SIZE=32 and FlagIMM16_5LSB(int_opcode)='1' -- opcode(2 downto 0)=GROUP_SHL might work too but you're never too safe else Field_SI4(3); Imm4 <= (Field_SI4(3) or not Field_SI4(2)) & Field_SI4(2 downto 0) & 0 -- range extension when RAM_out(R_SND'range)=REG_PC and form=FORM_EXT and RAM_out(1)='1' else InhibitImm4SignExt & Field_SI4; -- normal imm4, for SHL too SI4 <= Reg_SI4(SLVY'range) -- the register must come first because it's the longest latency when (form = FORM_RR) -- short reg or (form = FORM_EXT and RAM_out(1)='0') -- extended reg else Imm16 when form = FORM_RRI16 -- long immediate else (YASEP_SIZE-6 downto 0 => Field_SI4(3)) & Imm4; -- short Imm4 and extended Imm4 (with adjusted sign extension) -- SR access : SRaddress <= SI4; PutWritePort <= SND; PUT_en <= '1' when opcode_defined(Op_PUT,opcode,int_opcode) and phase='1' and CPU_prediv="0000" else '0'; GET_en <= '1' when opcode_defined(Op_GET,opcode,int_opcode) and phase='1' else '0'; ----------------- ALU/ROP2 ----------------- -- ROP2_S1 ROP2_S2 ROP2_X2 Addsub compare_signed -- ADD X X X 0 0 -- SUB X X X 1 0 -- compare (== SUB, only equal_out and Carry_out used) -- signed X X X 1 1 -- unsigned X X X 1 0 -- AND 1 0 0 0 0 -- ANDN 1 0 0 1 0 -- NAND 1 0 1 0 0 -- OR X 1 0 0 0 -- ORN X 1 0 1 0 -- NOR X 1 1 0 0 -- XOR 0 0 0 0 0 -- XORN 0 0 0 1 0 -- TODO : make a proper table. ROP2_X2 <= '1' when opcode_defined(Op_NOR ,opcode,int_opcode) or opcode_defined(Op_NAND,opcode,int_opcode) else '0'; Addsub <= '1' when opcode_defined(Op_SUB ,opcode,int_opcode) or opcode_defined(Op_CMPS,opcode,int_opcode) or opcode_defined(Op_CMPU,opcode,int_opcode) or opcode_defined(Op_ANDN,opcode,int_opcode) or opcode_defined(Op_ORN ,opcode,int_opcode) or opcode_defined(Op_XORN,opcode,int_opcode) else '0'; compare_signed <='1' when opcode_defined(Op_CMPS,opcode,int_opcode) or opcode_defined(Op_SMAX,opcode,int_opcode) else '0'; -- XOR préliminaire et ajustement du signe : ActualB <= ( SND(YASEP_SIZE-1) xor (Addsub and (not compare_signed)) ) & ( SND(YASEP_SIZE-2 downto 0) xor (YASEP_SIZE-2 downto 0 =>Addsub)); -- XOR the MSB here too for the signed comparisons : ActualA <= ( SI4(YASEP_SIZE-1) xor compare_signed ) & SI4(YASEP_SIZE-2 downto 0); -- Add/Sub sumAux <= unsigned('0' & ActualA & '1') + unsigned('0' & ActualB & Addsub); Carry_out <= std_logic(sumAux(YASEP_SIZE+1)); ASU <= std_logic_vector(sumAux(YASEP_SIZE downto 1)); -- ROP2 : -- ROP2_xor <= ActualA xor ActualB; -- XOR toujours implémenté pour CLEAR ? equal_out <= '1' when ROP2_xor=(ROP2_xor'range=>'1') -- AND reduce else '0'; -- NOTE: during CMP, the actual operation is XORN -- because addsub=1 => ActualB=~SND => so we compare with '1' instead of '0' -- First MUX ROP2_RS1 <= ActualA and ActualB when opcode_defined(Op_AND,opcode,int_opcode) or opcode_defined(Op_ANDN,opcode,int_opcode) or opcode_defined(Op_NAND,opcode,int_opcode) else ROP2_xor; ROP2_RS2 <= ActualA or ActualB when opcode_defined(Op_OR,opcode,int_opcode) or opcode_defined(Op_ORN,opcode,int_opcode) or opcode_defined(Op_NOR,opcode,int_opcode) else ROP2_RS1; -- last XOR ROP2 <= ROP2_RS2 xor (ROP2'range => ROP2_X2); ------------- The synchronous logic part ------------- process (clk, reset, ExecPrediv) variable opc: SLV6; begin if reset='0' then CPU_prediv <= ExecPrediv; phase <= '1'; INV <='0'; PC <= (PC'range => '1'); -- loop around to 0 through PC1 -- emulate a "NOP" : opcode <= "000000"; form <= "00"; Field_SND <= "0000"; Field_SI4 <= "0000"; -- because Synplify complains BUT they are not necessary for the stability Carry <= '0'; FlagEqual <= '0'; R1 <= (R1'range => '0'); R2 <= (R1'range => '0'); R3 <= (R1'range => '0'); R4 <= (R1'range => '0'); R5 <= (R1'range => '0'); A1 <= (R1'range => '0'); A2 <= (R1'range => '0'); A3 <= (R1'range => '0'); A4 <= (R1'range => '0'); A5 <= (R1'range => '0'); Reg_SI4 <= (R1'range => '0'); SND <= (R1'range => '0'); else if rising_edge(clk) and INV='0' then if ProgRdEn='0' then PC <= NPC; end if; if CPU_prediv /= "0000" then CPU_prediv <= std_logic_vector(unsigned(CPU_prediv)-1); else CPU_prediv <= ExecPrediv; if phase='0' then -------------------Phase 0----------------------- phase<='1'; -- latch the proper operands : form <= RAM_out(1 downto 0); opc <= RAM_out(R_OPCODE'range); opcode <= opc; int_opcode<=safe_to_integer(opc); -- pre-decodes the opcode Field_SND <= RAM_out(R_SND'range); SND <= RSND_out; if RAM_out(0)='1' then -- long instruction Field_SI4 <= RAM_out(R_SI4'range); -- destination for IMM16, source for EXT if RAM_out(1)='1' then -- latch if EXT Reg_SI4 <= RSI4_out; -- unused field for IMM16 end if; else -- short instruction if RAM_out(1)='1' then Field_SI4 <= RAM_out(R_SI4'range); -- FORM_RI4 else Reg_SI4 <= RSI4_out; -- FORM_RR end if; end if; -- lock the CPU if the opcode is invalid if RAM_out(R_OPCODE'range) = Op_INV or RAM_out(R_OPCODE'range) = Op_HALT or FlagValidInstruction(safe_to_integer(RAM_out(R_OPCODE'range)))='0' then INV <= '1'; end if; else -------------------Phase 1----------------------- phase<='0'; if WB_en='1' and FlagChangeCarry(int_opcode)='1' then Carry <= Carry_out; end if; if WB_en='1' and FlagChangeEqual(int_opcode)='1' then FlagEqual <= equal_out; end if; if WR_en='1' then case DST is when REG_R1 => R1 <= Result; when REG_R2 => R2 <= Result; -- note : the D registers come when REG_R3 => R3 <= Result; -- from external memories and when REG_R4 => R4 <= Result; -- are written by external circuits when REG_R5 => R5 <= Result; -- (the wrapper spies on the control when REG_A1 => A1 <= Result; -- signals and DST to detect if/when when REG_A2 => A2 <= Result; -- to write these registers) when REG_A3 => A3 <= Result; when REG_A4 => A4 <= Result; when REG_A5 => A5 <= Result; when others => -- NPC et PC sont gérés autre part end case; end if; end if; end if; end if; end if; end process; end reboot;