The control software and compiler is written in Python3 (to reach the maximal flexibility). The communication with FPGA design is done via the serial line and therefore I am also providing a module which allows to read and write 8 bit chunks of data.
You need to install the PySerial library into your system. The most suitable way is to use pip3
and install it inside your home folder:
pip3 --user PySerial
If you don't want to run the script as root, add yourself to the group which has access to serial line. On my system, Debian 10, the block device /dev/ttyUSB0
has following
righths:
crw-rw---- 1 root dialout 188, 0 Jun 27 19:15 /dev/ttyUSB0
Therefore, I have to add my accout to the group dialout
(after that, you have to login and logout to be part of the new group or you can run the su
tool if don't want to logout from your session):
sudo usermod -a -G dialout $USER
sudo su -l $USER -
The Python library for communication with engine via the UART end-point is located in the io folder.
The configuration of the UART is following:
- Baudrate - 115200
- Parity Bit - none
- HW control - none, should be disabled
Each command is the 8-bit value.
Name | Value |
---|---|
CMD_WRITE | 0x00 |
CMD_READ | 0x01 |
CMD_ACK | 0X02 |
The address space inside the component is possible to address via the 24-bit address space. In total, you are able to address 128 MB of address space.
The process of data reading is following:
- Send the CMD_READ command
- Send fist (index 7 downto 0) 8-bits of address where you want to read a data
- Send second (index 15 downto 8) 8-bits of address where you want to read a data
- Send third (index 23 downto 16) 8-bits of address where you want to read a data
- Read the 8-bit value from the serial line
The process of data writing is following:
- Send the CMD_WRITE command
- Send fist (index 7 downto 0) 8-bits of address where you want to write a data
- Send second (index 15 downto 8) 8-bits of address where you want to write a data
- Send third (index 23 downto 16) 8-bits of address where you want to write a data
- Send the 8-bit data to write
- Wait until the CMD_ACK is received
The bbus tool is a lightweight tool written in Python3 and it allows you writting and reading from the FPGA via the UART. It is using the implementation of the Brainfuck_io library provided in the io folder.
Example of reading from the address 0xab:
./bbus.py 0xab
Example of writing to address 0xbb, data 0x1c:
./bbus.py 0xbb 0x1c
The tool also allows you to write multiple bytes. In this mode, the passed address is used as the target address for the first byte (7 downto 0). So for example, if you want to write 0x010203 to address 0x2:
- Byte 0x03 to address 0x2
- Byte 0x02 to address 0x3
- Byte 0x01 to address 0x1
./bbus.py 0x2 0x010203
The tool is also capable to test the while address space with writing of random data to incrising destination address (the passed number is the address bit-width):
./bbus.py --test=8
You can also handle the tested address space using the --min-test-addr
and --max-test-addr
arguments:
./bbus.py --test=8 --min-test-addr=0x5 --max-test-addr=0x100
Braninfuck CPU is using the 16-bit address space. Reading from Instruction and Cell memory is allowed if the CPU is not enabled. Reading from the register address space is allowed anytime. The design is pipelined and it takes ~3 clock cycles to read/write the BCPU.
Address space | Coment |
---|---|
0x0 - 0x3FFF | Cell memory address space |
0x4000 - 0x7FFF | Instruction memory address space |
0x8000 - 0xBFFF | Register address space |
Register address space has following layout:
Address | Comment |
---|---|
0x8000 | CPU enabled |
0x8001 | Lower half of the PC |
0x8002 | Upper half of the PC |
0x8003 | Flag register |
0x8004 | Read/Write input/outou to/from the BCPU |
Input/output to BCPU is stored into internal FIFO fronts. The input FIFO front is read by the BCPU core when the required instruction is asserted. Output from the BCPU is stored in the output FIFO and the output flag is set if any data are available.
Flag register structure:
Bit index | Comment |
---|---|
0 | Output data available |
1 | Input data FIFO is full |
2 | Output data FIFO is full |
3 | Invalid operation code has been detected |
4 | Program is terminated |
5 | BCpu is waiting for input |
6 - 7 | Reserved - set to 0 |
Processor is using a 16-bit instructions (to encode longer jumps) and memory access is done in byte order (due to the UART). I know that instructions are little bit longer but this is done becase of some future reserve (if we will be adding some instructions) and for encoding of jump instructions. Each instruction consits of:
- 8 bits for instruction encoding & data (currently use for instructions only) - instruction is encoded in 4 MSB bits
- 8 bits for instruction data (currently for jumps)
Instructions are encoded like following (No = you can use any data, BCPU is ignoring them):
Source code symbol | Opcode | Data | Meaning |
---|---|---|---|
; (extended) | 0x0 | No | No operation |
> | 0x1 | No | Increment ptr |
< | 0x2 | No | Decrement ptr |
+ | 0x3 | No | Increment cell ptr |
- | 0x4 | No | Decrement cell ptr |
. | 0x5 | No | Send cell to output |
, | 0x6 | No | Store input to cell |
[ | 0x7 | Yes - jmp value (B) | Cell == 0 -> jump to ] |
] | 0x8 | Yes - jmp value (B) | Cell != 0 -> jump to [ |
x (extended) | 0x9 | No | Program termination (BCPU stops the operation) |
& (extended) | 0x10 | No | Preload data to cell register |
The jump value is in bytes which are added/subtracted from the current PC (program counter) in the BCPU - jump is relative from the position in the source code. Each program
starts from the address 0. The original Brainfuck language was extended with the ; symbol for No operation, x for the program termination, & preload
and line comment starting with // (like in C). Compiler source code is located in the compiler
folder.
We are not interested about the real value of the PC during the program termination. The BCPU program is terminated in the last pipeline stage and therefore you have to decrement the PC value by 2 during the debugging.
The translated program can be uploaded to the BCPU using the upload-program.py
tool - use the --help
to obtain more information.
The translation can be done like following:
cd compiler
./compiler --help # To obtain more detailed info
./compiler.py --memory file.b
The compiler generates a binary form of the code which can be then uploaded to the BCPU. You can also get a memory map in the mif format which can be used in Quartus for the memory inilization (and also in Bluespec simulation). We can start the program uploading - you can also erase the memmory but this operation is slow for now (it is not required but it is fine to do it before debugging):
./upload-program.py --help # To obtain more detailed info
./upload-program.py --erase compiler/a.out
The program is now uploaded into the instruction memory and you can fire the processing.