HDL4SE: software engineers learn Verilog language

Posted by nadinengland on Mon, 31 Jan 2022 17:37:48 +0100

8 write custom basic units in c language

This section was originally intended to continue to learn verilog language, but the software development progress bar is too heavy to be pushed, so it can only be worn slowly. Without the cooperation of software, the learning effect is worse, so bring the later content to the front. Of course, it's also because I'm addicted to programming and games, and I don't have the mind to write materials.
This section also uses an example to illustrate how to write your own basic unit library in c language in HDL4SE, and then let verilog call it. The basic idea is to write an LCOM object in c language to implement the IHDL4SEUnit, IHDL4SEDetector and other interfaces required by the basic unit. Of course, the IDList interface is implemented by the way so that it can be added to the two-way linked list. Use verilog to describe its model, and then it can be included in other verilog code and used by instantiation.
This basic unit can be large or small. It can be a small operation unit, such as a basic multiplication and accumulation operator used in deep neural network, or a relatively large unit, such as a RISC-V core. Our example is to realize a Tetris game. The basic unit is the controller of the game console. Of course, the display of the game console can also be regarded as a basic unit. As a device, hanging on the HDL4SE simulator is actually a basic unit, but it is not called directly with verilog code. At this time, the verilog code is very simple, which is to connect the controller with the simulation environment. See the following description for the specific way.
You may feel a little opportunistic. Write a controller in C language, and then the game runs. There is almost no verilog code, just connect the game controller with the game panel. However, this is the original intention of HDL4SE project. HDL4SE can support writing the basic unit model of digital circuit in C language for several reasons:

  1. In the early stage of system design, many modules have not determined how to design, so it is inappropriate to write verilog code at this time, but it is much easier to write a prototype system in c language. Therefore, we can use c language to build the system, which can be regarded as CModel. After all, writing c is much easier than writing the RTL description of verilog.
  2. As the number of iterations of the outline design of the system increases and gradually stabilizes, some modules can be written in verilog. At this time, combining the c language model and the circuit written in verilog can verify the correctness of verilog implementation and greatly speed up the simulation speed. Some parts are even auxiliary development modules. In fact, they don't need to be written into RTL code in the end.
  3. Many test inputs that are not suitable for writing in RTL can be written in c language, which can simplify the complexity of the system. For example, if you want to make a verilog code for deep neural network recognition, if you want to read the picture data directly from the JPG file, you can write a JPEG file decoding module in c language and hang it in the system for simulation. Otherwise, it's really difficult to write in verilog RTL, which can't be explained in detail here, You know what you've done. Even if it is written, the module will not run quickly, which takes up a lot of simulation time. Generally, FPGA development or ASIC development projects have to use software to decode JPEG files and convert them into text files that verilog can read directly, and then read them with verilog code. In fact, it is very inconvenient.
  4. Some interactive contents can be added in the simulation process, such as GPU development. You can immediately see the drawing results, do signal processing, directly see the signal processing results (for example, directly hang an FFT analysis module in the system and dynamically display the spectrum distribution), and play games. At least you can play. This is much better than using pure verilog RTL to compile and play simulation, and then analyzing the output signal waveform or output file. Of course, it directly lowers the force of those who make digital circuits. Since then, software engineers have dominated the world, digital circuit? Just software development.
  5. The device driver software and hardware can be simulated together, so that the driver software can intervene in the system design at an early stage to reduce the friction between software and hardware design. I won't say much. Those who have done similar projects know the benefits.
    Let's start with an example of Tetris game, then introduce the c language implementation of the basic unit library, and finally connect them in HDL4SE simulator with verilog code to form a practical game. Later, with the in-depth study and the improvement of compiler software, we will gradually replace the part implemented in c language with verilog, and finally form a Tetris game written in verilog. Burned in FPGA, it can be made into a game console and a special ASIC chip, which can be used to make a game console.

8.1 Tetris game

Tetris looks like this:

The bottom of the game is blocked by a solid panel. Of course, some of them have been blocked by a solid panel or an empty panel. Of course, some of them have been blocked by a solid panel. Some of them may have the same color from the bottom, At this time, this group of squares becomes a part of the panel. You can control the left and right movement of the square group through the left and right keys, or press the rotation key (our implementation uses the up key) to rotate the square. Of course, if the position has been adjusted well, you can use the down key to accelerate the falling of the square group. If the squares in the panel form a line with all solid squares horizontally, this line will disappear. The squares above the panel will move down one line, and the empty space can be filled with squares again. Of course, since a set of squares is 4x4 at most, it is possible that at most four lines at a time become solid lines and disappear at the same time. If there is no place for the falling blocks, then GAME OVER. Here we choose to restart automatically.
On the top right is the structure of the next block group that will fall behind. There are three lines of words below. The line starting from L represents how many solid lines disappear in total. The line starting from P represents the current speed. In our system, the speed starts from 0 – 6000, and the line starting from C represents the score. Our scoring rule is:
At the beginning of 200 points, one point will be deducted for the left and right moving block group, one point will be deducted for the rotating block group, two points will be added for accelerating downward, 10 points will be added for eliminating a single line, 40 points will be obtained for eliminating two lines at a time, 160 points will be obtained for eliminating three lines at a time, and 640 points will be obtained for eliminating four lines at a time. The current speed is the score divided by 10. The higher the score, the faster the speed. If the score is zero, start from the beginning, and if the score exceeds the display range, pass the customs.
I may be tired of talking so much. In fact, the game is still very simple. If you want to make such a game, it is actually divided into two parts, one is the panel of the game and the other is the control module of the game. The game panel should be able to dynamically display the content of the game, and also receive the key information to the control code. The control module controls the process of the game and the displayed content.

8.2 display panel

The display panel provides a writable address area similar to frame memory. Our display panel has 16x24 square positions, a 4x4 next block shape, and three integers represent the number of elimination lines, speed and score. Each square has 16 colors, so each square is represented by 4 bits and each of the three integers is represented by a 32-bit integer. Thus, the access address of the display panel is defined as follows (offset address):

Offset addressexplain
0x00Read only indicates the status of the keyboard. The lower four digits are valid. They respectively indicate the status of the right, left, lower and upper keys on the keyboard,
1 indicates that the key is pressed, and 0 indicates that it is not pressed.
0x10–0xcfEach 32-bit represents the color of 8 blocks, with a total of 16x24 blocks, accounting for 1536 bits and 192 8-bit byte addresses.
0xe0,0xe4A total of 8 bytes, 64 bits, storing the color of the next block group (4x4 blocks).
0xf0score
0xf4Number of rows eliminated
0xf8speed

According to the simulator design of HDL4SE, the display panel is hung in the simulator as a device. Like the general verilog module, the LCOM interface IHDL4SEUnit shall be realized, and IHDL4SEDetector can be realized optionally.
The following is the eight part essay of LCOM. Software engineers are relatively free. Everyone wants to write their own style of code, which is a disaster in software engineering. Therefore, LCOM stipulates the code writing specifications, including function naming, interface function name to be implemented, parameter name and type, return value, several transactions required to implement an interface, etc, Ensure that everyone can be connected together after implementation, and can understand the code structure of each other. Here is commonly known as LCOM eight part essay.

8.2.1 LCOM eight shares – from

Define the interface IID and interface, a class CLSID. The display panel object does not define a new interface, so there is only one CLSID. For ease of use, it also defines a function for fast object generation. Of course, it also defines the size of the panel, with 24 lines and 16 blocks per line. This part is called for external modules, so it is relatively standard and only describes what objects to make.

DEFINE_GUID(CLSID_TERRISDEVICE, 0x81de7969, 0xf783, 0x4023, 0xbd, 0xe6, 0xf8, 0x15, 0xf8, 0xd5, 0x9c, 0x5);
DEFINE_GUID(PARAMID_BASEADDR,0x55faed75, 0xefbe, 0x461d, 0x81, 0x99, 0x90, 0x58, 0x88, 0xab, 0x47, 0x93);

IHDL4SEUnit** guiCreate(unsigned int baseaddr, const char* name);

#define XCOUNT  16
#define YCOUNT  24

8.2.2 LCOM eight shares – commitment

Then define the data structure required for class implementation and the implementation definition of relevant interfaces, and declare the functions and data defined by the interface, most of which are the specified actions of LCOM octets. This part describes the interfaces to be implemented. Of course, it also defines some implementation related data, leaving some foreshadowing for the subsequent transfer. It can be regarded as a connecting part:

#define WIDTH  600
#define HEIGHT 800

/*
terris panel & keyboard
*/
typedef struct _sTerrisDevice {
	OBJECT_HEADER
	INTERFACE_DECLARE(IHDL4SEUnit)
	HDL4SEUNIT_VARDECLARE
	INTERFACE_DECLARE(IHDL4SEDetector)
	HDL4SEDETECTOR_VARDECLARE
	DLIST_VARDECLARE
		
	IHDL4SEModule** parent;
	char* name;
	GLFWwindow* window;
	int width;
	int height;
	int count;
	unsigned int baseaddr;
	unsigned int panelvalue[YCOUNT][XCOUNT]; /* 00000010--000000cc*/
	unsigned int nextblock[4][4];           /* 000000e0, 000000e4*/
	unsigned int score;                      /* 000000f0*/
	unsigned int level;                      /* 000000f4*/
	unsigned int speed;                      /* 000000f8*/
	
	unsigned int keypressed;                 /* 00000000 */

	unsigned int portdata[9];

	int wRead;              /* Last cycle read signal */
	unsigned int bReadAddr; /* Determines whether the current cycle responds */
	int wRead_cur;
	unsigned int bReadAddr_cur;
	 
	/*
	8 input ports of slave device
		0.wClk
		1.nwReset
		2.wWrite
		3.bWriteAddr
		4.bWriteData
		5.bWriteMask
		6.wRead
		7.bReadAddr
		8.bReadData
	*/
	IHDL4SEUnit** fromunit[9];
	int           fromindex[9];

}sTerrisDevice;

OBJECT_FUNCDECLARE(terrisdevice, CLSID_TERRISDEVICE);
HDL4SEUNIT_FUNCDECLARE(terrisdevice, CLSID_TERRISDEVICE, sTerrisDevice);
HDL4SEDETECTOR_FUNCDECLARE(terrisdevice, CLSID_TERRISDEVICE, sTerrisDevice);
DLIST_FUNCIMPL(terrisdevice, CLSID_TERRISDEVICE, sTerrisDevice);

OBJECT_FUNCIMPL(terrisdevice, sTerrisDevice, CLSID_TERRISDEVICE);


QUERYINTERFACE_BEGIN(terrisdevice, CLSID_TERRISDEVICE)
QUERYINTERFACE_ITEM(IID_HDL4SEUNIT, IHDL4SEUnit, sTerrisDevice)
QUERYINTERFACE_ITEM(IID_HDL4SEDETECTOR, IHDL4SEDetector, sTerrisDevice)
QUERYINTERFACE_ITEM(IID_DLIST, IDList, sTerrisDevice)
QUERYINTERFACE_END

8.2.3 LCOM eight shares - transfer

This part is the core of the eight part essay. After the topic is opened, it is necessary to realize each function. The core point of transfer is to realize the function of each interface one by one according to the requirements of the topic. This part includes two small parts. One is to implement some general functions of LCOM objects. Turn to:

static const char* terrisdeviceModuleInfo()
{
	return "1.0.0-20210610.0829 Terris Panel";
}

static int keypressed = 0;

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
	keypressed = 0;
	if (action != GLFW_PRESS)
		return;
	if (key >= GLFW_KEY_RIGHT && key <= GLFW_KEY_UP) {
		key -= GLFW_KEY_RIGHT;
		keypressed = (1 << key);
	}
}

static int terrisdeviceCreate(const PARAMITEM* pParams, int paramcount, HOBJECT* pObject)
{
	sTerrisDevice* pobj;
	int i;
	pobj = (sTerrisDevice*)malloc(sizeof(sTerrisDevice));
	if (pobj == NULL)
		return -1;
	*pObject = 0;
	HDL4SEUNIT_VARINIT(pobj, CLSID_TERRISDEVICE);
	INTERFACE_INIT(IHDL4SEUnit, pobj, terrisdevice, hdl4se_unit);
	INTERFACE_INIT(IHDL4SEDetector, pobj, terrisdevice, hdl4se_detector);
	DLIST_VARINIT(pobj, terrisdevice);

	pobj->baseaddr = 0xF0000000;
	pobj->name = NULL;
	pobj->parent = NULL;

	for (i = 0; i < paramcount; i++) {
		if (pParams[i].name == PARAMID_HDL4SE_UNIT_NAME) {
			if (pobj->name != NULL)
				free(pobj->name);
			pobj->name = strdup(pParams[i].pvalue);
		}
		else if (pParams[i].name == PARAMID_BASEADDR) {
			pobj->baseaddr = pParams[i].i32value;
		}
	}

	for (i = 0; i < 9; i++) {
		pobj->fromunit[i] = NULL;
	}

	memset(pobj->panelvalue, 0, sizeof(pobj->panelvalue));
	pobj->panelvalue[12][7] = 4;
	pobj->panelvalue[12][8] = 9;
	pobj->panelvalue[12][9] = 3;
	pobj->panelvalue[11][9] = 13;

	if (!glfwInit())
		return -1;
	pobj->width = WIDTH;
	pobj->height = HEIGHT;
	pobj->wRead = 0;
	pobj->count = 0;
	pobj->wRead_cur = 0;
	pobj->window = glfwCreateWindow(WIDTH, HEIGHT, "TERRIS", NULL, NULL);
	if (!pobj->window)
	{
		glfwTerminate();
		return -2;
	}
	pobj->keypressed = 0;

	glfwSetWindowAspectRatio(pobj->window, WIDTH, HEIGHT);
	
	glfwSetKeyCallback(pobj->window, key_callback);
	glfwMakeContextCurrent(pobj->window);
	gladLoadGL(glfwGetProcAddress);
	glfwSwapInterval(1);

	glfwGetFramebufferSize(pobj->window, &pobj->width, &pobj->height);

	/* Returns the generated object */
	OBJECT_RETURN_GEN(terrisdevice, pobj, pObject, CLSID_TERRISDEVICE);
	return EIID_OK;
}

static void terrisdeviceDestroy(HOBJECT object)
{
	sTerrisDevice* pobj;
	int i;
	pobj = (sTerrisDevice*)objectThis(object);
	if (pobj->name != NULL)
		free(pobj->name);
	for (i = 0; i < 9; i++) {
		objectRelease(pobj->fromunit[i]);
	}
	glfwTerminate();
	memset(pobj, 0, sizeof(sTerrisDevice));
	free(pobj);
}

static int terrisdeviceValid(HOBJECT object)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	return 1;
}

The second part is the implementation of each interface function, and then turn to:

static int terrisdevice_hdl4se_unit_Connect(HOBJECT object, int index, HOBJECT from, int fromindex)
{
	sTerrisDevice* pobj;
	IHDL4SEUnit** unit = NULL;
	pobj = (sTerrisDevice*)objectThis(object);
	if (index >= 0 && index < 9) {
		if (0 == objectQueryInterface(from, IID_HDL4SEUNIT, (void**)&unit)) {
			pobj->fromunit[index] = unit;
			pobj->fromindex[index] = fromindex;
			return 0;
		}
		else {
			return -2;
		}
	}
	return -1;
}

static int terrisdevice_hdl4se_unit_ConnectPart(HOBJECT object, int index, int start, int width, HOBJECT from, int fromindex)
{
	/* Partial connections are not supported */
	return 0;
}

#define isMyAddr(addr) ((addr & 0xFFFFFF00) == (pobj->baseaddr & 0xFFFFFF00))

static int terrisdevice_hdl4se_unit_GetValue(HOBJECT object, int index, int width, IBigNumber** value)
{
	int i;
	int sel;
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	if (index != 8) /* Only respond to 8 ReadData port */
		return -1;
	if (pobj->wRead == 0)
		return -2; /* No read command in the last cycle, no response, high resistance state */
	if (pobj->bReadAddr == 0) {
		/* The offset address is 0, and the state of the key is read */
		objectCall1(value, AssignInt32, pobj->keypressed);
		pobj->portdata[7] = pobj->keypressed;
		return 0;
	}
	return -2;
}

static int terrisdevice_hdl4se_unit_ClkTick(HOBJECT object)
{
	int i, j;
	int reset;
	sTerrisDevice* pobj;
	IBigNumber** temp = bigintegerCreate(32);
	pobj = (sTerrisDevice*)objectThis(object);
	pobj->keypressed = keypressed;

	for (i = 0; i < 9; i++) {
		if (0 == objectCall3(pobj->fromunit[i], GetValue, i, 32, temp)) {
			objectCall1(temp, GetInt32, &pobj->portdata[i]);
		}
	}
	objectRelease(temp);
	
	/* Read the nwReset signal to see if it is reset */
	reset = pobj->portdata[0] & 1;
	if (reset == 0) {
		pobj->keypressed = 0;
	}
	

	/* Read and write commands to see whether to write */
	if (reset != 0) {
		int wWrite = 0;
		unsigned int bWriteAddr = 0xFFFFFFFF;
		int wRead = 0;
		unsigned int bReadAddr = 0xFFFFFFFF;
		wWrite = pobj->portdata[2] & 1;
		bWriteAddr = pobj->portdata[3];
		if (wWrite && isMyAddr(bWriteAddr)) {
			bWriteAddr &= 0xff;
			unsigned int data = pobj->portdata[4];
			if (bWriteAddr >= 0x10 && bWriteAddr < 0xd0) {
				/*Each address updates 8 blocks, 4 bits per block, and each line
				No more than 16 blocks, so two words update a line*/
				int yindex, xindex;
				yindex = (bWriteAddr - 0x10);
				yindex /= 4;
				if (yindex / 2 < YCOUNT) {
					int i;
					for (i = 0; i < 8; i++) {
						int xindex;
						xindex = i + (yindex & 1) * 8;
						if (xindex < XCOUNT) {
							pobj->panelvalue[yindex / 2][xindex] = data & 0xf;
							data >>= 4;
						}
					}
				}
			}
			else if (bWriteAddr == 0xe0 || bWriteAddr == 0xe4) {
				int offs;
				offs = (bWriteAddr == 0xe0) ? 0 : 2;
				pobj->nextblock[0 + offs][0] = data & 0xf; data >>= 4;
				pobj->nextblock[0 + offs][1] = data & 0xf; data >>= 4;
				pobj->nextblock[0 + offs][2] = data & 0xf; data >>= 4;
				pobj->nextblock[0 + offs][3] = data & 0xf; data >>= 4;
				pobj->nextblock[1 + offs][0] = data & 0xf; data >>= 4;
				pobj->nextblock[1 + offs][1] = data & 0xf; data >>= 4;
				pobj->nextblock[1 + offs][2] = data & 0xf; data >>= 4;
				pobj->nextblock[1 + offs][3] = data & 0xf; data >>= 4;
			}
			else if (bWriteAddr == 0xf0) {
				pobj->score = data;
			}
			else if (bWriteAddr == 0xf4) {
				pobj->level = data;
			}
			else if (bWriteAddr == 0xf8) {
				pobj->speed = data;
			}
		}
		wRead = pobj->portdata[6] & 0x1;
		bReadAddr = pobj->portdata[7];
		if (wRead && isMyAddr(bReadAddr)) {
			bReadAddr &= 0x1f;
			if (bReadAddr == 0x0) {
				pobj->wRead_cur = 1;
				pobj->bReadAddr_cur = 0;
			}
		}
	}

	return 0;
}

static const float blockcolor[16][3] = {
	{0.0f, 0.0f, 0.0f},
	{0.1f, 0.2f, 0.1f},
	{0.0f, 0.0f, 1.0f},
	{0.0f, 1.0f, 0.0f},
	{0.0f, 1.0f, 1.0f},
	{1.0f, 0.0f, 0.0f},
	{1.0f, 0.0f, 1.0f},
	{1.0f, 1.0f, 0.0f},
	{1.0f, 1.0f, 1.0f},
	{0.0f, 0.0f, 0.7f},
	{0.0f, 0.7f, 0.0f},
	{0.0f, 0.7f, 0.7f},
	{0.7f, 0.0f, 0.0f},
	{0.7f, 0.0f, 0.7f},
	{0.7f, 0.7f, 0.0f},
	{0.7f, 0.7f, 0.7f},
};

#define xofblock(ii) (-1.0f + (2.0f / (XCOUNT + 4)) * ii)
#define yofblock(jj) (-1.0f + (2.0f / YCOUNT) * jj)
#define wofblock() (2.0f / (XCOUNT + 4))
#define hofblock() (2.0f / YCOUNT)

static int terrisdevice_Draw_Block(int x, int y, int c)
{
	glColor3fv(blockcolor[c]);
	glBegin(GL_TRIANGLE_FAN);
	glVertex2f(xofblock(x), yofblock(y));
	glVertex2f(xofblock(x), yofblock(y) + hofblock());
	glVertex2f(xofblock(x) + wofblock(), yofblock(y) + hofblock());
	glVertex2f(xofblock(x) + wofblock(), yofblock(y));
	glEnd();
	glLineWidth(2.0f);
	glColor3f(0.0f, 0.10f, 0.25f);
	glBegin(GL_LINE_STRIP);
	glVertex2f(xofblock(x), yofblock(y));
	glVertex2f(xofblock(x), yofblock(y) + hofblock());
	glVertex2f(xofblock(x) + wofblock(), yofblock(y) + hofblock());
	glVertex2f(xofblock(x) + wofblock(), yofblock(y));
	glVertex2f(xofblock(x), yofblock(y));
	glEnd();

}

#include "digitdisp.h"

static int terrisdevice_Render(sTerrisDevice* pobj)
{
	int i, j;
	glfwGetFramebufferSize(pobj->window, &pobj->width, &pobj->height);
	glViewport(0, 0, pobj->width, pobj->height);
	glClearColor(0.0f, 0.10f, 0.25f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	/* pannel */
	for (i = 0; i < XCOUNT; i++) {
		for (j = 0; j < YCOUNT; j++) {
			terrisdevice_Draw_Block(i, j, pobj->panelvalue[j][i] & 0xf);
		}
	}

	/* current block */
	for (i = 0; i < 4; i++) {
		for (j = 0; j < 4; j++) {
			terrisdevice_Draw_Block(i + XCOUNT, 20 - j, pobj->nextblock[j][i] & 0xf);
		}
	}

#define LEFTPOS  0.57f
	/* levels */
	glColor3f(0.0f, 1.0f, 0.0f);
	drawsym(SYM_L, LEFTPOS, 0.30f, 0.01f, 0.015f);
	drawnum(pobj->level, 7, LEFTPOS + 0.04f, 0.30f, 0.01f, 0.015f);
	drawsym(SYM_P, LEFTPOS, 0.20f, 0.01f, 0.015f);
	drawnum(pobj->speed, 5, LEFTPOS + 0.04f, 0.20f, 0.01f, 0.015f);
	drawsym(SYM_C, LEFTPOS, 0.10f, 0.01f, 0.015f);
	drawnum(pobj->score, 8, LEFTPOS + 0.04f, 0.10f, 0.01f, 0.015f);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	return 1;
}

int StopRunning();

static int terrisdevice_hdl4se_unit_Setup(HOBJECT object)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	/*Read signal and read address register one beat*/
	pobj->wRead = pobj->wRead_cur;
	pobj->bReadAddr = pobj->bReadAddr_cur;
	pobj->count++;
	/* New drawing every 256 cycles */
	if ((pobj->count & 0xff) == 0) {
		if (terrisdevice_Render(pobj)) {
			/* Swap buffers */
			glfwSwapBuffers(pobj->window);
		}
	}

	glfwPollEvents();

	/* Check if we are still running */
	if (glfwWindowShouldClose(pobj->window))
		StopRunning();
	return 0;
}

static int terrisdevice_hdl4se_detector_GetName(HOBJECT object, const char** pname)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	*pname = pobj->name;
	return 0;
}

static int terrisdevice_hdl4se_detector_GetSignalCount(HOBJECT object)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	return 1;
}

static const char* terrisdevice_dataname[10] = {
	"wClk",
	"nwReset",
	"wWrite",
	"bWriteAddr",
	"bWriteData",
	"bWriteMask",
	"wRead",
	"bReadAddr",
	"bReadData",
	"BASEADDR"
};

static const char* terrisdevice_datawidth[10] ={
	1, 1, 1, 32, 32, 4, 1, 32, 32, 32
};

static int terrisdevice_hdl4se_detector_GetSignalInfo(HOBJECT object, int index, const char** pname, int * width)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	if (index < 0 || index > 9)
		return -1;
	*pname = terrisdevice_dataname[index];
	*width = terrisdevice_datawidth[index];
	return 0;
}

static int terrisdevice_hdl4se_detector_GetSignalValue(HOBJECT object, int index, IBigNumber** value)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	if (index < 0 || index > 9)
		return -1;
	objectCall2(value, SetWidth, 32, 0);
	if (index < 8)
		objectCall1(value, AssignInt32, pobj->portdata[index]);
	if (index == 8)
		objectCall1(value, AssignInt32, pobj->baseaddr);
	objectCall2(value, SetWidth, terrisdevice_datawidth[index], 0);
	return 0;
}

static int terrisdevice_hdl4se_detector_GetUnitCount(HOBJECT object)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	return 0;
}

static int terrisdevice_hdl4se_detector_GetUnit(HOBJECT object, int index, HOBJECT* unit)
{
	sTerrisDevice* pobj;
	pobj = (sTerrisDevice*)objectThis(object);
	return 0;
}

8.2.3 LCOM eight shares – combined

Well, the object has been implemented. Now let's implement the object generation interface that can be called externally. In fact, if this part is not implemented, the LCOM library will provide a standard generation function. However, there is such an interface, which is much more convenient to use, and programmers can't tie their hands and feet for specification.

IHDL4SEUnit** guiCreate(unsigned int baseaddr, const char * name)
{
	int ret;
	IHDL4SEUnit** gui;
	PARAMITEM param[2];
	IHDL4SEUnit** result = NULL;
	param[0].name = PARAMID_BASEADDR;
	param[0].i32value = baseaddr;
	param[1].name = PARAMID_HDL4SE_UNIT_NAME;
	param[1].pvalue = name;
	A_u_t_o_registor_terrisdevice();
	ret = objectCreateEx(CLSID_TERRISDEVICE, param, 2, IID_HDL4SEUNIT, (const void**)&gui);
	return gui;
}

8.3 controller

The controller of Tetris game system is a little more complex. We are divided into two parts, one is the implementation of game logic, and the other is LCOM octet.

8.3.1 logic of Tetris game

This part is also easy to talk about. Just go directly to the code. We communicate directly in c language:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "time.h"

enum TERRIS_KEY {
	TK_LEFT,
	TK_DOWN,
	TK_RIGHT,
	TK_TURNLEFT,
	TK_TURNRIGHT
};

#define PANELWIDTH	XCOUNT
#define PANELHEIGHT YCOUNT
#define PANELGAP    0
#define BLOCKSIZE 4

#define BLOCKSHAPES (sizeof(blockshape) / (sizeof(unsigned long) * 16))


/*Block shape. You can add or delete blocks in it,
You can also design new block shapes*/
const unsigned long blockshape[][4][4] = 
{
	{
		{0,0,0,0},
		{0,1,1,0},
		{0,1,1,0},
		{0,0,0,0},
	},
	{
		{0,0,0,0},
		{0,1,1,0},
		{0,0,1,0},
		{0,0,0,0},
	},
	{
		{0,0,0,0},
		{0,1,0,0},
		{0,0,1,0},
		{0,0,0,0},
	},
/* hard	*/
	{
		{0,1,0,0},
		{1,1,1,0},
		{0,1,0,0},
		{0,0,0,0},
	},
	
	{
		{0,1,0,0},
		{0,1,0,0},
		{0,1,0,0},
		{0,1,0,0},
	},
/*	hard*/
	{
		{0,0,0,0},
		{1,0,0,1},
		{1,1,1,1},
		{0,0,0,0},
	},

/*	metamorphosis*/	
	{
		{1,0,1,0},
		{0,1,0,0},
		{1,0,1,0},
		{0,0,0,0},
	},

	{
		{0,1,0,0},
		{0,1,1,0},
		{0,0,1,0},
		{0,0,0,0},
	},
	{
		{0,0,1,0},
		{0,1,1,0},
		{0,1,0,0},
		{0,0,0,0},
	},
	{
		{0,0,0,0},
		{0,1,0,0},
		{1,1,1,0},
		{0,0,0,0},
	},
	{
		{0,0,0,0},
		{0,1,0,0},
		{0,0,0,0},
		{0,0,0,0},
	},
	{
		{0,1,1,0},
		{0,0,1,0},
		{0,0,1,0},
		{0,0,0,0},
	},
	{
		{0,1,1,0},
		{0,1,0,0},
		{0,1,0,0},
		{0,0,0,0},
	},
};


typedef struct tagTerrisBlock
{
	int posx, posy;
	unsigned long subblock[BLOCKSIZE][BLOCKSIZE];
}TerrisBlock;

#define MAXSPPED  6000
static int gameScore = 0;
static int currentspeed = MAXSPPED;
static int gameLevel = 0;
static int currenttick = 0;
static TerrisBlock	  currentblock, nextblock;
static unsigned long terrisPanel[PANELHEIGHT][PANELWIDTH];

static int terrisBlockCanSetTo(TerrisBlock * pBlock, int x, int y);
static int terrisGenNewBlock(TerrisBlock * pBlock);

static void terrisInit()
{
	int i, j;
	/*Initialize and start*/
	srand( (unsigned)time( NULL ) );
	memset((char *)terrisPanel, 0, sizeof(terrisPanel));
	currentspeed = MAXSPPED;
	currenttick = 0;
	gameScore = 200;
	gameLevel = 0;

	/*edge*/
	for (i = 0;i<PANELGAP;i++)
	{
		for (j = 0;j<PANELHEIGHT;j++)
		{
			terrisPanel[j][i] = 1;
			terrisPanel[j][i + PANELWIDTH - PANELGAP] = 1;
		}
	}
	for (i = 0;i<PANELGAP;i++)
	{
		for (j = 0;j<PANELWIDTH;j++)
			terrisPanel[PANELHEIGHT - PANELGAP + i][j] = 1;
	}
	terrisGenNewBlock(&currentblock);
	terrisGenNewBlock(&nextblock);
}

static void terrisBlockRotate(TerrisBlock * pBlock, int dir)
{
	/*dir 0:Clockwise, otherwise counterclockwise*/
	int i, j;
	unsigned long paneltemp[BLOCKSIZE][BLOCKSIZE];
	for (i = 0;i<BLOCKSIZE;i++)
	for (j = 0;j<BLOCKSIZE;j++)
	{
		if (dir==0)
			paneltemp[i][j] = pBlock->subblock[BLOCKSIZE-1-j][i];
		else
			paneltemp[i][j] = pBlock->subblock[j][BLOCKSIZE-1-i];
	}
	memcpy(pBlock->subblock, paneltemp, sizeof(paneltemp));
}

static int terrisBlockCanSetTo(TerrisBlock * pBlock, int x, int y)
{
	int i;
	int j;
	for (i = 0;i<BLOCKSIZE;i++)
	for (j = 0;j<BLOCKSIZE;j++)
	{
		if (pBlock->subblock[i][j] != 0)
		{
			int xx, yy;
			xx = x - BLOCKSIZE / 2 + j;
			yy = y - BLOCKSIZE / 2 + i;
			if (yy < 0)
				continue;
			if (yy >= PANELHEIGHT)
				return 0;
			if (xx < 0)
				return 0;
			if (xx >= PANELWIDTH)
				return 0;
			if (terrisPanel[yy][xx] != 0)
				return 0;
		}
	}
	return 1;
}

static int terrisCheckLine()
{
	int i;
	int j;
	int totallines;
	totallines = 0;
	for (i = 0;i<PANELHEIGHT - PANELGAP;i++)
	{
		int removeit;
		removeit = 1;
		for (j = PANELGAP;j<PANELWIDTH-PANELGAP;j++)
		{
			if (terrisPanel[i][j] == 0)
			{
				removeit = 0;
				break;
			}
		}
		if (removeit)
		{
			int k;
			for (k = i;k>0;k--)
			{
				for (j = 0;j<PANELWIDTH;j++)
					terrisPanel[k][j] = terrisPanel[k-1][j];
			}
			for (j = PANELGAP;j<PANELWIDTH-PANELGAP;j++)
				terrisPanel[0][j] = 0;
			
			totallines ++;
		}
	}
	gameLevel += totallines;
	if (totallines == 1)
		gameScore += 10;
	if (totallines == 2)
		gameScore += 40;
	if (totallines == 3)
		gameScore += 160;
	if (totallines == 4)
		gameScore += 640;
	currentspeed = MAXSPPED - gameScore / 10;
	if (currentspeed < 1)
		currentspeed = 1;
	return 0;
}

static int terrisGenNewBlock(TerrisBlock * pBlock)
{
	int i;
	int j;
	int shape;
	memset((char *)pBlock, 0, sizeof(TerrisBlock));
	pBlock->posx = PANELWIDTH / 2;
	pBlock->posy = 0;
	shape = rand() % BLOCKSHAPES;
	for (i = 0;i<BLOCKSIZE;i++)
	for (j = 0;j<BLOCKSIZE;j++)
	{
		unsigned long color;
		if (blockshape[shape][i][j] == 0)
			color = 0;
		else
			color = (rand() % 14) + 2;
		pBlock->subblock[i][j] = color;
	}
	return terrisBlockCanSetTo(pBlock, pBlock->posx, pBlock->posy);
}

static void terrisDown()
{		
	if (terrisBlockCanSetTo(&currentblock, currentblock.posx, currentblock.posy+1))
	{
		currentblock.posy ++;
	}
	else
	{
		int i;
		int j;
		for (i = 0;i<BLOCKSIZE;i++)
		{
			for (j = 0;j<BLOCKSIZE;j++)
			{
				if (currentblock.subblock[i][j] != 0)
				{
					int xx, yy;
					xx = currentblock.posx - BLOCKSIZE / 2 + j;
					yy = currentblock.posy - BLOCKSIZE / 2 + i;
					if (yy < 0)
						continue;
					if (yy >= PANELHEIGHT)
						continue;
					if (xx < 0)
						continue;
					if (xx >= PANELWIDTH)
						continue;
					if (terrisPanel[yy][xx] != 0)
						continue;
					terrisPanel[yy][xx] = currentblock.subblock[i][j];
				}
			}
		}
		terrisCheckLine();
		memcpy(&currentblock, &nextblock, sizeof(currentblock));
		if (terrisGenNewBlock(&nextblock) == 0)
		{
			terrisInit();
		}
	}
}

static int terrisTick()
{
	currenttick++;
	if ((currenttick % currentspeed) == 0)
	{
		terrisDown();
		return 1;
	}
	return 0;
}

static void terrisPressKey(int key)
{
	switch (key)
	{
	case TK_LEFT:/*left*/
		if (terrisBlockCanSetTo(&currentblock, currentblock.posx - 1, currentblock.posy))
			currentblock.posx--;
		gameScore--;
		break;
	case TK_DOWN:/*down*/
		gameScore += 2;
		terrisDown();
		break;
	case TK_RIGHT:/*right*/
		if (terrisBlockCanSetTo(&currentblock, currentblock.posx + 1, currentblock.posy))
			currentblock.posx++;
		gameScore--;
		break;
	case TK_TURNLEFT:/*Counterclockwise 1*/
		terrisBlockRotate(&currentblock, 1);
		if (terrisBlockCanSetTo(&currentblock, currentblock.posx, currentblock.posy)==0)
			terrisBlockRotate(&currentblock, 0);
		gameScore--;
		break;
	case TK_TURNRIGHT:/*Clockwise 0*/
		terrisBlockRotate(&currentblock, 0);
		if (terrisBlockCanSetTo(&currentblock, currentblock.posx, currentblock.posy)==0)
			terrisBlockRotate(&currentblock, 1);
		gameScore--;
		break;
	}
	if (gameScore < 0)
		terrisInit();
}

8.3.2 the controller is the basic unit of HDL4SE

As a basic unit, it is also an LCOM octet. I won't talk about it in detail here. I'll post some codes for communication. The function of IHDL4SEDetector is not actually realized, which needs to be discussed in the future.

typedef struct _sTerrisCtrl {
	OBJECT_HEADER
	INTERFACE_DECLARE(IHDL4SEUnit)
	HDL4SEUNIT_VARDECLARE
	INTERFACE_DECLARE(IHDL4SEDetector)
	HDL4SEDETECTOR_VARDECLARE
	DLIST_VARDECLARE
	
	IHDL4SEModule** parent;
	char* name;

	IHDL4SEUnit** readdata_unit;
	int           readdata_index;
	IBigNumber**  readdata;
	IBigNumber**  lastreaddata;

	unsigned int writeaddr;
	unsigned int writedata;

	int currentline;

}sTerrisCtrl;

OBJECT_FUNCDECLARE(terrisctrl, CLSID_TERRISCTRL);
HDL4SEUNIT_FUNCDECLARE(terrisctrl, CLSID_TERRISCTRL, sTerrisCtrl);
HDL4SEDETECTOR_FUNCDECLARE(terrisctrl, CLSID_TERRISCTRL, sTerrisCtrl);
DLIST_FUNCIMPL(terrisctrl, CLSID_TERRISCTRL, sTerrisCtrl);

OBJECT_FUNCIMPL(terrisctrl, sTerrisCtrl, CLSID_TERRISCTRL);


QUERYINTERFACE_BEGIN(terrisctrl, CLSID_TERRISCTRL)
QUERYINTERFACE_ITEM(IID_HDL4SEUNIT, IHDL4SEUnit, sTerrisCtrl)
QUERYINTERFACE_ITEM(IID_HDL4SEDETECTOR, IHDL4SEDetector, sTerrisCtrl)
QUERYINTERFACE_ITEM(IID_DLIST, IDList, sTerrisCtrl)
QUERYINTERFACE_END

static const char* terrisctrlModuleInfo()
{
	return "1.0.0-20210610.1806 Terris Controller";
}

static int terrisctrlCreate(const PARAMITEM* pParams, int paramcount, HOBJECT* pObject)
{
	sTerrisCtrl* pobj;
	int i;
	pobj = (sTerrisCtrl*)malloc(sizeof(sTerrisCtrl));
	if (pobj == NULL)
		return -1;
	*pObject = 0;
	HDL4SEUNIT_VARINIT(pobj, CLSID_TERRISCTRL);
	INTERFACE_INIT(IHDL4SEUnit, pobj, terrisctrl, hdl4se_unit);
	INTERFACE_INIT(IHDL4SEDetector, pobj, terrisctrl, hdl4se_detector);
	DLIST_VARINIT(pobj, terrisctrl);
	
	pobj->name = NULL;
	pobj->parent = NULL;
	pobj->readdata_unit = NULL;
	pobj->readdata = bigintegerCreate(32);
	pobj->lastreaddata = bigintegerCreate(32);
	pobj->currentline = 0;
	for (i = 0; i < paramcount; i++) {
		if (pParams[i].name == PARAMID_HDL4SE_UNIT_NAME) {
			if (pobj->name != NULL)
				free(pobj->name);
			pobj->name = strdup(pParams[i].pvalue);
		} 
		else if (pParams[i].name == PARAMID_HDL4SE_UNIT_PARENT) {
			pobj->parent = (IHDL4SEModule **)pParams[i].pvalue;
		}
	}
	terrisInit();
	/* Returns the generated object */
	OBJECT_RETURN_GEN(terrisctrl, pobj, pObject, CLSID_TERRISCTRL);
	return EIID_OK;
}

static void terrisctrlDestroy(HOBJECT object)
{
	sTerrisCtrl* pobj;
	int i;
	pobj = (sTerrisCtrl*)objectThis(object);
	if (pobj->name != NULL)
		free(pobj->name);
	memset(pobj, 0, sizeof(sTerrisCtrl));
	free(pobj);
}

static int terrisctrlValid(HOBJECT object)
{
	sTerrisCtrl* pobj;
	pobj = (sTerrisCtrl*)objectThis(object);
	return 1;
}

static int terrisctrl_hdl4se_unit_Connect(HOBJECT object, int index, HOBJECT from, int fromindex) 
{
	sTerrisCtrl* pobj;
	IHDL4SEUnit** unit = NULL;
	pobj = (sTerrisCtrl*)objectThis(object);
	if (index == 4) {
		if (0 == objectQueryInterface(from, IID_HDL4SEUNIT, (void**)&unit)) {
			objectRelease(pobj->readdata_unit);
			pobj->readdata_unit = unit;
			pobj->readdata_index = fromindex;
		}
	}
	return 0;
}

static int terrisctrl_hdl4se_unit_ConnectPart(HOBJECT object, int index, int start, int width, HOBJECT from, int fromindex)
{
	sTerrisCtrl* pobj;
	IHDL4SEUnit** unit = NULL;
	pobj = (sTerrisCtrl*)objectThis(object);
	return 0;
}

static int terrisctrl_hdl4se_unit_GetValue(HOBJECT object, int index, int width, IBigNumber ** value)
{
	int i;
	sTerrisCtrl* pobj;
	pobj = (sTerrisCtrl*)objectThis(object);
	if (index == 2) {
		objectCall1(value, AssignUint32, pobj->writeaddr);
	}
	else if (index == 3) {
		objectCall1(value, AssignUint32, pobj->writedata);
	}
	return 0;
}

static int terrisctrl_hdl4se_unit_ClkTick(HOBJECT object)
{
	sTerrisCtrl* pobj;

	pobj = (sTerrisCtrl*)objectThis(object);
	objectCall3(pobj->readdata_unit, GetValue, pobj->readdata_index, 32, pobj->readdata);
	if (!objectCall1(pobj->readdata, IsEQ, pobj->lastreaddata)) {
		unsigned int key;
		objectCall1(pobj->readdata, GetUint32, &key);
		objectCall1(pobj->lastreaddata, AssignU, pobj->readdata);
		if (key & 1)
			terrisPressKey(TK_RIGHT);
		if (key & 2)
			terrisPressKey(TK_LEFT);
		if (key & 4)
			terrisPressKey(TK_DOWN);
		if (key & 8)
			terrisPressKey(TK_TURNLEFT);
	}
	terrisTick();
	return 0;
}

static unsigned int get_line_data(sTerrisCtrl* pobj, int line, int right)
{
	unsigned int data;
	int i;
	data = 0;
	line = YCOUNT - 1 - line;
	for (i = 0; i < 8; i++) {
		int xx, yy, ii, jj;
		unsigned int c;
		xx = 7 - i + right * 8;
		yy = line;
		c = 0;
		jj = xx - currentblock.posx + BLOCKSIZE / 2;
		ii = yy - currentblock.posy + BLOCKSIZE / 2;
		if (ii >= 0 && ii < BLOCKSIZE && jj >= 0 && jj < BLOCKSIZE) {
			c = currentblock.subblock[ii][jj];
		}

		data <<= 4;
		if (c == 0)
			data |= terrisPanel[line][7 - i + right * 8] & 0xf;
		else
			data |= c & 0xf;

	}
	return data;
}

static int terrisctrl_hdl4se_unit_Setup(HOBJECT object)
{
	sTerrisCtrl* pobj;
	pobj = (sTerrisCtrl*)objectThis(object);
	/* Update half a row every cycle */
	if (pobj->currentline >= 0 && pobj->currentline < YCOUNT * 2) {
		pobj->writeaddr = 0xF0000010 + pobj->currentline * 4;
		pobj->writedata = get_line_data(pobj, pobj->currentline / 2, pobj->currentline & 1);
	}
	else if (pobj->currentline == YCOUNT * 2 + 0) {
		pobj->writeaddr = 0xF00000e0;
		pobj->writedata = 
			  ((nextblock.subblock[0][0] & 0xf) << 0)
			| ((nextblock.subblock[0][1] & 0xf) << 4)
			| ((nextblock.subblock[0][2] & 0xf) << 8)
			| ((nextblock.subblock[0][3] & 0xf) << 12)
			| ((nextblock.subblock[1][0] & 0xf) << 16)
			| ((nextblock.subblock[1][1] & 0xf) << 20)
			| ((nextblock.subblock[1][2] & 0xf) << 24)
			| ((nextblock.subblock[1][3] & 0xf) << 28)
			;
	}
	else if (pobj->currentline == YCOUNT * 2 + 1) {
		pobj->writeaddr = 0xF00000e4;
		pobj->writedata =
			  ((nextblock.subblock[2][0] & 0xf) << 0)
			| ((nextblock.subblock[2][1] & 0xf) << 4)
			| ((nextblock.subblock[2][2] & 0xf) << 8)
			| ((nextblock.subblock[2][3] & 0xf) << 12)
			| ((nextblock.subblock[3][0] & 0xf) << 16)
			| ((nextblock.subblock[3][1] & 0xf) << 20)
			| ((nextblock.subblock[3][2] & 0xf) << 24)
			| ((nextblock.subblock[3][3] & 0xf) << 28)
			;
	}
	else if (pobj->currentline == YCOUNT * 2 + 2) {
		pobj->writeaddr = 0xF00000f0;
		pobj->writedata = gameScore;
	}
	else if (pobj->currentline == YCOUNT * 2 + 3) {
		pobj->writeaddr = 0xF00000f4;
		pobj->writedata = gameLevel;
	}
	else if (pobj->currentline == YCOUNT * 2 + 4) {
		pobj->writeaddr = 0xF00000f8;
		pobj->writedata = MAXSPPED - currentspeed;
	}
	pobj->currentline = pobj->currentline + 1;
	if (pobj->currentline > YCOUNT * 2 + 4)
		pobj->currentline = 0;
	return 0;
}

8.4 simulator connection and operation

Then we write verilog code to connect the controller to a main module:

/* main.v */

`include "hdl4secell.v"

/* Tetris controller written in c */
(* 
  HDL4SE="LCOM", 
  CLSID="6f8a9aa6-ec57-4734-a183-7871ed57ea95", 
  softmodule="hdl4se" 
*) 
module teris_ctrl
  (
    input  wClk,
    input  nwReset,
    output [31:0] bWriteAddr,
    output [31:0] bWriteData,
    input  [31:0] bKeyPressed
  );
endmodule

module main(
    input wClk, nwReset,
    output wWrite,
    output [31:0] bWriteAddr,
    output [31:0] bWriteData,
    output [3:0]  bWriteMask,
    output wRead,
    output [31:0] bReadAddr,
    input [31:0]  bReadData);

/* Game controller */
	teris_ctrl ctrl(wClk, nwReset, bWriteAddr, bWriteData, bReadData);

/*We've been reading the state of the keys*/
	hdl4se_const #(1, 1) const_wRead(wRead);
	hdl4se_const #(32, 32'hF000_0000) const_bReadAddr(bReadAddr);

/*Always writing*/
    hdl4se_const #(1, 1) const_wWrite(wWrite);

endmodule

It can be seen that the control is hung in the top-level module as a basic unit, and the code of the top-level module is relatively simple. The compiler hasn't finished yet, so call the humanoid compiler to compile it into object code:

#include "stdlib.h"
#include "stdio.h"
#include "string.h"

#include "object.h"
#include "dlist.h"
#include "bignumber.h"
#include "hdl4secell.h"
#include "conststring.h"
#include "verilog_parsetree.h"

static const char* CLSID_000F = "6f8a9aa6-ec57-4734-a183-7871ed57ea95";

IHDL4SEUnit** hdl4seCreate_main2(IHDL4SEModule** parent, const char* instanceparam, const char* name)
{ /* module main */
	IHDL4SEModule** module;
	IHDL4SEUnit** unit;
	
	/* Generate module object */
	unit = hdl4seCreateUnit(parent, CLSID_HDL4SE_MODULE, instanceparam, name);
	/* Get the IHDL4SEModule interface of the object */
	objectQueryInterface(unit, IID_HDL4SEMODULE, (void**)&module);

	/* add port */
	/*  0*/ objectCall3(module, AddPort, 1, PORT_DIRECT_INPUT, "wClk");
	/*  1*/ objectCall3(module, AddPort, 1, PORT_DIRECT_INPUT, "nwReset");
	/*  2*/ objectCall3(module, AddPort, 1, PORT_DIRECT_OUTPUT, "wWrite");
	/*  3*/ objectCall3(module, AddPort, 32, PORT_DIRECT_OUTPUT, "bWriteAddr");
	/*  4*/ objectCall3(module, AddPort, 32, PORT_DIRECT_OUTPUT, "bWriteData");
	/*  5*/ objectCall3(module, AddPort, 4, PORT_DIRECT_OUTPUT, "bWriteMask");
	/*  6*/ objectCall3(module, AddPort, 1, PORT_DIRECT_OUTPUT, "wRead");
	/*  7*/ objectCall3(module, AddPort, 32, PORT_DIRECT_OUTPUT, "bReadAddr");
	/*  8*/ objectCall3(module, AddPort, 32, PORT_DIRECT_INPUT, "bReadData");

	unsigned int clsid[4];
	str2iid(CLSID_000F, clsid);
	IHDL4SEUnit** ctrl = hdl4seCreateUnit(module, clsid, "", "ctrl");

	IHDL4SEUnit** const_1b1 = hdl4seCreateUnit(module, CLSID_HDL4SE_CONST, "1,1", "const_bReadAddr");
	objectCall3(unit, Connect, 2, const_1b1, 1); /* Simplify processing and keep writing */
	objectCall3(unit, Connect, 3, ctrl, 2);
	objectCall3(unit, Connect, 4, ctrl, 3);
	IHDL4SEUnit** const_0_wRead = hdl4seCreateUnit(module, CLSID_HDL4SE_CONST, "1, 1", "const_0_wRead");
	objectCall3(unit, Connect, 6, const_0_wRead, 0);
	IHDL4SEUnit** const_bReadAddr = hdl4seCreateUnit(module, CLSID_HDL4SE_CONST, "32, 32'hF000_0000", "const_bReadAddr");
	objectCall3(unit, Connect, 7, const_bReadAddr, 0);
	objectCall3(ctrl, Connect, 4, unit, 8);

	/*Release module interface*/
	objectRelease(module);
	/*Return to unit interface*/
	return unit;
}

Then connect it to the simulator:

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "object.h"
#include "bignumber.h"
#include "hdl4secell.h"
#include "hdl4sesim.h"
#include "hdl4sevcdfile.h"
#include "terris.h"

IHDL4SESimulator** sim;
IHDL4SEUnit** topmodule; 
IHDL4SEUnit** gui;
unsigned long long clocks = 0;

static int running = 1;



int StopRunning()
{
	running = 0;
	return 0;
}

IHDL4SEUnit** hdl4seCreate_main(IHDL4SEModule** parent, const char* instanceparam, const char* name);
IHDL4SEUnit** hdl4seCreate_main2(IHDL4SEModule** parent, const char* instanceparam, const char* name);
extern int (*A_u_t_o_registor_terrisctrl)();

int main(int argc, char* argv[])
{
	int i;
	int width;
	int count, unitcount;
	IHDL4SEUnit** sim_unit;
	A_u_t_o_registor_terrisctrl();
	sim = hdl4sesimCreateSimulator();
	topmodule = hdl4seCreate_main2(NULL, "", "main");
	gui = guiCreate(0xf0000000, "terris");
	objectCall1(sim, SetTopModule, topmodule);
	objectCall1(sim, AddDevice, gui);
	objectCall1(sim, SetReset, 0);
	unitcount = 0;
	objectQueryInterface(sim, IID_HDL4SEUNIT, (void**) &sim_unit);
	objectPrintInfo(stdout, fprintf);
	do {
		//objectCall0(sim, RunClockTick);
		objectCall0(sim_unit, ClkTick);
		objectCall0(sim_unit, Setup);
		clocks++;
		if (clocks == 4)
			objectCall1(sim, SetReset, 1);
	} while (running);
	return 0;
}

Then, you can play games, hehe hehe. This game is too difficult. Change the code yourself, or add a back door and clear half with one click?

8.5 progress report

We are still promoting the software programming of the compiler, which is still relatively complex.
The object code of the above code generated by the compiler is as follows:

/*
*  Created by HDL4SE @ Fri Jun 11 21:59:23 2021
*  Don't edit it.
*/

#include "stdlib.h"
#include "stdio.h"
#include "string.h"

#include "object.h"
#include "dlist.h"
#include "bignumber.h"
#include "hdl4secell.h"
#include "conststring.h"
#include "verilog_parsetree.h"


typedef IHDL4SEUnit** (*hdl4seCreate_Func)(IHDL4SEModule** parent, const char* instanceparam, const char* name); 

typedef struct s_module_info {
	int isbasiccell;
	int moduleindex;
	union {
		const char** clsid; 
		hdl4seCreate_Func *hdl4seCreate_func; 
	};
}module_info;

static const char *CLSID_000F = "6f8a9aa6-ec57-4734-a183-7871ed57ea95";
static const char *softmodule_000F = "hdl4se";
static const char *CLSID_000B = "8FBE5B87-B484-4f95-8291-DBEF86A1C354";
static const char *softmodule_000B = "hdl4se";

IHDL4SEUnit** hdl4seCreate_main(IHDL4SEModule** parent, const char* instanceparam, const char* name)
{ /* module main */
	IHDL4SEModule**	module;
	IHDL4SEUnit**	unit;
	IHDL4SEUnit**	modules[4];

	/* Generate module object */
	unit = hdl4seCreateUnit(parent, CLSID_HDL4SE_MODULE, instanceparam, name);
	/* Get the IHDL4SEModule interface of the object */
	objectQueryInterface(unit, IID_HDL4SEMODULE, (void**)&module);

	/* add port */
	/*  0*/ objectCall3(module, AddPort, PORT_DIRECT_INPUT , 1, "wClk");
	/*  1*/ objectCall3(module, AddPort, PORT_DIRECT_INPUT , 1, "nwReset");
	/*  2*/ objectCall3(module, AddPort, PORT_DIRECT_OUTPUT, 1, "wWrite");
	{
		int portwidth;
		const expr_code expr_info[] = {
			{.exprtype=EXPRTYPE_NUMBER, .value="31" }, 
			{.exprtype=EXPRTYPE_NUMBER, .value="0" }, 
			{.exprtype=EXPRTYPE_BINOP, .op=OP_MINUS},
			{.exprtype=EXPRTYPE_NUMBER, .value="1"},
			{.exprtype=EXPRTYPE_BINOP, .op=OP_PLUS},
		};
		portwidth = const_expr_eval(5, expr_info, 0, NULL);
	/*  3*/ objectCall3(module, AddPort, PORT_DIRECT_OUTPUT, portwidth, "bWriteAddr");
	}
	{
		int portwidth;
		const expr_code expr_info[] = {
			{.exprtype=EXPRTYPE_NUMBER, .value="31" }, 
			{.exprtype=EXPRTYPE_NUMBER, .value="0" }, 
			{.exprtype=EXPRTYPE_BINOP, .op=OP_MINUS},
			{.exprtype=EXPRTYPE_NUMBER, .value="1"},
			{.exprtype=EXPRTYPE_BINOP, .op=OP_PLUS},
		};
		portwidth = const_expr_eval(5, expr_info, 0, NULL);
	/*  4*/ objectCall3(module, AddPort, PORT_DIRECT_OUTPUT, portwidth, "bWriteData");
	}
	{
		int portwidth;
		const expr_code expr_info[] = {
			{.exprtype=EXPRTYPE_NUMBER, .value="3" }, 
			{.exprtype=EXPRTYPE_NUMBER, .value="0" }, 
			{.exprtype=EXPRTYPE_BINOP, .op=OP_MINUS},
			{.exprtype=EXPRTYPE_NUMBER, .value="1"},
			{.exprtype=EXPRTYPE_BINOP, .op=OP_PLUS},
		};
		portwidth = const_expr_eval(5, expr_info, 0, NULL);
	/*  5*/ objectCall3(module, AddPort, PORT_DIRECT_OUTPUT, portwidth, "bWriteMask");
	}
	/*  6*/ objectCall3(module, AddPort, PORT_DIRECT_OUTPUT, 1, "wRead");
	{
		int portwidth;
		const expr_code expr_info[] = {
			{.exprtype=EXPRTYPE_NUMBER, .value="31" }, 
			{.exprtype=EXPRTYPE_NUMBER, .value="0" }, 
			{.exprtype=EXPRTYPE_BINOP, .op=OP_MINUS},
			{.exprtype=EXPRTYPE_NUMBER, .value="1"},
			{.exprtype=EXPRTYPE_BINOP, .op=OP_PLUS},
		};
		portwidth = const_expr_eval(5, expr_info, 0, NULL);
	/*  7*/ objectCall3(module, AddPort, PORT_DIRECT_OUTPUT, portwidth, "bReadAddr");
	}
	{
		int portwidth;
		const expr_code expr_info[] = {
			{.exprtype=EXPRTYPE_NUMBER, .value="31" }, 
			{.exprtype=EXPRTYPE_NUMBER, .value="0" }, 
			{.exprtype=EXPRTYPE_BINOP, .op=OP_MINUS},
			{.exprtype=EXPRTYPE_NUMBER, .value="1"},
			{.exprtype=EXPRTYPE_BINOP, .op=OP_PLUS},
		};
		portwidth = const_expr_eval(5, expr_info, 0, NULL);
	/*  8*/ objectCall3(module, AddPort, PORT_DIRECT_INPUT , portwidth, "bReadData");
	}

	{/* module instances  */
		int i;
		static const char *inst_name[4] = {
			/*  0*/ "ctrl",
			/*  1*/ "const_wRead",
			/*  2*/ "const_bReadAddr",
			/*  3*/ "const_wWrite",
		};

		static module_info inst_module_info[4] = {
			/*  0*/ {1, 0x000F, &CLSID_000F},
			/*  1*/ {1, 0x000B, &CLSID_000B},
			/*  2*/ {1, 0x000B, &CLSID_000B},
			/*  3*/ {1, 0x000B, &CLSID_000B},
		};
		for(i = 0; i< 4; i++) { 
			char instanceparam[256];
			strcpy(instanceparam, "");
			if (inst_module_info[i].isbasiccell) {
				unsigned int clsid[4];
				str2iid(*inst_module_info[i].clsid, clsid);
				modules[i] = hdl4seCreateUnit(module, clsid, instanceparam, inst_name[i]);
			} else {
				modules[i] = (*inst_module_info[i].hdl4seCreate_func)(module, instanceparam, inst_name[i]);
			}
		}
	}

	/* Wire network connection */

	/* instance "ctrl", module "teris_ctrl" */
		objectCall3(modules[0], Connect, 0, unit, 0);
		objectCall3(modules[0], Connect, 1, unit, 1);
		objectCall3(unit, Connect, 3, modules[0], 2);
		objectCall3(unit, Connect, 4, modules[0], 3);
		objectCall3(modules[0], Connect, 4, unit, 8);

	/* instance "const_wRead", module "hdl4se_const" */
		objectCall3(unit, Connect, 6, modules[1], 0);

	/* instance "const_bReadAddr", module "hdl4se_const" */
		objectCall3(unit, Connect, 7, modules[2], 0);

	/* instance "const_wWrite", module "hdl4se_const" */
		objectCall3(unit, Connect, 2, modules[3], 0);

	/*Release module interface*/
	objectRelease(module);
	/*Return to unit interface*/
	return unit;
}

It's so close. I missed the progress of playing the game, but I just couldn't help playing it,
[please refer to]
1.HDL4SE: software engineers learn Verilog language (VII)
2.HDL4SE: software engineers learn Verilog language (VI)
3.HDL4SE: software engineers learn Verilog language (V)
4.HDL4SE: software engineers learn Verilog language (IV)
5.HDL4SE: software engineers learn Verilog language (III)
6.HDL4SE: software engineers learn Verilog language (II)
7.HDL4SE: software engineers learn Verilog language (I)
8.LCOM: lightweight component object model
9.LCOM: interface with data
10. Tool download: bison 3.7 and flex 2.6.4 under 64 bit windows
11. Git: Verilog parser open source project
12.git: HDL4SE project
13.git: LCOM project
14.git: GLFW project

Topics: C Programming git Game Development Visual Studio