lcgamboa / picsimlab

PICsimLab - Programmable IC Simulator Laboratory
GNU General Public License v2.0
476 stars 90 forks source link

Remote TCP interface #79

Closed dasiUAH closed 11 months ago

dasiUAH commented 1 year ago

Hi,

I'm a new PICSimLab user. Thank you so much for your work on this project.

II am interested in using PICSimLab for teaching purposes. For this I would like to use the RemoteTCP interface with an existing RISCV instruction set simulator.

At the moment, I'm trying to get a good understanding of the example described in exp_board_RemoteTCP and exp_bsim_remote files.

Just for testing, I have written a fake simulator that only performs read/write operations on the ports. This works fine with the led bar and switches but when I try to use the ultrasonic part (input_hcsr04), timing is not very precise and some pins toggles are lost.

Could you give some hints on how to handle this kind of device?

The same happend for I2C/SPI comunications. I guess I should use the library functions to make bitbang but I'm not sure how to integrate it in the board code.

Thanks very much for your attention.

lcgamboa commented 1 year ago

Hi @dasiUAH ,

Remote TCP was initially developed to work with Ripes, which is a RISCV simulator. The Ripes client is in this repository: https://github.com/lcgamboa/Ripes/blob/external_io_tcp_support/src/io/ioexternalbus.cpp

For the peripherals to work, it is necessary to be synchronized with PICSimLab, so the peripherals must be implemented in PICSimLab and not in the CPU simulator. Like Timer for example.

In PICsimLab there are already functions to implement UART, SPI and I2C controllers (the src/devices/*bitbang files), they would have to be added in the exp_board_RemoteTCP.cc file. I can try to add those controllers.

To read the input_hcsr04 it would be necessary to use a Timer on the PICSimLab side.

Which RISCV simulator do you intend to use? Could you share your fake simulator?

Perhaps adding the simulator directly to PICSimLab could be a better option, as with simavr, qemu-stm32, qemu-esp32..., than use TCP/IP.

dasiUAH commented 1 year ago

Hi @lcgamboa,

Thanks for your response.

I understand that it would be best to integrate the simulator in PICSimLab, I just used the remote interface because it was the easiest way to start using PICSimLab parts and it works great.

For research reasons we are evaluating SPIKE as RISCV simulator and I wonder if I could use it for teaching activities in a more attractive way using the PICSimLab.

Now I am taking a look to bsim_qemu.c and trying to understand the g_board->Run_CPU_ns(GotoNow()); mechanism.

I send you the code I have used to test the remote interface.

main.zip

lcgamboa commented 1 year ago

Hi @dasiUAH ,

a while ago I found this project based on Spike and I thought about using it instead of Ripes with PICSimLab, but I still don't have time to implement it.

The g_board->Run_CPU_ns(GotoNow()); is a hack to try to synchronize qemu to PICSimLab. It works satisfactorily, the biggest problem is that qemu is not cycle-accurate and the simulation time is based on the computer's wall time. Basically PICSimLab is updated by a qemu timer (currently every 100ms). Or when some IO operation like reading and writing of pins occurs. When the IO operation occurs, PICSimLab reads the simulation time from qemu and advances the simulation until that time.

When I developed the remote TCP board I still hadn't implemented this part of Run_CPU_ns. I believe it is an solution to synchronize the simulators. It would be enough for the simulator to send the synchronization packet with the simulation time every 100ms and every time it makes an IO request. As Spike and cycle-accurate I think it would work better than the current implementation.

lcgamboa commented 1 year ago

@dasiUAH

I added a new branch https://github.com/lcgamboa/picsimlab/tree/SyncRemoteTCP with initial support for Run_CPU_ns. Now the simulation works synchronized. I believe it will now work the way you expected. The client only has to update the simulation time with a time close to the wall time for the simulation to run in real time.

Run_ns

Your project with time sync support added: remote.zip

dasiUAH commented 1 year ago

Hi @lcgamboa,

I am familiar with the Spike plugin interface and it works well for simulating a memory mapped register interface. The problem is that, to date, it does not allow interrupt generation by the peripheral and this limits its use. Maybe I could implement a custom interface.

I am going to try the SyncRemoteTCP solution you have posted.

Thanks again for your work.

lcgamboa commented 1 year ago

Hi @dasiUAH ,

as you requested, I added I2C, SPI, UART, and ADC support to the RemoteTCP card. I haven't had time to test everything yet. I updated the branch SyncRemoteTCP.

dasiUAH commented 1 year ago

Hi @lcgamboa,

I have been testing the new board just using the IO ports, doing bitbanging from the simulator side, and it worked with the adxl345 and the hcsr04. With the new support thinks are going to be easier.

I understand that the integration with SPIKE should work well so I will dedicate this month to that goal. I will keep you informed about my progress or any problems I may face.

Thanks again.

dasiUAH commented 1 year ago

Hi @lcgamboa,

I have been making progress with the integration with SPIKE. The UART and I/O ports are working but for GPIO I need to improve the performance of the system.

What I am not able to integrate properly is the SPI interface handling using "bitbang_spi_t master_sp;"

For example, to read/write a register from the adxl345 device a 16-bit transfer is needed.

The first call to bitbang_spi_ctrl_write transfers the first 8 bits,

Please, give me an indication of the steps to follow to transfer 16 bits, read the response from the device and leave the device ready for the next operation.

Thanks

lcgamboa commented 1 year ago

Hi @dasiUAH ,

It took some modifications for the SPI to work on commit 5e45d3b .

¿ how do I know that the transfer is complete?

Just read the SPI0STA register. Bit 0 indicates whether the write/read operation is in progress (value 0) or has ended (value 1)

after that, ¿ should I call bitbang_spi_ctrl_write again to transfer the second byte?

Make two consecutive writes to the SPI0DAT address (waiting for the end of the operation with the reading of SPI0STA)

Below is the function I used to test the operation of the SPI port:

// Send and receive form SPI
// COPI0 (MOSI) is connected to CIPO0 (MISO) throw JumperWire part
void test_4()
{
    struct timespec start;
    clock_gettime(CLOCK_MONOTONIC, &start);
    uint64_t simtime = (start.tv_sec * 1000000000L) + start.tv_nsec;
    uint32_t payload[2];

    // Configure SPI0
    payload[0] = htonl(SPI0CFG);
    payload[1] = htonl(0x8000); //SPI ON
    send_packet(VB_PWRITE, (char *)payload, sizeof(payload), simtime);
    recv_packet(NULL);

    while (1)
    {
        // Write SPI A 
        payload[0] = htonl(SPI0DAT);
        payload[1] = htonl(0x55);
        send_packet(VB_PWRITE, (char *)payload, sizeof(payload), simtime);
        recv_packet(NULL);

        do
        {
            usleep(10);
            simtime += 10000L;

            // Read Status
            payload[0] = htonl(SPI0STA);
            payload[1] = htonl(0);
            send_packet(VB_PREAD, (char *)payload, 4, simtime);
            recv_packet((char *)payload);
            payload[0] = ntohl(payload[0]);
            payload[1] = ntohl(payload[1]);
            printf("waiting %i \n", payload[1]);
        } while (!payload[1]); // wait the write operation finish

        // Read Data
        payload[0] = htonl(SPI0DAT);
        payload[1] = htonl(0);
        send_packet(VB_PREAD, (char *)payload, 4, simtime);
        recv_packet((char *)payload);
        payload[0] = ntohl(payload[0]);
        payload[1] = ntohl(payload[1]);
        printf("===> received 0x%02X \n", payload[1]);
    }
}
dasiUAH commented 1 year ago

Hi @lcgamboa,

Thanks for your answer.

I am trying to write my own remote board and the register set of the the UART or SPI controllers that I try to mimic is different. Mainly they are GRLIB IP cores (https://www.gaisler.com/index.php/products/ipcores/spi/spicontroller?task=view&id=377)

In the case of the SPI controller, it has data register is 32 bits wide and the size of the transfer is defined in a mode control register.

I want to make just one write to the TX data register and depending on mode selected, transfer 8, 16 or 32 bits. At the end a flag will be raised in an event register. Simulator will be waiting for the activation of this flag in order to read the result in RX data register, in case of a read operation.

This means that the first call to bitbang_spi_ctrl_write is made in response to TX data register write but the next calls must be made from board code without intervention from the simulator.

So, where should I detect the eight bits end condition (master_spi[0].bit > 7) in order to transfer the next byte ?

I have tried to do it in Run_CPU and I am able to read the DEVID register (E5) from adxl345 but it seems that the controller or the board code reach some unstable state which prevents to carry out a new device operation.

The is as follows:

` I only have one controller (master_spi) // First byte transfer, payload is 0x80 ( Read DEVID ), adxl345 will respond during the second byte transfer // Done in EvThreadRun

                 case SPI0_TX:
                        master_spi.ctrl_on = 1;
                        master_spi.cs_value[0] = 0;
                        bitbang_spi_ctrl_write(&master_spi, payload[1]);     
                        break;

// Second byte transfer // Done In Run_CPU

        static int first = 1;

        if ( master_spi.ctrl_on && (master_spi.bit > 7) && (master_spi.status == 0) )
        {
           if ( first )
           {
              bitbang_spi_ctrl_write(&master_spi, 0 );   // Second write
              first = 0;
           }
           else
           {
              master_spi.ctrl_on = 0;
              master_spi.cs_value[0] = 1;
              printf("REC: %02X\n", master_spi.data8 );  // Here I get the right response (0xE5) but following writes
                                                                                       //  to SPIO_TX in order to read the same or oder adxl
                                                                                       // register does not work
              first = 1;
           }               
        }  

`

During the net week I will make some code cleanup and I will share it to you.

Thanks again for your support

lcgamboa commented 1 year ago

Hi @dasiUAH ,

I understand your goals now. I made some modifications to the bitbang_spi code to support the transmission with sizes different from 8 bits. I added the spi->transmitting flag in the controller to monitor the writing/reading process. The modifications are in commit 6f198f1 .

dasiUAH commented 1 year ago

Hi @lcgamboa,

I am sending you the code I have so far.

My board has the following peripherals: UART, GPIO and SPI and they seems to work fine.

image

From a functional point of view they work right, UART is connected to a pseudo-terminal and GPIO lines are connect to the board four leds, four switches and four buttons.

SPI controller can read adxl345 register but when I connect the VCD to the SPI signals the logical values are not properly displayed. I think that the problem is about simulation time management, it seems not to be right but I don't see how I can fix it.

I still be a little confused about MStep, updatehardware and other methods used by different board implementation. Please give me a hint of what might be going on.

Thanks again for your support

image

remote.zip board.zip

lcgamboa commented 1 year ago

Hi @dasiUAH ,

I'm sorry for the delay, but I had a lot of student work to grade this week.

For simulators controlled by PICSimLab (all others except qemu and remote TCP) the Run_CPU method is called every 100ms of wall time and must simulate 100ms of simulation time.

The non-controlled simulators (qemu and Remote TCP) must call the Run_CPU_ns method to synchronize the PICSimLab simulation with the external simulation. Every IO operation already does this normally. In the case of Remote TCP, it is necessary to ensure that the client sends at least 1 packet every 100ms to keep PICSimLab updating. (or UI will freeze)

In your annexed code it is necessary to remove the code from the Run_CPU method and put it inside at the end of the Run_CPU_ns (in the 100ms if ). The MStep method must be called every cycle time (PICSimLab clock frequency), in the case of qemu and Remote TCP the CPU clock can be different from the IO clock (PICSimLab).

The UpdateHardware method is to read the computer's real serial port and its signals, as it consumes time it cannot execute every simulated instruction and every 100ms is a long time, so it is called with the highest possible rate that does not impact simulator performance.

dasilvin commented 1 year ago

Ok, understood.

I am afraid I am in the same situation. A lot of students work.

Thank you for the clarification.