Dit is voor iedereen die interesseert is in FPGA. Na vloeken met de Picoblaze van xilinx die maar 8bit is. Ik had 16 nodig voor wat bit gemanipuleer. Heb ik mijn eigen CPU geschreven om mijn kennis van verilog te vergroten en de werking van een processor. Dit is wat ik nu heb.
-2 clockcyclus per instructie
-16 16bit registers
-11 instructies
-4 interrupts (nog niet af)
-16bit input and output
-8bit adres lijnen
-reset (nog niet af)
-en k.i.s.s Asembler (nog niet af)
Hier is de sourcecode:
http://robin.tussen.be/SPU16BIT.v
Hier is het Instruction Set
http://robin.tussen.be/SPU.doc
To Do
De interrupts werken niet mee.
Mijn vraag naar jullie willen je mijn processor eens goed testen? Een heel simpel assembler komt er aan maar ik kan tot morgen niet aan mijn gebruikelijk PC waar de asembler opstaat.
Alvast bedankt
Robin
Een klein kickje met een vraagje? Heeft iemand een goei idee voor een interrupt controller? Ik zit hier een beetje vast.
Robin
free_electron
Silicon Member
Professioneel ElectronenTemmer - siliconvalleygarage.com - De voltooid verleden tijd van 'halfgeleider' is 'zand' ... US 8,032,693 / US 7,714,746 / US 7,355,303 / US 7,098,557 / US 6,762,632 / EP 1804159 - Real programmers write Hex into ROM
uw in code is verkeerd. is identiek aan je out code.
en gebruik ene case constructie voor je opvodes i s veel simpeler om te lezen
je hebt ook geen default state : je machine kan in deadlock gaan.
tweak volgt ...
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Engineer: Theunis Robin
// Project: MPU
//
// SPU: CPU CORE
// Revision : 0.1 free_electron
//
//////////////////////////////////////////////////////////////////////////////////
module SPU16BIT(
input clk_i, rst_i;
input [23:0] IDATA_i;
input [15:0] DATA_i;
output wire [7:0] IADR_o;
output reg [7:0] DADR_o;
output reg [15:0] DATA_o;
output reg DREAD_o;
output reg DWRITE_o;
);
//REGISTER BANK
reg [15:0] REG [3:0];
//INSTRUCTION FETCH
wire OPCODE = IDATA_I[23:20];
wire OPTK = IDATA_I[16];
wire OPTZ = IDATA_I[17];
wire OPTC = IDATA_I[18];
wire OPTQ = IDATA_I[19];
wire INSTDATA = IDATA_I[15:0];
//REGISTER SELECTS
wire regslctA = IDATA_[15:12];
wire regslctB = IDATA_[11:8];
wire regslctC = IDATA_[19:16];
reg [1:0] STATE;
reg [7:0] PROGRAMCNT
always@(posedge clk_i) begin
if(STATE == 1) begin
//INIT
DWRITE_o <= 0;
DREAD_o <= 0;
case (OPCODE)
1: begin //JUMP
if( (OPTQ & (OPTZ == ZERO)) | (OPTQ & (OPTC == CARRY)) | !OPTQ )
if(OPTK) PROGRAMCNT <= REG[regslctA];
else PROGRAMCNT <= INSTDATA; // ontbreekt hier geen state <=2 assignment ?
end
2: begin // MOVE
REG[regslctA] <= REG[regslctB];
STATE <= 2;
end
3: begin //LOAD
REG[regslctC] <= INSTDATA;
STATE <= 2;
end
4: begin //ADD
REG[regslctA] <= REG[regslctB] + REG[regslctA];
STATE <= 2;
end
5: begin //AND
REG[regslctA] <= REG[regslctB] & REG[regslctA];
STATE <= 2;
end
6: begin // NOT
REG[regslctA] <= !REG[regslctB];
STATE <= 2;
end
7; begin//IN
if(OPTK) DADR_o <= REG[regslctA]; // dit is niet juis t!!
else DADR_o <= INSTDATA;
end
8: begin // OUt
if(OPTK) DADR_o <= REG[regslctA];
else DADR_o <= INSTDATA;
end
endcase
//STATE 2
if(STATE == 2) begin
PROGRAMCNT <= PROGRAMCNT + 1;
STATE <= 1;
if(REG[regslctA] == 0) ZERO <= 1;
else ZERO <= 0;
if(OPCODE == 7) begin
DREAD_o <= 1;
REG[15] <= DATA_i;
end
if(OPCODE == 8) begin
DWRITE_o <= 1;
DATA_i <= REG[15];
end
end
end
endmodule
en dan de problemen
- eerste instructie ; moet je state niet veranderen ? indien ja :smijt al die state assignments weg en zet dat voorop ( net buiten de case ) je kan dan al een gans pak begin / end weghalen ook.
zo
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Engineer: Theunis Robin
// Project: MPU
//
// SPU: CPU CORE
// Revision : 0.1 free_electron
//
//////////////////////////////////////////////////////////////////////////////////
module SPU16BIT(
input clk_i, rst_i;
input [23:0] IDATA_i;
input [15:0] DATA_i;
output wire [7:0] IADR_o;
output reg [7:0] DADR_o;
output reg [15:0] DATA_o;
output reg DREAD_o;
output reg DWRITE_o;
);
//REGISTER BANK
reg [15:0] REG [3:0];
//INSTRUCTION FETCH
wire OPCODE = IDATA_I[23:20];
wire OPTK = IDATA_I[16];
wire OPTZ = IDATA_I[17];
wire OPTC = IDATA_I[18];
wire OPTQ = IDATA_I[19];
wire INSTDATA = IDATA_I[15:0];
//REGISTER SELECTS
wire regslctA = IDATA_[15:12];
wire regslctB = IDATA_[11:8];
wire regslctC = IDATA_[19:16];
reg [1:0] STATE;
reg [7:0] PROGRAMCNT
always@(posedge clk_i) begin
if(STATE == 1) begin
//INIT
DWRITE_o <= 0;
DREAD_o <= 0;
STATE <= 2;
case (OPCODE)
1: begin //JUMP
if( (OPTQ & (OPTZ == ZERO)) | (OPTQ & (OPTC == CARRY)) | !OPTQ )
if(OPTK) PROGRAMCNT <= REG[regslctA];
else PROGRAMCNT <= INSTDATA; // ontbreekt hier geen state <=2 assignment ?
end
2: REG[regslctA] <= REG[regslctB];
3: REG[regslctC] <= INSTDATA;
4: REG[regslctA] <= REG[regslctB] + REG[regslctA];
5: REG[regslctA] <= REG[regslctB] & REG[regslctA];
6: REG[regslctA] <= !REG[regslctB];
7; if(OPTK) DADR_o <= REG[regslctA]; // dit is niet juis t!!
else DADR_o <= INSTDATA;
8: if(OPTK) DADR_o <= REG[regslctA];
else DADR_o <= INSTDATA;
default : PROGRAMCNT<=PROGRAMCNT +1; ' NOP instructie
endcase
//STATE 2
if(STATE == 2) begin
PROGRAMCNT <= PROGRAMCNT + 1;
STATE <= 1;
if(REG[regslctA] == 0) ZERO <= 1;
else ZERO <= 0;
if(OPCODE == 7) begin
DREAD_o <= 1;
REG[15] <= DATA_i;
end
if(OPCODE == 8) begin
DWRITE_o <= 1;
DATA_i <= REG[15];
end
end
end
endmodule
ik heb er maar meteen een NOp tussengezet ook. als de processor in jouw code ene onbekende instructie tegenkomt blokkeert hij ( state verandert niet waardoor de program counter niet ophoogt ....
mijn 'default' zorgt er voor datvoor een onbekende instructie de program countr verhoogt.
je in /out code snap ik niet.
waar is de 'reset' code ?
[Bericht gewijzigd door free_electron op (92%)]
UPDATED:
versie 231
@FE
Mijn IN en OUT code heb ik verandert. Ik had de verkeerde versie online gezet. Van al die IF's heb ik een case gemaakt
en een default toegevoegt. Nu is opcode 0 eigenlijk een NOP.
Dank voor uw professionele blik
Robin
free_electron
Silicon Member
Professioneel ElectronenTemmer - siliconvalleygarage.com - De voltooid verleden tijd van 'halfgeleider' is 'zand' ... US 8,032,693 / US 7,714,746 / US 7,355,303 / US 7,098,557 / US 6,762,632 / EP 1804159 - Real programmers write Hex into ROM
blijf nog efkes online. ik ga eerst ontbijten (net wakker .. klets verilog bekijken op nuchtere maag is niet gezond ... ) kzal er dan nog eens paar blikken op werpen.
een tros 'wires' kan je beter vervangen door `define.
en ik heb zo nog ene paar iedeeen.
je kan met een paar extra lijnen code dat ding veel krachtiger maken ( setbit , clearbit , testbit handig om io operaties te doen. + en - is ook altijd leuk
jump instructie ... gosub/ return instructie met stack .... ( ik zou met vast stack werken. das eenvoudigst. stack die 8 diep is bijvoorbeeld.
die gosub return heb je nodig om interrupts te processen. je moet current vector pushen jumpen en daarnaa terugkeren.
IEEP!!! Dat is veel verandert. Lekker kort 
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
//
// Engineer: Theunis Robin
// Project: MPU
//
// SPU: CPU CORE
//
//////////////////////////////////////////////////////////////////////////////////
module SPU16BIT(clk_i, rst_i,INTA_i,INTB_i,INTC_i,INTD_i,INTACK_o, IADR_o, IDATA_i, DADR_o, DATA_i, DATA_o, DREAD_o , DWRITE_o);
//CTL
input clk_i;
input rst_i;
//DATA INPUTS
input wire [23:0] IDATA_i;
input wire [15:0] DATA_i;
//DATA OUT's
output reg [7:0] IADR_o;
output reg [7:0] DADR_o;
output reg [15:0] DATA_o;
output reg DREAD_o;
output reg DWRITE_o;
//REGISTER BANK
reg [15:0] REG [0:15];
//INTERRUPT ENABLE
reg INTENABLE;
//INTERRUPT INPUTS
input wire INTA_i;
input wire INTB_i;
input wire INTC_i;
input wire INTD_i;
//ACK OUTOUT
output wire [7:0] INTACK_o;
reg INTA_fired;
reg INTB_fired;
reg INTC_fired;
reg INTD_fired;
wire INT_fired = INTA_fired | INTB_fired | INTC_fired | INTD_fired;
//INTERRUPT ADRESS
reg [7:0] INTA;
reg [7:0] INTB;
reg [7:0] INTC;
reg [7:0] INTD;
reg [7:0] DEBUG;
//STACK ADR
reg [7:0] STACK;
//INSTRUCTION FETCH
wire [3:0] OPCODE = IDATA_i[23:20];
wire OPTK = IDATA_i[16];
wire OPTZ = IDATA_i[17];
wire OPTC = IDATA_i[18];
wire OPTQ = IDATA_i[19];
wire INSTDATA = IDATA_i[15:0];
//REGISTER SELECTS
wire regslctA = IDATA_i[15:12];
wire regslctB = IDATA_i[11:8];
wire regslctC = IDATA_i[19:16];
wire INTslct = IDATA_i[17:16];
wire INTadr = IDATA_i[7:0];
reg STATE;
reg [7:0] PROGRAMCNT;
reg [7:0] DATAREGOUT;
reg [7:0] DATAREGIN;
reg ZERO;
reg CARRY;
assign INTACK_o = DEBUG ;
always@(posedge clk_i)
begin
if (INTA_i) begin
INTA_fired <= 1;
end
if(STATE == 0) begin
INTA_fired <= 0;
INTB_fired <= 0;
INTC_fired <= 0;
INTD_fired <= 0;
//INIT
case(OPCODE)
//JUMP
1: begin
if( (OPTQ & (OPTZ == ZERO)) | (OPTQ & (OPTC == CARRY)) | !OPTQ ) begin
if(OPTK) begin
PROGRAMCNT <= REG[regslctA];
end else begin
PROGRAMCNT <= INSTDATA;
end
STATE <= 1;
end
end
//END JUMP
//MOVE
2: begin
REG[regslctA] <= REG[regslctB];
STATE <= 1;
end
//END MOVE
//LOAD
3: begin
REG[regslctC] <= INSTDATA;
STATE <= 1;
end
//END LOAD
//ADD
4: begin
REG[regslctA] <= REG[regslctB] + REG[regslctA];
STATE <= 1;
end
//END ADD
//AND
5: begin
REG[regslctA] <= REG[regslctB] & REG[regslctA];
STATE <= 1;
end
//END AND
//NOT
6: begin
REG[regslctA] <= !REG[regslctB];
STATE <= 1;
end
//END NOT
//IN
7: begin
DADR_o <= REG[regslctA];
DREAD_o <= 1;
STATE <= 1;
end
//END IN
//OUT
8: begin
DADR_o <= REG[regslctA];
DATA_o <= REG[regslctB];
DWRITE_o <= 1;
STATE <= 1;
end
//END OUT
//LOAD INT
9: begin
case (INTslct)
0: INTA <= INTadr;
1: INTB <= INTadr;
2: INTC <= INTadr;
3: INTD <= INTadr;
endcase
STATE <= 1;
end
//ENDLOADINT
//ENABLE/DISABLE INT
10: begin
INTENABLE <= OPTK;
STATE <= 1;
end
//INT
//RETURN
11: begin
PROGRAMCNT <= STACK;
STATE <= 1;
end
default:
STATE <= 1;
endcase
end
//END STATE 1
//STATE 2
if(STATE==1) begin
STATE <= 0;
if(!INT_fired) begin
if(!(OPCODE==1) & !(OPCODE==11)) begin
PROGRAMCNT <= PROGRAMCNT + 1;
end
end else begin
STACK <= PROGRAMCNT;
if(INTA_fired) begin
PROGRAMCNT <= INTA;
end else begin
if(INTB_fired) begin
PROGRAMCNT <= INTB;
end else begin
if(INTC_fired) begin
PROGRAMCNT <= INTC;
end else begin
if(INTD_fired) begin
PROGRAMCNT <= INTD;
end
end
end
end
end
IADR_o <= PROGRAMCNT;
//ZERO CHECH
if(REG[regslctA] == 0) begin
ZERO <= 1;
end else begin
ZERO <= 0;
end
//READ DELAY
if(OPCODE == 7) begin
REG[regslctB] <= DATA_i;
end
DWRITE_o <= 0;
DREAD_o <= 0;
end
//END STATE 2
end
endmodule
Hier is de in/out wel goed. In de eerste state verander ik het adress en en de dataout en in het tweede state lees ik de data in.
PS Drink een goeie tas koffie.
Robin
free_electron
Silicon Member
Professioneel ElectronenTemmer - siliconvalleygarage.com - De voltooid verleden tijd van 'halfgeleider' is 'zand' ... US 8,032,693 / US 7,714,746 / US 7,355,303 / US 7,098,557 / US 6,762,632 / EP 1804159 - Real programmers write Hex into ROM
doe je daar nu om om dat zo langdradig te schrijven ?
zet die state <=1 woorop en gooi dat uit al de cases.
je bent van een gans pak begin en ends vanaf.
en verplaats je commentaar naar achter ene regel. in plaats van 36 paginas code heb je dan1 pagina...
en gebruik verilog 2000 syntax dan hoef je die module ports en de in/out definities geen 2x te schrijven.vermindert kans op fouten en kans op miserie.
free_electron
Silicon Member
Professioneel ElectronenTemmer - siliconvalleygarage.com - De voltooid verleden tijd van 'halfgeleider' is 'zand' ... US 8,032,693 / US 7,714,746 / US 7,355,303 / US 7,098,557 / US 6,762,632 / EP 1804159 - Real programmers write Hex into ROM
een paar ideen : maak een harvard machine met gesplit code/data memory. dat maakt je compilerbouw en je core bouw eenvoudiger.
als je dan je datadomain wilt scalen lukt dat heel makkelijk
je hebt 3 move instructies nodig :
- rom naar register.
- ram naar register.
- register naar ram.
alle operaties draaien op registers.
we gaan even van een 16 bit programawoord uit. en 32 registers in de cpu.
das 5 bit voor source en 5 bit voor target. dat laat 6 bit voor instructies. in een code word.
ik zou een flag register maken met daarin de volgende flags : carry , equal , less , more ,check
[6bit operand] [target reg] [source reg]
vor de compiler :
de machiene heeft 32 registers.
normaal werk je met R0 en R1 ( kan met andere dingen zijn ) maar voor de meeste operaties : deze.
R2 en R3 worden gebruikt om jumps te berekenen
R4,R5,R6 en R7 zijn scratchpad.
de preserve restore en conswap operands kunnen een blok van 8 registers verplaatsen of 'swappen' met ene ander blok . dat maakt dat je heel snel interrupt kan afhandelen. geef elke interrupt zijn eigen 'blok' al wat je moet doen is even een preserve/ restore of een conswap en de boel draait verder. geen gemodder met berekeningen.
de SFR zijn hardware registers. die hebben hun eigen poort. daar kan je dan configuratie registers aan knopen zoals bijvoorbeeld een baudrate generator of de interrupt control registers.
de stack is ene memory apart. ( in fpga zijn vaak memories aanwezig : knoop een deidcated memory aan de stack bus vast. zo heb je weer een zorg minder.
reg [15:0] code_pointer , data_pointer ,sfr_pointer;
reg [15:0] data_to_ram , data_to_sfr;
wire [15:0] data_from_ram , data_from_rom ,data_from_sfr;
reg [7:0] stack_pointer;
reg [15:0] data_tostack;
wire [15:0] data_from_stack;
`define OPERAND data_from_rom[16:10]
`define TARGET reg[data_from_rom[9:5]]
`define SOURCE reg[data_from_rom[4:0]]
// instruction set
`define CLEAR 0
`define AND 1
`define OR 2
`define NOT 3
`define EXOR 4
`define SETBIT 5
`define CLEARBIT 6
`define SHIFTL 7
`define SHIFTR 8
`define ROR 9
`define ROL 10
`define ADD 11
`define SUB 12
`define INCR 20
`define DECR 21
`define MUL 22
`define DIV 23
// context operaties
`define PRESERVE 40 // bewaar de context
`define RESTORE 41 // restore context
`define CONSWAP 42
// vanaf nu zijn dit 2 cycli instructies : de instructe gevolgd door het address ( altijd 16 bit )
`define MOVTSFR 48 // move to SFR
`define MOVFSFR 49 // move from SFR
`define MOVTRAM 50 // move register to RAM
`define MOVFRAM 51 // move from RAM
`define MOVFROM 52 // move from ROM
`define PUSH 53 // push iets op de stack
`define POP 54 // haal van de stack
`define GOSUB 55
`define RETURN 56
`define JMP 57 // jump absolute
`define JMPC 58 // jump carry
`define JMPT 59 // jump on testbit
`define JMPE 60 // jump if equal
`define JMPNE 61 // jump not equal
`define JMPL 62 // jump less
`define JMPM 63 // jump if more
// de processor states. de meest instructies zijn 1 clocktick
// voor sprongopdrachten is een argument nodig.
// voor interrupts is er een cleanup.
reg [1:0] CPU_STATE;
`define DECODE 0 // decodeer instructie
`define FETCH_ARG 1 // haal argumetn op
`define APPLY_ARG 2 // voer argument uit
`define NEXT 3 // cleanup
//de state machine wordt simpel :
always @(posedge clk) begin
case (CPU_STATE)
`DECODE : begin
case(OPERAND)
`CLEAR : `TARGET <=0;
`AND : `TARGET <= (`TARGET && `SOURCE);
// enzxovoort
endcase
end
`FETCH_ARG : begin
end
`APPLY_ARG : begin
end
`NEXT begin
end
default : CPU_STATE <= `DECODE;
endcase
if(reset) begin
CPU_STATE <= `DECODE;
// wis all registers
// wis all SFR's
// wis de stack memories.
end