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) {
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;
// Split the line

View file

@ -40,44 +40,44 @@ extern "C" {
typedef enum
{
// system:
OP_NOP = 0, // do nothing
OP_HALT = 1, // halt execution
OP_SYSCALL = 2, // system call handled by user-registered function, 4 arguments (R0 - R3) passed by value
OP_NOP = 0, ///< do nothing
OP_HALT = 1, ///< halt execution
OP_SYSCALL = 2, ///< system call handled by user-registered function, 4 arguments (R0 - R3) passed by value
// constants:
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
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
// 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:
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_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
// 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_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_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)
// arithmetic:
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_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_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_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_SHL = 13, // logical shift left, e.g.: shl 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_SHL = 13, ///< logical shift left, e.g.: shl 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_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_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_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_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
// branching/functions
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_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_SKIPNZ = 24, // skip next instruction if not zero, e.g.: skipnz r2
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_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_SKIPNZ = 24, ///< skip next instruction if not zero, e.g.: skipnz r2
INSTRUCTION_COUNT
} chip32_instruction_t;
@ -186,6 +186,10 @@ typedef struct
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
{
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);
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));
});
@ -98,16 +98,16 @@ MainWindow::MainWindow()
addDockWidget(Qt::DockWidgetArea::RightDockWidgetArea, 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());
});
connect(m_vmDock, &VmDock::sigStepInstruction, [=]() {
connect(m_vmDock, &VmDock::sigStepInstruction, [&]() {
QCoreApplication::postEvent(this, new VmEvent(VmEvent::evStep));
});
connect(m_vmDock, &VmDock::sigBuild, [=]() {
buildScript();
connect(m_vmDock, &VmDock::sigBuild, [&]() {
BuildScript();
});
m_ramView = new MemoryViewDock("RamViewDock", "RAM");
@ -176,7 +176,7 @@ MainWindow::MainWindow()
NewProject();
});
connect(m_toolbar, &ToolBar::sigSave, this, [=]() {
connect(m_toolbar, &ToolBar::sigSave, this, [&]() {
SaveProject();
});
@ -215,6 +215,39 @@ MainWindow::MainWindow()
CloseProject();
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);
}
@ -224,8 +257,7 @@ void MainWindow::BuildAndRun()
// FIXME
// 2. Generate the assembly code from the model
std::string code = m_project.BuildResources() + "\n";
code += m_model.Build();
std::string code = m_model.Build();
// Add global functions
code += ReadResourceFile(":/scripts/media.asm").toStdString();
@ -235,8 +267,14 @@ void MainWindow::BuildAndRun()
m_scriptEditorDock->setScript(code.c_str());
// 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);
}
QString MainWindow::GetFileName(uint32_t addr)
QString MainWindow::GetFileNameFromMemory(uint32_t addr)
{
char strBuf[100];
bool isRam = addr & 0x80000000;
@ -452,19 +490,21 @@ bool MainWindow::event(QEvent *event)
uint8_t MainWindow::Syscall(uint8_t code)
{
uint8_t retCode = 0;
uint8_t retCode = SYSCALL_RET_OK;
qDebug() << "SYSCALL: " << (int)code;
// Media
if (code == 1)
{
// 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
QString soundFile = GetFileName(m_chip32_ctx.registers[R1]);
QString soundFile = m_model.BuildFullSoundPath(GetFileNameFromMemory(m_chip32_ctx.registers[R1]));
qDebug() << "Image: " << imageFile << ", Sound: " << soundFile;
m_ostHmiDock->SetImage(imageFile);
m_model.PlaySound(soundFile);
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
}
// WAIT EVENT bits:
// 0: block
@ -478,7 +518,7 @@ uint8_t MainWindow::Syscall(uint8_t code)
// Event mask is located in R0
// optional timeout is located in R1
// 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();
}
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.BuildBinary(m_program, m_result) == true)

View file

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

View file

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

View file

@ -129,10 +129,10 @@ std::string MediaNodeModel::GenerateConstants()
}
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;
}
@ -142,8 +142,8 @@ std::string MediaNodeModel::Build()
std::stringstream ss;
ss << R"(; ---------------- )" << GetNodeTitle() << "\n";
std::string image = m_mediaData["image"].get<std::string>();
std::string sound = m_mediaData["sound"].get<std::string>();
std::string image = StoryProject::RemoveFileExtension(m_mediaData["image"].get<std::string>());
std::string sound = StoryProject::RemoveFileExtension(m_mediaData["sound"].get<std::string>());
if (image.size() > 0)
{
ss << "lcons r0, $" << image << "\n";
@ -161,6 +161,38 @@ std::string MediaNodeModel::Build()
{
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">
<size>
<width>320</width>
<height>200</height>
<height>240</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>320</width>
<height>240</height>
</size>
</property>
<property name="frameShape">
@ -35,6 +41,9 @@
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">

View file

@ -12,6 +12,13 @@ StoryGraphModel::StoryGraphModel(StoryProject &project)
m_player = new QMediaPlayer;
m_audioOutput = new QAudioOutput;
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()
@ -173,6 +180,9 @@ QVariant StoryGraphModel::nodeData(NodeId nodeId, NodeRole role) const
result = QVariant::fromValue(w);
break;
}
case NodeRole::Id:
result = model->getNodeId();
break;
}
return result;

View file

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

View file

@ -247,13 +247,18 @@ void StoryProject::ReplaceCharacter(std::string &theString, const std::string &t
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 fileName = GetFileName(FileName);
std::string ext = GetFileExtension(fileName);
EraseString(fileName, "." + ext); // on retire l'extension du pack
return "$" + fileName + " DC8 \"" + fileName + "." + ext + "\", 8\r\n";
std::string f = RemoveFileExtension(FileName);
return "$" + f + " DC8 \"" + FileName + "\", 8\r\n";
}
void StoryProject::AppendResource(const Resource &res)
@ -292,24 +297,6 @@ std::string StoryProject::GetFileExtension(const std::string &fileName)
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)
{

View file

@ -68,7 +68,6 @@ struct StoryProject
m_initialized = false;
}
std::string BuildResources();
void SetImageFormat(ImageFormat format);
void SetSoundFormat(SoundFormat format);
void SetDisplayFormat(int w, int h);
@ -85,6 +84,7 @@ struct StoryProject
static std::string GetFileExtension(const std::string &FileName);
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 std::string FileToConstant(const std::string &FileName);