Build and run a node in VM, first!

This commit is contained in:
Anthony Rabine 2023-05-22 15:59:54 +02:00
parent 416b6b2f43
commit 843fa7aeba
20 changed files with 165 additions and 73 deletions

Binary file not shown.

Binary file not shown.

BIN
art/pack/le_sceptre.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
art/pack/un_oiseau.mp3 Normal file

Binary file not shown.

View file

@ -340,6 +340,7 @@ bool Assembler::Parse(const std::string &data)
if (pos != std::string::npos) { if (pos != std::string::npos) {
line.erase(pos); line.erase(pos);
} }
line.erase(std::remove(line.begin(), line.end(), '\t'), line.end()); // remove tabulations
if (std::all_of(line.begin(), line.end(), ::isspace)) continue; if (std::all_of(line.begin(), line.end(), ::isspace)) continue;
// Split the line // Split the line

View file

@ -40,44 +40,44 @@ extern "C" {
typedef enum typedef enum
{ {
// system: // system:
OP_NOP = 0, // do nothing OP_NOP = 0, ///< do nothing
OP_HALT = 1, // halt execution OP_HALT = 1, ///< halt execution
OP_SYSCALL = 2, // system call handled by user-registered function, 4 arguments (R0 - R3) passed by value OP_SYSCALL = 2, ///< system call handled by user-registered function, 4 arguments (R0 - R3) passed by value
// constants: // constants:
OP_LCONS = 3, // store a value in a register, e.g.: lcons r0, 0xA2 0x00 0x00 0x00 OP_LCONS = 3, ///< store a value in a register, e.g.: lcons r0, 0xA2 0x00 0x00 0x00
// can also load a variable address: lcons r2, $DataInRam ///< can also load a variable address: lcons r2, $DataInRam
// register operations: // register operations:
OP_MOV = 4, // copy a value between registers, e.g.: mov r0, r2 OP_MOV = 4, ///< copy a value between registers, e.g.: mov r0, r2
// stack: // stack:
OP_PUSH = 5, // push a register onto the stack, e.g.: push r0 OP_PUSH = 5, ///< push a register onto the stack, e.g.: push r0
OP_POP = 6, // pop the first element of the stack to a register, e.g.: pop r0 OP_POP = 6, ///< pop the first element of the stack to a register, e.g.: pop r0
// memory get/set from/to an address. The last argument is a size (1, 2 or 4 bytes) // memory get/set from/to an address. The last argument is a size (1, 2 or 4 bytes)
OP_STORE = 7, // copy a value from a register to a ram address located in a register, specific size e.g. : store @r4, r1, 2 (2 bytes) OP_STORE = 7, ///< copy a value from a register to a ram address located in a register, specific size e.g. : store @r4, r1, 2 (2 bytes)
OP_LOAD = 8, // copy a value from a ram address located in a register to a register, specific size e.g.: load r0, @r3, 1 (1 byte) OP_LOAD = 8, ///< copy a value from a ram address located in a register to a register, specific size e.g.: load r0, @r3, 1 (1 byte)
// arithmetic: // arithmetic:
OP_ADD = 9, // sum and store in first reg, e.g.: add r0, r2 OP_ADD = 9, ///< sum and store in first reg, e.g.: add r0, r2
OP_SUB = 10, // subtract and store in first reg, e.g.: sub r0, r2 OP_SUB = 10, ///< subtract and store in first reg, e.g.: sub r0, r2
OP_MUL = 11, // multiply and store in first reg, e.g.: mul r0, r2 OP_MUL = 11, ///< multiply and store in first reg, e.g.: mul r0, r2
OP_DIV = 12, // divide and store in first reg, remain in second, e.g.: div r0, r2 OP_DIV = 12, ///< divide and store in first reg, remain in second, e.g.: div r0, r2
OP_SHL = 13, // logical shift left, e.g.: shl r0, r1 OP_SHL = 13, ///< logical shift left, e.g.: shl r0, r1
OP_SHR = 14, // logical shift right, e.g.: shr r0, r1 OP_SHR = 14, ///< logical shift right, e.g.: shr r0, r1
OP_ISHR = 15, // arithmetic shift right (for signed values), e.g.: ishr r0, r1 OP_ISHR = 15, ///< arithmetic shift right (for signed values), e.g.: ishr r0, r1
OP_AND = 16, // and two registers and store result in the first one, e.g.: and r0, r1 OP_AND = 16, ///< and two registers and store result in the first one, e.g.: and r0, r1
OP_OR = 17, // or two registers and store result in the first one, e.g.: or r0, r1 OP_OR = 17, ///< or two registers and store result in the first one, e.g.: or r0, r1
OP_XOR = 18, // xor two registers and store result in the first one, e.g.: xor r0, r1 OP_XOR = 18, ///< xor two registers and store result in the first one, e.g.: xor r0, r1
OP_NOT = 19, // not a register and store result, e.g.: not r0 OP_NOT = 19, ///< not a register and store result, e.g.: not r0
// branching/functions // branching/functions
OP_CALL = 20, // set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00 OP_CALL = 20, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
OP_RET = 21, // return to the address of last callee (RA), e.g.: ret OP_RET = 21, ///< return to the address of last callee (RA), e.g.: ret
OP_JUMP = 22, // jump to address (can use label or address), e.g.: jump .my_label OP_JUMP = 22, ///< jump to address (can use label or address), e.g.: jump .my_label
OP_SKIPZ = 23, // skip next instruction if zero, e.g.: skipz r0 OP_SKIPZ = 23, ///< skip next instruction if zero, e.g.: skipz r0
OP_SKIPNZ = 24, // skip next instruction if not zero, e.g.: skipnz r2 OP_SKIPNZ = 24, ///< skip next instruction if not zero, e.g.: skipnz r2
INSTRUCTION_COUNT INSTRUCTION_COUNT
} chip32_instruction_t; } chip32_instruction_t;
@ -186,6 +186,10 @@ typedef struct
typedef uint8_t (*syscall_t)(uint8_t); typedef uint8_t (*syscall_t)(uint8_t);
#define SYSCALL_RET_OK 0 ///< Default state, continue execution immediately
#define SYSCALL_RET_WAIT_EV 1 ///< Sets the VM in wait for event state
typedef struct typedef struct
{ {
virtual_mem_t rom; virtual_mem_t rom;

@ -1 +1 @@
Subproject commit 3b9ae0777cd52d6037bdcc6e0b05b57c5eb8701e Subproject commit ba75bd93e1b1c5d5bc387e49f35737697d180b30

View file

@ -82,7 +82,7 @@ MainWindow::MainWindow()
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock); addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_ostHmiDock);
m_toolbar->AddDockToMenu(m_ostHmiDock->toggleViewAction(), m_ostHmiDock); m_toolbar->AddDockToMenu(m_ostHmiDock->toggleViewAction(), m_ostHmiDock);
connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [=]() { connect(m_ostHmiDock, &OstHmiDock::sigOkButton, [&]() {
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton)); QCoreApplication::postEvent(this, new VmEvent(VmEvent::evOkButton));
}); });
@ -98,16 +98,16 @@ MainWindow::MainWindow()
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock); addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, m_vmDock);
m_toolbar->AddDockToMenu(m_vmDock->toggleViewAction(), m_vmDock); m_toolbar->AddDockToMenu(m_vmDock->toggleViewAction(), m_vmDock);
connect(m_vmDock, &VmDock::sigCompile, [=]() { connect(m_vmDock, &VmDock::sigCompile, [&]() {
// m_scriptEditorDock->setScript(m_project.BuildResources()); // m_scriptEditorDock->setScript(m_project.BuildResources());
}); });
connect(m_vmDock, &VmDock::sigStepInstruction, [=]() { connect(m_vmDock, &VmDock::sigStepInstruction, [&]() {
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evStep)); QCoreApplication::postEvent(this, new VmEvent(VmEvent::evStep));
}); });
connect(m_vmDock, &VmDock::sigBuild, [=]() { connect(m_vmDock, &VmDock::sigBuild, [&]() {
buildScript(); BuildScript();
}); });
m_ramView = new MemoryViewDock("RamViewDock", "RAM"); m_ramView = new MemoryViewDock("RamViewDock", "RAM");
@ -176,7 +176,7 @@ MainWindow::MainWindow()
NewProject(); NewProject();
}); });
connect(m_toolbar, &ToolBar::sigSave, this, [=]() { connect(m_toolbar, &ToolBar::sigSave, this, [&]() {
SaveProject(); SaveProject();
}); });
@ -215,6 +215,39 @@ MainWindow::MainWindow()
CloseProject(); CloseProject();
RefreshProjectInformation(); RefreshProjectInformation();
// Prepare run timer
m_runTimer = new QTimer(this);
m_runTimer->setSingleShot(true);
connect(m_runTimer, &QTimer::timeout, this, [&]() {
if (m_dbg.run_result == VM_OK)
{
stepInstruction();
}
if (m_dbg.run_result == VM_OK)
{
// Restart timer
m_runTimer->start(100);
}
else if (m_dbg.run_result == VM_FINISHED)
{
m_dbg.running = false;
}
else if (m_dbg.run_result == VM_WAIT_EVENT)
{
m_runTimer->stop(); // just to make sure
}
});
connect(&m_model, &StoryGraphModel::sigAudioStopped, this, [&]() {
if ((m_dbg.run_result == VM_WAIT_EVENT) && (m_dbg.running))
{
// Continue execution of node
m_runTimer->start(100);
}
});
// QMetaObject::invokeMethod(this, "slotWelcome", Qt::QueuedConnection); // QMetaObject::invokeMethod(this, "slotWelcome", Qt::QueuedConnection);
} }
@ -224,8 +257,7 @@ void MainWindow::BuildAndRun()
// FIXME // FIXME
// 2. Generate the assembly code from the model // 2. Generate the assembly code from the model
std::string code = m_project.BuildResources() + "\n"; std::string code = m_model.Build();
code += m_model.Build();
// Add global functions // Add global functions
code += ReadResourceFile(":/scripts/media.asm").toStdString(); code += ReadResourceFile(":/scripts/media.asm").toStdString();
@ -235,8 +267,14 @@ void MainWindow::BuildAndRun()
m_scriptEditorDock->setScript(code.c_str()); m_scriptEditorDock->setScript(code.c_str());
// 3. Compile the assembly to machine binary // 3. Compile the assembly to machine binary
// buildScript(); BuildScript();
// 4. Run the VM code!
if (m_dbg.run_result == VM_OK)
{
m_dbg.running = true;
m_runTimer->start(100);
}
} }
@ -406,7 +444,7 @@ void MainWindow::updateAll()
m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size); m_ramView->SetMemory(m_ram_data, m_chip32_ctx.ram.size);
} }
QString MainWindow::GetFileName(uint32_t addr) QString MainWindow::GetFileNameFromMemory(uint32_t addr)
{ {
char strBuf[100]; char strBuf[100];
bool isRam = addr & 0x80000000; bool isRam = addr & 0x80000000;
@ -452,19 +490,21 @@ bool MainWindow::event(QEvent *event)
uint8_t MainWindow::Syscall(uint8_t code) uint8_t MainWindow::Syscall(uint8_t code)
{ {
uint8_t retCode = 0; uint8_t retCode = SYSCALL_RET_OK;
qDebug() << "SYSCALL: " << (int)code; qDebug() << "SYSCALL: " << (int)code;
// Media // Media
if (code == 1) if (code == 1)
{ {
// image file name address is in R0 // image file name address is in R0
QString imageFile = GetFileName(m_chip32_ctx.registers[R0]); QString imageFile = m_model.BuildFullImagePath(GetFileNameFromMemory(m_chip32_ctx.registers[R0]));
// sound file name address is in R1 // sound file name address is in R1
QString soundFile = GetFileName(m_chip32_ctx.registers[R1]); QString soundFile = m_model.BuildFullSoundPath(GetFileNameFromMemory(m_chip32_ctx.registers[R1]));
qDebug() << "Image: " << imageFile << ", Sound: " << soundFile; qDebug() << "Image: " << imageFile << ", Sound: " << soundFile;
m_ostHmiDock->SetImage(imageFile); m_ostHmiDock->SetImage(imageFile);
m_model.PlaySound(soundFile);
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
} }
// WAIT EVENT bits: // WAIT EVENT bits:
// 0: block // 0: block
@ -478,7 +518,7 @@ uint8_t MainWindow::Syscall(uint8_t code)
// Event mask is located in R0 // Event mask is located in R0
// optional timeout is located in R1 // optional timeout is located in R1
// if timeout is set to zero, wait for infinite and beyond // if timeout is set to zero, wait for infinite and beyond
retCode = 1; // set the VM in pause retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
} }
@ -491,8 +531,11 @@ void MainWindow::stepInstruction()
updateAll(); updateAll();
} }
void MainWindow::buildScript() void MainWindow::BuildScript()
{ {
m_dbg.run_result = VM_FINISHED;
m_dbg.running = false;
if (m_assembler.Parse(m_scriptEditorDock->getScript().toStdString()) == true ) if (m_assembler.Parse(m_scriptEditorDock->getScript().toStdString()) == true )
{ {
if (m_assembler.BuildBinary(m_program, m_result) == true) if (m_assembler.BuildBinary(m_program, m_result) == true)

View file

@ -13,6 +13,7 @@
#include <QLabel> #include <QLabel>
#include <QMainWindow> #include <QMainWindow>
#include <QDockWidget> #include <QDockWidget>
#include <QTimer>
#include <QtNodes/ConnectionStyle> #include <QtNodes/ConnectionStyle>
#include <QtNodes/GraphicsView> #include <QtNodes/GraphicsView>
@ -136,6 +137,7 @@ private:
GraphicsView *m_view{nullptr}; GraphicsView *m_view{nullptr};
// Qt stuff // Qt stuff
QTimer *m_runTimer{nullptr};
ToolBar *m_toolbar{nullptr}; ToolBar *m_toolbar{nullptr};
OstHmiDock *m_ostHmiDock{nullptr}; OstHmiDock *m_ostHmiDock{nullptr};
ResourcesDock *m_resourcesDock{nullptr}; ResourcesDock *m_resourcesDock{nullptr};
@ -166,12 +168,12 @@ private:
void createStatusBar(); void createStatusBar();
void SaveProject(); void SaveProject();
void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId); void DisplayNode(StoryNode *m_tree, QtNodes::NodeId parentId);
void buildScript(); void BuildScript();
void highlightNextLine(); void highlightNextLine();
void readSettings(); void readSettings();
void updateAll(); void updateAll();
uint8_t Syscall(uint8_t code); uint8_t Syscall(uint8_t code);
QString GetFileName(uint32_t addr); QString GetFileNameFromMemory(uint32_t addr);
bool event(QEvent *event); bool event(QEvent *event);
void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg); void MessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);

View file

@ -148,6 +148,9 @@
<item> <item>
<widget class="QSpinBox" name="spinBox"> <widget class="QSpinBox" name="spinBox">
<property name="minimum"> <property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>1</number> <number>1</number>
</property> </property>
</widget> </widget>

View file

@ -129,10 +129,10 @@ std::string MediaNodeModel::GenerateConstants()
} }
if (sound.size() > 0) if (sound.size() > 0)
{ {
s = StoryProject::FileToConstant(sound); s += StoryProject::FileToConstant(sound);
} }
// Generate choice table if needed (out ports > 1) // FIXME: Generate choice table if needed (out ports > 1)
return s; return s;
} }
@ -142,8 +142,8 @@ std::string MediaNodeModel::Build()
std::stringstream ss; std::stringstream ss;
ss << R"(; ---------------- )" << GetNodeTitle() << "\n"; ss << R"(; ---------------- )" << GetNodeTitle() << "\n";
std::string image = m_mediaData["image"].get<std::string>(); std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get<std::string>());
std::string sound = m_mediaData["sound"].get<std::string>(); std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get<std::string>());
if (image.size() > 0) if (image.size() > 0)
{ {
ss << "lcons r0, $" << image << "\n"; ss << "lcons r0, $" << image << "\n";
@ -161,6 +161,38 @@ std::string MediaNodeModel::Build()
{ {
ss << "lcons r1, 0\n"; ss << "lcons r1, 0\n";
} }
// Call the media executor (image, sound)
ss << "syscall 1\n";
std::unordered_set<ConnectionId> conns = m_model.allConnectionIds(getNodeId());
int nb_out_ports = 0;
for (auto & c : conns)
{
if (c.outNodeId > 0)
{
nb_out_ports++;
}
}
if (nb_out_ports == 0)
{
ss << "halt\n";
}
else
{
}
// Check output connections number
// == 0: end, generate halt
// == 1: jump directly to the other node
// > 1 : call the node choice manager
// lcons r0, $ChoiceObject
// jump .media ; no return possible, so a jump is enough
/* /*

View file

@ -26,7 +26,13 @@
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>320</width> <width>320</width>
<height>200</height> <height>240</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>240</height>
</size> </size>
</property> </property>
<property name="frameShape"> <property name="frameShape">
@ -35,6 +41,9 @@
<property name="text"> <property name="text">
<string/> <string/>
</property> </property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">

View file

@ -12,6 +12,13 @@ StoryGraphModel::StoryGraphModel(StoryProject &project)
m_player = new QMediaPlayer; m_player = new QMediaPlayer;
m_audioOutput = new QAudioOutput; m_audioOutput = new QAudioOutput;
m_player->setAudioOutput(m_audioOutput); m_player->setAudioOutput(m_audioOutput);
connect(m_player, &QMediaPlayer::playbackStateChanged, this, [&](QMediaPlayer::PlaybackState newState) {
if (newState == QMediaPlayer::PlaybackState::StoppedState) {
m_player->stop();
emit sigAudioStopped();
}
});
} }
StoryGraphModel::~StoryGraphModel() StoryGraphModel::~StoryGraphModel()
@ -173,6 +180,9 @@ QVariant StoryGraphModel::nodeData(NodeId nodeId, NodeRole role) const
result = QVariant::fromValue(w); result = QVariant::fromValue(w);
break; break;
} }
case NodeRole::Id:
result = model->getNodeId();
break;
} }
return result; return result;

View file

@ -145,6 +145,7 @@ public:
NodeId FindFirstNode() const; NodeId FindFirstNode() const;
signals: signals:
void sigChooseFile(NodeId id, const QString &type); void sigChooseFile(NodeId id, const QString &type);
void sigAudioStopped();
private: private:
StoryProject &m_project; StoryProject &m_project;

View file

@ -247,13 +247,18 @@ void StoryProject::ReplaceCharacter(std::string &theString, const std::string &t
while (found != std::string::npos); while (found != std::string::npos);
} }
std::string StoryProject::RemoveFileExtension(const std::string &FileName)
{
std::string f = GetFileName(FileName);
std::string ext = GetFileExtension(f);
EraseString(f, "." + ext);
return f;
}
std::string StoryProject::FileToConstant(const std::string &FileName) std::string StoryProject::FileToConstant(const std::string &FileName)
{ {
std::string fileName = GetFileName(FileName); std::string f = RemoveFileExtension(FileName);
std::string ext = GetFileExtension(fileName); return "$" + f + " DC8 \"" + FileName + "\", 8\r\n";
EraseString(fileName, "." + ext); // on retire l'extension du pack
return "$" + fileName + " DC8 \"" + fileName + "." + ext + "\", 8\r\n";
} }
void StoryProject::AppendResource(const Resource &res) void StoryProject::AppendResource(const Resource &res)
@ -292,24 +297,6 @@ std::string StoryProject::GetFileExtension(const std::string &fileName)
return ""; return "";
} }
std::string StoryProject::BuildResources()
{
std::stringstream chip32;
chip32 << "\tjump .entry\r\n";
for (auto & r : m_resources)
{
std::string fileName = GetFileName(r.file);
std::string ext = GetFileExtension(fileName);
EraseString(fileName, "." + ext); // on retire l'extension du pack
chip32 << "$" << fileName << " DC8 \"" << fileName + "." + ext << "\", 8\r\n";
}
chip32 << ".entry:\r\n";
return chip32.str();
}
void StoryProject::SetImageFormat(ImageFormat format) void StoryProject::SetImageFormat(ImageFormat format)
{ {

View file

@ -68,7 +68,6 @@ struct StoryProject
m_initialized = false; m_initialized = false;
} }
std::string BuildResources();
void SetImageFormat(ImageFormat format); void SetImageFormat(ImageFormat format);
void SetSoundFormat(SoundFormat format); void SetSoundFormat(SoundFormat format);
void SetDisplayFormat(int w, int h); void SetDisplayFormat(int w, int h);
@ -85,6 +84,7 @@ struct StoryProject
static std::string GetFileExtension(const std::string &FileName); static std::string GetFileExtension(const std::string &FileName);
static std::string GetFileName(const std::string &path); static std::string GetFileName(const std::string &path);
static std::string RemoveFileExtension(const std::string &FileName);
static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace); static void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
static std::string FileToConstant(const std::string &FileName); static std::string FileToConstant(const std::string &FileName);