|
nedoPC.orgElectronics hobbyists community established in 2002 |
|
TRCM - симулируем цифровое железо на C++
Author |
Message |
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Вот более вменяемый тест - три полусумматора подключенные как инкремент трёх-битного числа: | | | | Code: #include "TRCMath.hpp"
using namespace std;
using namespace TRC;
class HalfAdder : public Entity { protected:
// indecies: int iA,iB,iS,iC;
// inputs: Signal A,B;
// outputs: Signal S,C;
public:
HalfAdder(const char* s) : Entity(s) {
// empty constructor for generic unit
}
void step() { A = io(iA).read(); B = io(iB).read();
if(A==TRUE && B==TRUE) S = FALSE; else if(A==FALSE && B==TRUE) S = TRUE; else if(A==TRUE && B==FALSE) S = TRUE; else // if(A==FALSE && B==FALSE) S = FALSE;
if(A==TRUE && B==TRUE) C = TRUE; else C = FALSE;
// cout << name() << ":" << A << B << "->" << C << S << endl;
io(iS) << S; io(iC) << C; } };
class World : public Entity {
// indecies: int i_increment,i_input,i_output,i_carry;
// internal counter: int counter;
public:
World() : Entity("World") { i_increment = at("INC"); i_input = at("I",3); i_output = at("O",3); i_carry = at("C2");
counter = 0; }
void step() // test cases { Wire<4> vec; // temporary vector vec[0] = io(i_output+0).read(); vec[1] = io(i_output+1).read(); vec[2] = io(i_output+2).read(); vec[3] = io(i_carry).read(); cout << "Case " << counter << " output=" << vec << endl;
switch(counter++) { case 0: case 1: case 2: case 3: io(i_increment) << FALSE; io(i_input+0) << TRUE; io(i_input+1) << TRUE; io(i_input+2) << TRUE; break;
case 4: case 5: case 6: case 7: io(i_increment) << TRUE; io(i_input+0) << TRUE; io(i_input+1) << TRUE; io(i_input+2) << TRUE; break;
case 8: case 9: case 10: case 11: io(i_increment) << TRUE; io(i_input+0) << FALSE; io(i_input+1) << FALSE; io(i_input+2) << FALSE; break;
} }
};
int main() { System *sys = System::getInstance();
World world;
class HalfAdder0 : public HalfAdder { public: HalfAdder0() : HalfAdder("HalfAdder0") { iA = at("INC"); iB = at("I[0]"); iS = at("O[0]"); iC = at("C0"); } } ha0;
class HalfAdder1 : public HalfAdder { public: HalfAdder1() : HalfAdder("HalfAdder1") { iA = at("C0"); iB = at("I[1]"); iS = at("O[1]"); iC = at("C1"); } } ha1;
class HalfAdder2 : public HalfAdder { public: HalfAdder2() : HalfAdder("HalfAdder2") { iA = at("C1"); iB = at("I[2]"); iS = at("O[2]"); iC = at("C2"); } } ha2;
for(int i=0;i<=12;i++) { sys->prepare(); ha0.step(); ha1.step(); ha2.step(); world.step(); } }
| | | | |
Вывод программы: Обратите внимание как считается 111+1: Case 5 output=PPPN (0111 от старого результата) Case 6 output=NPPN (0110) Case 7 output=NNPN (0100) Case 8 output=NNNP (1000) Только на четвёртом такте бит переполнения прошёл через все три полусумматора
|
15 Aug 2018 00:28 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Всё таки решил добавить сишные макросы, чтобы спрятать кишки классов: И вот тест на проверку 8-битного инкрементера, составленного из 8 двоичных полусумматоров: | | | | Code: #include "../TRCMath.hpp" #undef DEBUG
#include <time.h> #include <stdio.h> #include <stdlib.h>
using namespace std;
using namespace TRC;
class HalfAdder : public Entity { protected:
// indecies: int iA,iB,iS,iC;
// inputs: Signal A,B;
// outputs: Signal S,C;
public:
HalfAdder(const char* s) : Entity(s) {
// empty constructor for generic unit
}
void step() { A = io(iA).read(); B = io(iB).read();
if(A==TRUE && B==TRUE) S = FALSE; else if(A==FALSE && B==TRUE) S = TRUE; else if(A==TRUE && B==FALSE) S = TRUE; else // if(A==FALSE && B==FALSE) S = FALSE;
if(A==TRUE && B==TRUE) C = TRUE; else C = FALSE;
// cout << name() << ":" << A << B << "->" << C << S << endl;
io(iS) << S; io(iC) << C; } };
class World : public Entity {
// indecies: int i_increment,i_input,i_output,i_carry;
public:
// internal counter: long long counter;
World() : Entity("World") { i_increment = at("INC"); i_input = at("I",8); i_output = at("O",8); i_carry = at("COUT");
counter = 0; }
void step() // test cases { Wire<9> vec; // temporary vector vec[8] = io(i_output+0).read(); vec[7] = io(i_output+1).read(); vec[6] = io(i_output+2).read(); vec[5] = io(i_output+3).read(); vec[4] = io(i_output+4).read(); vec[3] = io(i_output+5).read(); vec[2] = io(i_output+6).read(); vec[1] = io(i_output+7).read(); vec[0] = io(i_carry).read(); #ifdef DEBUG cout << "Case " << counter << " output=" << vec.binarize() << endl; #endif
io(i_increment) << TRUE; io(i_input+0) << ((counter&(1<<4))?TRUE:FALSE); io(i_input+1) << ((counter&(1<<5))?TRUE:FALSE); io(i_input+2) << ((counter&(1<<6))?TRUE:FALSE); io(i_input+3) << ((counter&(1<<7))?TRUE:FALSE); io(i_input+4) << ((counter&(1<<8))?TRUE:FALSE); io(i_input+5) << ((counter&(1<<9))?TRUE:FALSE); io(i_input+6) << ((counter&(1<<10))?TRUE:FALSE); io(i_input+7) << ((counter&(1<<11))?TRUE:FALSE);
counter++; }
};
unsigned char BYTE = 0;
int main() { unsigned long t1,t2; int i,n = 100000000; t1 = clock(); for(i=0;i<n;i++) { BYTE++; BYTE++; BYTE++; BYTE++; BYTE++; BYTE++; BYTE++; BYTE++; BYTE++; BYTE++; } t2 = clock();
System *sys = System::getInstance();
World world;
INSTANCE(HalfAdder,0); iA = at("INC"); iB = at("I[0]"); iS = at("O[0]"); iC = at("C0"); NAMED(ha0);
INSTANCE(HalfAdder,1); iA = at("C0"); iB = at("I[1]"); iS = at("O[1]"); iC = at("C1"); NAMED(ha1);
INSTANCE(HalfAdder,2); iA = at("C1"); iB = at("I[2]"); iS = at("O[2]"); iC = at("C2"); NAMED(ha2);
INSTANCE(HalfAdder,3); iA = at("C2"); iB = at("I[3]"); iS = at("O[3]"); iC = at("C3"); NAMED(ha3);
INSTANCE(HalfAdder,4); iA = at("C3"); iB = at("I[4]"); iS = at("O[4]"); iC = at("C4"); NAMED(ha4);
INSTANCE(HalfAdder,5); iA = at("C4"); iB = at("I[5]"); iS = at("O[5]"); iC = at("C5"); NAMED(ha5);
INSTANCE(HalfAdder,6); iA = at("C5"); iB = at("I[6]"); iS = at("O[6]"); iC = at("C6"); NAMED(ha6);
INSTANCE(HalfAdder,7); iA = at("C6"); iB = at("I[7]"); iS = at("O[7]"); iC = at("COUT"); NAMED(ha7);
printf("BYTE=0x%2.2X (%6.6fs or %2.2fns per increment)\n",BYTE, (double)(t2-t1)/CLOCKS_PER_SEC, (double)(t2-t1)/(n/1e8)/CLOCKS_PER_SEC );
n = 0x100000; t1 = clock(); while(world.counter!=n) { sys->prepare(); ha0.step(); ha1.step(); ha2.step(); ha3.step(); ha4.step(); ha5.step(); ha6.step(); ha7.step(); world.step(); } t2 = clock();
printf("%4.4fs or %2.2fns per step\n", (double)(t2-t1)/CLOCKS_PER_SEC, (double)(t2-t1)/(n/1e9)/CLOCKS_PER_SEC ); }
| | | | |
Тут заодно проверка скорости идёт и сравнение с тупым инкрементом одного байта (unsigned char): С учётом того, что для 8-битного инкрементера распространение правильного значения проходит до 8 тактов мы считаем 1646ns*8=13168ns против 2.31ns у скомпилированной версии - в 5700 раз медленнее!!! P.S. А если с ключами оптимизации собирать, то получается так: почти в 10 раз быстрее чем было (и в 600 раз медленнее нативного байтового инкремента) P.P.S. это получается около 700 тысяч байтовых инкрементов в секунду на моей машине - Intel 8080 в реальном времени не сэмулишь, но полюбому побыстрее будет чем в логисиме P.P.P.S. с другой стороны если эмулировать схему быстрого переноса, то икремент раза в 4 побыстрее должно быть, а так получилось почти 6 миллионов шагов симуляции в секунду ( для 18 точек коннекта, на одной коре что равносильно порядка 100 милионнов точек коннекта в секунду, но в теории можно и распараллелить на все доступные коры ; )
|
16 Aug 2018 22:55 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Вот чего существут из похожего кроме SystemC: https://www.veripool.org/wiki/verilator - Verilator - транслятор из Verilog в C++ и SystemC http://verilog2cpp.sourceforge.net/ - Verilog2C++ (по ссылке выше написано, что проект заброшен) Надо попробовать их тоже... P.S. вот тут достаточно простой тест скорости с цифрами: https://www.veripool.org/boards/2/topics/1896-Verilator-Verilator-Speedно чуваку отвечают, что его тест слишком простой, чтобы увидеть все чудеса ускорения Верилятора... P.P.S. существует очень компактная реализация 8080 на Верилоге (с микрокодом): https://opencores.org/project/light8080надо её чтоли попробовать сконвертить да погонять... P.P.P.S. есть более старая реализация 8080 потяжелее (без микрокода): https://opencores.org/project/cpu8080P.P.P.P.S. ещё есть софт-процы OPC (One Page Computing) - одна страница на верилоге для каждого процыка от OPC-1 до OPC-7: https://hackaday.io/project/25357-opc-1-cpu-for-cpldhttps://github.com/revaldinho/opc
|
17 Aug 2018 16:10 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Наверное пока не поздно надо добавлять слабую подтяжку к промежуточному значению - PULLMID которая скажем символ '-' (минус)? Ну или '~' (тильда) P.S. Любое значение 'X' кстати наверное таки надо добавить - для сравнений (и возможно следует разделить понятия "любое двоичное значение" и "любое троичное значение")
|
17 Aug 2018 16:18 |
|
|
Клапауций
Banned
Joined: 29 Jun 2018 08:48 Posts: 413
|
так или иначе - результатом всего этого будет: или "любое двоичное" или "любое". любое значение вообще. потому, как: почему троичная система, а не n-ричная, где n - бесконечность?
|
18 Aug 2018 00:46 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
N-ричные системы где N>3 не имеют никаких преимуществ - только лишние сложности...
|
18 Aug 2018 00:55 |
|
|
Клапауций
Banned
Joined: 29 Jun 2018 08:48 Posts: 413
|
ок. если теоретически N>3 не имеет смысла, то вопрос закрыт.
|
18 Aug 2018 01:33 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Я в том же 2010 когда нарисовал свой классический сдвоенный TRIMUX из двух DG403 также нарисовал четверичный мультиплексор на тех же DG403 - на каждую границу уровней надо 2 половинки DG403, т.е. чем больше "ричность" тем больше DG403-х...
|
18 Aug 2018 09:48 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Всё - перевалил за 1000 строк C++ кода Теперь у меня базовым классом является нетемплейтный класс Wires, а уже все остальные темплейтные классы происходят от него - так проще потом будет с разными типами цепей работать...
|
18 Aug 2018 20:21 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Попробую вручную переписать процык OPC1 с Верилога на C++ | | | | Code: module opccpu( inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b); parameter FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4 ; parameter AND=5'bx0000, LDA=5'bx0001, NOT=5'bx0010, ADD=5'bx0011; parameter LDAP=5'b01001, STA=5'b11000, STAP=5'b01000; parameter JPC=5'b11001, JPZ=5'b11010, JP=5'b11011, JSR=5'b11100; parameter RTS=5'b11101, LXA=5'b11110; reg [10:0] OR_q, PC_q; reg [7:0] ACC_q; reg [2:0] FSM_q; reg [4:0] IR_q; reg [2:0] LINK_q; // bottom bit doubles up as carry flag `define CARRY LINK_q[0] wire writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ; assign rnw = ~writeback_w ; assign data = (writeback_w)?ACC_q:8'bz ; assign address = ( writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2)? OR_q:PC_q;
always @ (posedge clk or negedge reset_b ) if (!reset_b) FSM_q <= FETCH0; else case(FSM_q) FETCH0 : FSM_q <= FETCH1; FETCH1 : FSM_q <= (IR_q[4])?EXEC:RDMEM ; RDMEM : FSM_q <= (IR_q==LDAP)?RDMEM2:EXEC; RDMEM2 : FSM_q <= EXEC; EXEC : FSM_q <= FETCH0; endcase
always @ (posedge clk) begin IR_q <= (FSM_q == FETCH0)? data[7:3] : IR_q; // OR_q[10:8] is upper part nybble for address - needs to be zeroed for both pointer READ and WRITE operations once ptr val is read OR_q[10:8] <= (FSM_q == FETCH0)? data[2:0]: (FSM_q==RDMEM)?3'b0:OR_q[10:8]; OR_q[7:0] <= data; //Lowest byte of OR is dont care in FETCH0 and at end of EXEC if ( FSM_q == EXEC ) casex (IR_q) JSR : {LINK_q,ACC_q} <= PC_q ; LXA : {LINK_q,ACC_q} <= {ACC_q[2:0], 5'b0, LINK_q}; AND : {`CARRY, ACC_q} <= {1'b0, ACC_q & OR_q[7:0]}; NOT : ACC_q <= ~OR_q[7:0]; LDA : ACC_q <= OR_q[7:0]; LDAP : ACC_q <= OR_q[7:0]; ADD : {`CARRY,ACC_q} <= ACC_q + `CARRY + OR_q[7:0]; default: {`CARRY,ACC_q} <= {`CARRY,ACC_q}; endcase end
always @ (posedge clk or negedge reset_b ) if (!reset_b) // On reset start execution at 0x100 to leave page zero clear for variables PC_q <= 11'h100; else if ( FSM_q == FETCH0 || FSM_q == FETCH1 ) PC_q <= PC_q + 1; else case (IR_q) JP : PC_q <= OR_q; JPC : PC_q <= (`CARRY)?OR_q:PC_q; JPZ : PC_q <= ~(|ACC_q)?OR_q:PC_q; JSR : PC_q <= OR_q; RTS : PC_q <= {LINK_q, ACC_q}; default: PC_q <= PC_q; endcase endmodule
| | | | |
У всех OPC есть тесты - так что можно легко проверить удачно сконвертилось или нет
|
18 Aug 2018 21:16 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Вобщем внутри каждого класса, описывающего пучёк проводов (Wire<N>, Uint<N>, Sint<N> и Tint<N>), каждый провод является символом в строке - т.е. их всегда можно заслать на печать т.к. это обычная сишная zero-terminated строка). Отсюда проблема - обращения к строке по индексу сначала (слева), а при представлении числа битами (тритами) как бы подразумевается, что слева самый старший, а справа - самый младший разряд. Получается строку надо переворачивать - вот я думаю переворачивать её только при печати в консоль и при создании объектов из строк, а внтури оно всё также будет идти от младшего к старшему. Пример: У кого какие соображения? P.S. Всё сделал https://gitlab.com/ternary/trcmP.P.S. Uint и Sint отличаются при конверсии из числовых типов си и в числовые типы (например для печати), а также тем в какой момент будет происходить переполнение при сложении/вычитании (Uint<16> например переполнится если сложить 65535 и 1 или вычесть 1 из 0, а Sint<16> если сложить 32767 и 1 или вычесть 1 из -32768)
|
19 Aug 2018 14:58 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Пока вот как получается (комментарии с начала строки - это оригинальный верилоговский код OPC1): | | | | Code: // OPC1 test - based on https://github.com/revaldinho/opc/tree/master/opc1
#include <TRCMath.hpp>
using namespace std;
using namespace TRC;
// module opccpu( inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b); // SEE BELOW
class OPC1 : public Entity { public:
// parameter FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4 ; const unsigned FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4;
// parameter AND=5'bx0000, LDA=5'bx0001, NOT=5'bx0010, ADD=5'bx0011; const Wire<5> AND, LDA, NOT, ADD; // SEE BELOW
// parameter LDAP=5'b01001, STA=5'b11000, STAP=5'b01000; const Wire<5> LDAP, STA, STAP; // SEE BELOW
// parameter JPC=5'b11001, JPZ=5'b11010, JP=5'b11011, JSR=5'b11100; const Wire<5> JPC, JPZ, JP, JSR; // SEE BELOW
// parameter RTS=5'b11101, LXA=5'b11110; const Wire<5> RTS, LXA; // SEE BELOW
// reg [10:0] OR_q, PC_q; Wire<11> OR_q; Uint<11> PC_q;
// reg [7:0] ACC_q; Uint<8> ACC_q;
// reg [2:0] FSM_q; Uint<3> FSM_q;
// reg [4:0] IR_q; Wire<5> IR_q;
// reg [2:0] LINK_q; // bottom bit doubles up as carry flag Wire<3> LINK_q;
//`define CARRY LINK_q[0] #define CARRY LINK_q[0]
// IMPLEMENTATION OF INTERFACE (inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b);
int i_data, i_address, i_rnw, i_clk, i_reset_b;
OPC1() : Entity("OPC1"), // initialization for constant Wire<5>s: AND("X0000"), LDA("X0001"), NOT("X0010"), ADD("X0011"), LDAP("01001"), STA("11000"), STAP("01000"), JPC("11001"), JPZ("11010"), JP("11011"), JSR("11100"), RTS("11101"), LXA("11110") { // global attachements (we have the only instance of the class so it will work) i_data = at("DATA",8,PULLUP); i_address = at("ADDRESS",11); i_rnw = at("RNW"); i_clk = at("CLK"); i_reset_b = at("RESET_B"); }
void step() {
Signal reset_b = io(i_reset_b).read();
// wire writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ; Signal writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;
// assign rnw = ~writeback_w ; Signal rnw = ~writeback_w;
// assign data = (writeback_w)?ACC_q:8'bz ; Wire<8> data; // it's all Z by default if(writeback_w) data = ACC_q; else // below required for later logic { for(int i=0;i<8;i++) data[i] = io(i_data+i).read(); }
// assign address = ( writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2)? OR_q:PC_q; Wire<11> address = ( (bool)writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2 )? OR_q:PC_q;
// always @ (posedge clk or negedge reset_b ) if(posedge(i_clk) || negedge(i_reset_b)) {
// if (!reset_b) if (!reset_b)
// FSM_q <= FETCH0; FSM_q = FETCH0;
// else else { // case(FSM_q) // FETCH0 : FSM_q <= FETCH1; if(FSM_q == FETCH0) FSM_q = FETCH1;
// FETCH1 : FSM_q <= (IR_q[4])?EXEC:RDMEM ; else if(FSM_q == FETCH1) FSM_q = (IR_q[4]==TRUE)?EXEC:RDMEM;
// RDMEM : FSM_q <= (IR_q==LDAP)?RDMEM2:EXEC; else if(FSM_q == RDMEM) FSM_q = (IR_q==LDAP)?RDMEM2:EXEC;
// RDMEM2 : FSM_q <= EXEC; else if(FSM_q == RDMEM2) FSM_q = EXEC;
// EXEC : FSM_q <= FETCH0; else if(FSM_q == EXEC) FSM_q = FETCH0;
// endcase } }
// always @ (posedge clk) if(posedge(i_clk))
// begin {
// IR_q <= (FSM_q == FETCH0)? data[7:3] : IR_q; if(FSM_q == FETCH0) { IR_q[0] = data[3]; IR_q[1] = data[4]; IR_q[2] = data[5]; IR_q[3] = data[6]; IR_q[4] = data[7]; }
// // OR_q[10:8] is upper part nybble for address - needs to be zeroed // // for both pointer READ and WRITE operations once ptr val is read // OR_q[10:8] <= (FSM_q == FETCH0)? data[2:0]: (FSM_q==RDMEM)?3'b0:OR_q[10:8]; if(FSM_q == FETCH0) { OR_q[8] = data[0]; OR_q[9] = data[1]; OR_q[10] = data[2]; } else if(FSM_q == RDMEM) { OR_q[8] = FALSE; OR_q[9] = FALSE; OR_q[10] = FALSE; }
// OR_q[7:0] <= data; //Lowest byte of OR is dont care in FETCH0 and at end of EXEC for(int i=0;i<8;i++) OR_q[i] = data[i];
// if ( FSM_q == EXEC ) if(FSM_q == EXEC) {
// casex (IR_q) // JSR : {LINK_q,ACC_q} <= PC_q ; if(IR_q==JSR) { LINK_q = PC_q.part(8,10); ACC_q = PC_q.part(0,7); }
// LXA : {LINK_q,ACC_q} <= {ACC_q[2:0], 5'b0, LINK_q}; else if(IR_q==LXA) { LINK_q = ACC_q.part(0,2); for(int i=7;i>=0;i--) { if(i>=3) ACC_q[i] = FALSE; else ACC_q[i] = LINK_q[i]; } }
// AND : {`CARRY, ACC_q} <= {1'b0, ACC_q & OR_q[7:0]}; else if(IR_q==AND) { CARRY = FALSE; ACC_q = ACC_q & OR_q.part(0,7); }
// NOT : ACC_q <= ~OR_q[7:0]; else if(IR_q==NOT) { ACC_q = ~OR_q.part(0,7); }
// LDA : ACC_q <= OR_q[7:0]; else if(IR_q==LDA) { ACC_q = OR_q.part(0,7); }
// LDAP : ACC_q <= OR_q[7:0]; else if(IR_q==LDAP) { ACC_q = OR_q.part(0,7); }
// ADD : {`CARRY,ACC_q} <= ACC_q + `CARRY + OR_q[7:0]; else if(IR_q==ADD) { ACC_q += OR_q.part(0,7); if(CARRY==TRUE) ACC_q++; if(ACC_q.overflow()) CARRY = TRUE; else CARRY = FALSE; }
// default: {`CARRY,ACC_q} <= {`CARRY,ACC_q}; // endcase }
// end }
// always @ (posedge clk or negedge reset_b ) if(posedge(i_clk) || negedge(i_reset_b)) {
// if (!reset_b) // On reset start execution at 0x100 to leave page zero clear for variables if(!reset_b)
// PC_q <= 11'h100; PC_q = 0x100;
// else else
// if ( FSM_q == FETCH0 || FSM_q == FETCH1 ) if(FSM_q==FETCH0 || FSM_q==FETCH1)
// PC_q <= PC_q + 1; PC_q++;
// else else {
// case (IR_q) // JP : PC_q <= OR_q; if(IR_q==JP) { PC_q = OR_q; }
// JPC : PC_q <= (`CARRY)?OR_q:PC_q; else if(IR_q==JPC) { PC_q = (CARRY==TRUE)?OR_q:PC_q; }
// JPZ : PC_q <= ~(|ACC_q)?OR_q:PC_q; else if(IR_q==JPZ) { PC_q = (ACC_q==0)?OR_q:PC_q; }
// JSR : PC_q <= OR_q; else if(IR_q==JSR) { PC_q = OR_q; }
// RTS : PC_q <= {LINK_q, ACC_q}; else if(IR_q==RTS) { for(int i=10;i>=0;i--) { if(i>=8) PC_q[i]=LINK_q[i-8]; else PC_q[i]=ACC_q[i]; } }
// default: PC_q <= PC_q; // endcase }
if(writeback_w) { for(int i=0;i<8;i++) io(i_data) << data[i]; } for(int i=0;i<11;i++) io(i_address) << address[i]; io(i_rnw) << rnw; }
//endmodule } };
| | | | |
|
20 Aug 2018 00:46 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
В коде OPC1 есть проблема - там аж ТРИ блока always @ (posedge clk ... которые как бы параллельно исполняются - там читаются и пишутся по сути один и тот же набор регистров. Это значит что выполнять эти блоки последовательно нельзя т.к. более поздняя логика будет использовать уже измененные в ранней логике значения, а оно всё должно меняться одномоментно - по изменению CLK из нуля в "1". Значит придётся хитрить и мухлевать, например вот так: | | | | Code: // OPC1 test - based on https://github.com/revaldinho/opc/tree/master/opc1
#include <TRCMath.hpp>
using namespace std;
using namespace TRC;
// module opccpu( inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b); // SEE BELOW
class OPC1 : public Entity { public:
// parameter FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4 ; const unsigned FETCH0=0, FETCH1=1, RDMEM=2, RDMEM2=3, EXEC=4;
// parameter AND=5'bx0000, LDA=5'bx0001, NOT=5'bx0010, ADD=5'bx0011; const Wire<5> AND, LDA, NOT, ADD; // SEE BELOW
// parameter LDAP=5'b01001, STA=5'b11000, STAP=5'b01000; const Wire<5> LDAP, STA, STAP; // SEE BELOW
// parameter JPC=5'b11001, JPZ=5'b11010, JP=5'b11011, JSR=5'b11100; const Wire<5> JPC, JPZ, JP, JSR; // SEE BELOW
// parameter RTS=5'b11101, LXA=5'b11110; const Wire<5> RTS, LXA; // SEE BELOW
// reg [10:0] OR_q, PC_q; Wire<11> OR_q,OR_q_REG; Uint<11> PC_q,PC_q_REG;
// reg [7:0] ACC_q; Uint<8> ACC_q,ACC_q_REG;
// reg [2:0] FSM_q; Uint<3> FSM_q,FSM_q_REG;
// reg [4:0] IR_q; Wire<5> IR_q,IR_q_REG;
// reg [2:0] LINK_q; // bottom bit doubles up as carry flag Wire<3> LINK_q,LINK_q_REG; // SHOUD WE HAVE A MACROS REG(Wire<3>,LINK_q)?
//`define CARRY LINK_q[0] #define CARRY LINK_q[0] // BUT FOR THE LEFT SIDE WE NEED TO USE TEMPORARY COPY!!! #define CARRY_REG LINK_q_REG[0]
// TEMPORARY SIGNALS: Signal reset_b, writeback_w, rnw; Wire<11> address; Wire<8> data; // it's all Z by default
// IMPLEMENTATION OF INTERFACE (inout[7:0] data, output[10:0] address, output rnw, input clk, input reset_b);
int i_data, i_address, i_rnw, i_clk, i_reset_b;
OPC1() : Entity("OPC1"), // initialization for constant Wire<5>s: AND("X0000"), LDA("X0001"), NOT("X0010"), ADD("X0011"), LDAP("01001"), STA("11000"), STAP("01000"), JPC("11001"), JPZ("11010"), JP("11011"), JSR("11100"), RTS("11101"), LXA("11110") { // global attachements (we have the only instance of the class so it will work) i_data = at("DATA",8,PULLUP); i_address = at("ADDRESS",11); i_rnw = at("RNW"); i_clk = at("CLK"); i_reset_b = at("RESET_B"); }
void step() {
reset_b = io(i_reset_b).read();
// wire writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ; writeback_w = ((FSM_q == EXEC) && (IR_q == STA || IR_q == STAP)) & reset_b ;
// assign rnw = ~writeback_w ; rnw = ~writeback_w;
// assign data = (writeback_w)?ACC_q:8'bz ; if(writeback_w) data = ACC_q; else // below required for later logic { for(int i=0;i<8;i++) data[i] = io(i_data+i).read(); }
// assign address = ( writeback_w || FSM_q == RDMEM || FSM_q==RDMEM2)? OR_q:PC_q; address = ( (bool)writeback_w || FSM_q == RDMEM || FSM_q == RDMEM2 )? OR_q:PC_q;
// always @ (posedge clk or negedge reset_b ) if(posedge(i_clk) || negedge(i_reset_b)) // WRITE FSM_q {
// if (!reset_b) if (!reset_b)
// FSM_q <= FETCH0; FSM_q_REG = FETCH0;
// else else { // case(FSM_q) // FETCH0 : FSM_q <= FETCH1; if(FSM_q_REG == FETCH0) FSM_q = FETCH1;
// FETCH1 : FSM_q <= (IR_q[4])?EXEC:RDMEM ; else if(FSM_q == FETCH1) FSM_q_REG = (IR_q[4]==TRUE)?EXEC:RDMEM;
// RDMEM : FSM_q <= (IR_q==LDAP)?RDMEM2:EXEC; else if(FSM_q == RDMEM) FSM_q_REG = (IR_q==LDAP)?RDMEM2:EXEC;
// RDMEM2 : FSM_q <= EXEC; else if(FSM_q == RDMEM2) FSM_q_REG = EXEC;
// EXEC : FSM_q <= FETCH0; else if(FSM_q == EXEC) FSM_q_REG = FETCH0;
// endcase } }
// always @ (posedge clk) if(posedge(i_clk)) // WRITE IR_q, OR_q, LINK_q, ACC_q and CARRY
// begin {
// IR_q <= (FSM_q == FETCH0)? data[7:3] : IR_q; if(FSM_q == FETCH0) { IR_q_REG[0] = data[3]; IR_q_REG[1] = data[4]; IR_q_REG[2] = data[5]; IR_q_REG[3] = data[6]; IR_q_REG[4] = data[7]; }
// // OR_q[10:8] is upper part nybble for address - needs to be zeroed // // for both pointer READ and WRITE operations once ptr val is read // OR_q[10:8] <= (FSM_q == FETCH0)? data[2:0]: (FSM_q==RDMEM)?3'b0:OR_q[10:8]; if(FSM_q == FETCH0) { OR_q_REG[8] = data[0]; OR_q_REG[9] = data[1]; OR_q_REG[10] = data[2]; } else if(FSM_q == RDMEM) { OR_q_REG[8] = FALSE; OR_q_REG[9] = FALSE; OR_q_REG[10] = FALSE; } else { OR_q_REG[8] = OR_q[8]; OR_q_REG[9] = OR_q[9]; OR_q_REG[10] = OR_q[10]; }
// OR_q[7:0] <= data; //Lowest byte of OR is dont care in FETCH0 and at end of EXEC for(int i=0;i<8;i++) OR_q[i] = data[i];
// if ( FSM_q == EXEC ) if(FSM_q == EXEC) {
// casex (IR_q) // JSR : {LINK_q,ACC_q} <= PC_q ; if(IR_q==JSR) { LINK_q_REG = PC_q.part(8,10); ACC_q_REG = PC_q.part(0,7); }
// LXA : {LINK_q,ACC_q} <= {ACC_q[2:0], 5'b0, LINK_q}; else if(IR_q==LXA) { LINK_q_REG = ACC_q.part(0,2); for(int i=7;i>=0;i--) { if(i>=3) ACC_q_REG[i] = FALSE; else ACC_q_REG[i] = LINK_q[i]; } }
// AND : {`CARRY, ACC_q} <= {1'b0, ACC_q & OR_q[7:0]}; else if(IR_q==AND) { CARRY_REG = FALSE; ACC_q_REG = ACC_q & OR_q.part(0,7); }
// NOT : ACC_q <= ~OR_q[7:0]; else if(IR_q==NOT) { ACC_q_REG = ~OR_q.part(0,7); }
// LDA : ACC_q <= OR_q[7:0]; else if(IR_q==LDA) { ACC_q_REG = OR_q.part(0,7); }
// LDAP : ACC_q <= OR_q[7:0]; else if(IR_q==LDAP) { ACC_q_REG = OR_q.part(0,7); }
// ADD : {`CARRY,ACC_q} <= ACC_q + `CARRY + OR_q[7:0]; else if(IR_q==ADD) { ACC_q_REG = ACC_q + (CARRY==TRUE)?1:0 + OR_q.part(0,7); if(ACC_q.overflow()) CARRY_REG = TRUE; else CARRY_REG = FALSE; }
// default: {`CARRY,ACC_q} <= {`CARRY,ACC_q}; else { CARRY_REG = CARRY; ACC_q_REG = ACC_q; }
// endcase }
// end }
// always @ (posedge clk or negedge reset_b ) if(posedge(i_clk) || negedge(i_reset_b)) // WRITE PC_q {
// if (!reset_b) // On reset start execution at 0x100 to leave page zero clear for variables if(!reset_b)
// PC_q <= 11'h100; PC_q_REG = 0x100;
// else else
// if ( FSM_q == FETCH0 || FSM_q == FETCH1 ) if(FSM_q==FETCH0 || FSM_q==FETCH1)
// PC_q <= PC_q + 1; PC_q_REG = PC_q + 1;
// else else {
// case (IR_q) // JP : PC_q <= OR_q; if(IR_q==JP) { PC_q_REG = OR_q; }
// JPC : PC_q <= (`CARRY)?OR_q:PC_q; else if(IR_q==JPC) { PC_q_REG = (CARRY==TRUE)?OR_q:PC_q; }
// JPZ : PC_q <= ~(|ACC_q)?OR_q:PC_q; else if(IR_q==JPZ) { PC_q_REG = ((unsigned)ACC_q==0)?OR_q:PC_q; }
// JSR : PC_q <= OR_q; else if(IR_q==JSR) { PC_q_REG = OR_q; }
// RTS : PC_q <= {LINK_q, ACC_q}; else if(IR_q==RTS) { for(int i=10;i>=0;i--) { if(i>=8) PC_q_REG[i]=LINK_q[i-8]; else PC_q_REG[i]=ACC_q[i]; } }
// default: PC_q <= PC_q; else PC_q_REG = PC_q;
// endcase }
OR_q = OR_q_REG; PC_q = PC_q_REG; ACC_q = ACC_q_REG; FSM_q = FSM_q_REG; IR_q = IR_q_REG; LINK_q = LINK_q_REG;
if(writeback_w) { for(int i=0;i<8;i++) io(i_data) << data[i]; } for(int i=0;i<11;i++) io(i_address) << address[i]; io(i_rnw) << rnw; }
//endmodule } };
| | | | |
|
20 Aug 2018 21:44 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Короче суть мухлежа состоит в том, что каждый регистр, сохраняющий N сигналов, представляется парой пучков проводов: и в обработчике шага эмуляции все изменения пишутся в *_REG и только в самом конце, когда все вычисления выполнены, *_REG копируют свои значения в основные переменные...
|
22 Aug 2018 18:55 |
|
|
Shaos
Admin
Joined: 08 Jan 2003 23:22 Posts: 22821 Location: Silicon Valley
|
Чото я запустил вопрос - надо возвращаться в тему...
|
13 Sep 2018 21:22 |
|
|
Who is online |
Users browsing this forum: No registered users and 4 guests |
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot post attachments in this forum
|
|