1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-02-27 01:00:00 +00:00

Infrastructure for emulated CPUs: Bus arbitrator, Interrupt fielding processor

This commit is contained in:
Joerg Hoppe
2019-08-16 19:04:12 +02:00
parent 155b02a089
commit 8ff33a0be1
16 changed files with 412 additions and 172 deletions

View File

@@ -65,7 +65,7 @@ int mailbox_connect(void) {
void mailbox_print(void) {
printf("INFO: Content of mailbox to PRU:\n"
"arm2pru: req=0x%x, resp=0x%x\n", mailbox->arm2pru_req, mailbox->arm2pru_resp);
"arm2pru: req=0x%x\n", mailbox->arm2pru_req);
}
/* simulate simple register accesses:
@@ -101,17 +101,25 @@ void mailbox_test1() {
/* start cmd to PRU via mailbox. Wait until ready
* mailbox union members must have been filled.
*/
uint32_t xxx;
void mailbox_execute(uint8_t request) {
//uint32_t xxx;
bool mailbox_execute(uint8_t request) {
// write to arm2pru_req must be last memory operation
__sync_synchronize();
while (mailbox->arm2pru_req != ARM2PRU_NONE)
; // wait to complete
mailbox->arm2pru_req = request; // go!
// wait until ACKed
while (mailbox->arm2pru_req == request);
/*
do {
xxx = mailbox->arm2pru_req;
} while (xxx != ARM2PRU_NONE);
while (mailbox->arm2pru_req != ARM2PRU_NONE)
; // wait until processed
*/
// result false = error
return (mailbox->arm2pru_req == ARM2PRU_NONE) ;
}

View File

@@ -303,7 +303,7 @@ void unibusadapter_c::requests_cancel_scheduled(void) {
pthread_mutex_lock(&req->complete_mutex);
req->complete = true;
pthread_cond_signal(&req->complete_cond);
pthread_mutex_unlock(&req->complete_mutex);
pthread_mutex_unlock(&req->complete_mutex);
}
}
}
@@ -399,7 +399,7 @@ void unibusadapter_c::request_execute_active_on_PRU(unsigned level_index) {
// assert(mailbox->events.event_dma == 0); // previous signal must have been processed
// DEBUG("request_execute_active_on_PRU() DMA: ->active = dma_request %p, start = 0%06o, control=%u, wordcount=%u, data=0%06o ...", dmareq,
// mailbox->dma.startaddr, (unsigned)mailbox->dma.control, (unsigned)mailbox->dma.wordcount, (unsigned)mailbox->dma.words[0]);
mailbox->dma.cur_status = 0; // device DMA, not by CPU
mailbox_execute(ARM2PRU_DMA);
// scheduling is fast, on complete there's a signal.
dmareq->executing_on_PRU = true;
@@ -493,7 +493,8 @@ void unibusadapter_c::request_active_complete(unsigned level_index) {
pthread_mutex_lock(&tmprq->complete_mutex);
tmprq->complete = true;
pthread_cond_signal(&tmprq->complete_cond);
pthread_mutex_unlock(&tmprq->complete_mutex);
pthread_mutex_unlock(&tmprq->complete_mutex);
}
// Request a DMA cycle from Arbitrator.
@@ -549,11 +550,12 @@ void unibusadapter_c::DMA(dma_request_c& dma_request, bool blocking, uint8_t uni
// DEBUG("device DMA start: %s @ %06o, len=%d", unibus->control2text(unibus_control), unibus_addr, wordcount);
if (blocking) {
pthread_mutex_lock(&dma_request.complete_mutex);
// DMA() is blocking: Wait for request to finish.
while (!dma_request.complete) {
int res = pthread_cond_wait(&dma_request.complete_cond, &dma_request.complete_mutex);
assert(!res);
}
// DMA() is blocking: Wait for request to finish.
// pthread_mutex_lock(&dma_request.mutex);
while (!dma_request.complete) {
int res = pthread_cond_wait(&dma_request.complete_cond, &dma_request.complete_mutex);
assert(!res) ;
}
pthread_mutex_unlock(&dma_request.complete_mutex);
}
}
@@ -686,6 +688,56 @@ void unibusadapter_c::cancel_INTR(intr_request_c& intr_request) {
}
// do DATO/DATI as master CPU.
// no NPR/NPG/SACK request, but waiting for BUS idle
// result: success, else BUS TIMOUT
void unibusadapter_c::cpu_DATA_transfer(dma_request_c& dma_request, uint8_t unibus_control,
uint32_t unibus_addr, uint16_t *buffer) {
timeout_c timeout;
bool success;
cpu_data_transfer_request = &dma_request;
// request is not queued, so only request.mutex used
pthread_mutex_lock(&cpu_data_transfer_request->complete_mutex);
cpu_data_transfer_request->complete = false;
mailbox->dma.startaddr = unibus_addr;
mailbox->dma.control = unibus_control;
mailbox->dma.wordcount = 1;
// Copy outgoing data into mailbox device_DMA buffer
if (UNIBUS_CONTROL_ISDATO(unibus_control))
memcpy((void*) mailbox->dma.words, buffer, 2);
// do the transfer. Wait until concurrent device DMA/INTR complete
do {
while (mailbox->arbitrator.device_BBSY)
timeout.wait_us(100);
mailbox->arbitrator.cpu_BBSY = true; // mark as "initiated by CPU, not by device"
success = mailbox_execute(ARM2PRU_DMA);
// a device may have acquired the bus concurrently,
// then ARM2PRU_DMA is answered with an error
// infinite time may have passed after that check above
} while (!success);
// wait for PRU complete event
//pthread_mutex_lock(&cpu_data_transfer_request->complete_mutex);
//pthread_mutex_unlock(&cpu_data_transfer_request->complete_mutex);
pthread_mutex_lock(&cpu_data_transfer_request->complete_mutex);
// DMA() is blocking: Wait for request to finish.
// pthread_mutex_lock(&dma_request.mutex);
while (!cpu_data_transfer_request->complete) {
int res = pthread_cond_wait(&cpu_data_transfer_request->complete_cond, &cpu_data_transfer_request->complete_mutex);
assert(!res) ;
}
pthread_mutex_unlock(&cpu_data_transfer_request->complete_mutex);
// Copy incoming data from mailbox DMA buffer
if (unibus_control == UNIBUS_CONTROL_DATI)
memcpy(buffer, (void *) mailbox->dma.words, 2);
}
// set state of INIT
void unibusadapter_c::worker_init_event() {
unsigned device_handle;
@@ -799,63 +851,80 @@ void unibusadapter_c::worker_deviceregister_event() {
}
// called by PRU signal when DMA transmission complete
void unibusadapter_c::worker_dma_chunk_complete_event() {
priority_request_level_c *prl = &request_levels[PRIORITY_LEVEL_INDEX_NPR];
bool more_chunks;
// Must run under pthread_mutex_lock(&requests_mutex) ;
// Called for device DMA() chunk,
// or cpu_DATA_transfer()
void unibusadapter_c::worker_dma_chunk_complete_event(bool cpu_DATA_transfer) {
dma_request_c *dmareq = dynamic_cast<dma_request_c *>(prl->active);
if (cpu_DATA_transfer) {
// cpu_DATA_transfer() started independent of request_levels table,
// prl->active == NULL
cpu_data_transfer_request->success = (mailbox->dma.cur_status == DMA_STATE_READY);
assert(dmareq != NULL);
dmareq->unibus_end_addr = mailbox->dma.cur_addr; // track emnd of trasnmission, eror position
unsigned wordcount_transferred = dmareq->wordcount_completed_chunks()
+ mailbox->dma.wordcount;
assert(wordcount_transferred <= dmareq->wordcount);
if (mailbox->dma.control == UNIBUS_CONTROL_DATI) {
// guard against buffer overrun
// PRU read chunk data from UNIBUS into mailbox
// copy result cur_DMA_wordcount from mailbox->DMA buffer to cur_DMA_buffer
memcpy(dmareq->chunk_buffer_start(), (void *) mailbox->dma.words,
2 * mailbox->dma.wordcount);
}
if (mailbox->dma.cur_status != DMA_STATE_READY) {
// failure: abort remaining chunks
dmareq->success = false;
more_chunks = false;
} else if (wordcount_transferred == dmareq->wordcount) {
// last chunk completed
dmareq->success = true;
more_chunks = false;
} else {
// more data to transfer: next chunk.
dmareq->chunk_unibus_start_addr = mailbox->dma.cur_addr + 2;
// dmarequest remains prl->active and ->busy
// signal to DMA() or INTR()
cpu_data_transfer_request->complete = true; // close to signal
pthread_mutex_unlock(&cpu_data_transfer_request->complete_mutex);
// re-activate this request, or choose another with higher slot priority,
// inserted in parallel (interrupt this DMA)
prl->active = NULL;
request_activate_lowest_slot(PRIORITY_LEVEL_INDEX_NPR);
request_execute_active_on_PRU(PRIORITY_LEVEL_INDEX_NPR);
more_chunks = true;
// DEBUG("DMA chunk: %s @ %06o..%06o, wordcount %d, data=%06o, %06o, ... %s",
// unibus->control2text(mailbox->dma.control), mailbox->dma.startaddr,
// mailbox->dma.cur_addr, mailbox->dma.wordcount, mailbox->dma.words[0],
// mailbox->dma.words[1], dmareq->success ? "OK" : "TIMEOUT");
}
if (!more_chunks) {
DEBUG("DMA ready: %s @ %06o..%06o, wordcount %d, data=%06o, %06o, ... %s",
unibus->control2text(dmareq->unibus_control), dmareq->unibus_start_addr,
dmareq->unibus_end_addr, dmareq->wordcount, dmareq->buffer[0],
dmareq->buffer[1], dmareq->success ? "OK" : "TIMEOUT");
// clear from schedule table of this level
request_active_complete(PRIORITY_LEVEL_INDEX_NPR);
// check and execute DMA on other priority_slot
// concurrent to this DATA transfer a device may have requested DMA.
if (request_activate_lowest_slot(PRIORITY_LEVEL_INDEX_NPR))
request_execute_active_on_PRU(PRIORITY_LEVEL_INDEX_NPR);
} else {
priority_request_level_c *prl = &request_levels[PRIORITY_LEVEL_INDEX_NPR];
bool more_chunks;
// Must run under pthread_mutex_lock(&requests_mutex) ;
dma_request_c *dmareq = dynamic_cast<dma_request_c *>(prl->active);
assert(dmareq != NULL);
dmareq->unibus_end_addr = mailbox->dma.cur_addr; // track emnd of trasnmission, eror position
unsigned wordcount_transferred = dmareq->wordcount_completed_chunks()
+ mailbox->dma.wordcount;
assert(wordcount_transferred <= dmareq->wordcount);
if (mailbox->dma.control == UNIBUS_CONTROL_DATI) {
// guard against buffer overrun
// PRU read chunk data from UNIBUS into mailbox
// copy result cur_DMA_wordcount from mailbox->DMA buffer to cur_DMA_buffer
memcpy(dmareq->chunk_buffer_start(), (void *) mailbox->dma.words,
2 * mailbox->dma.wordcount);
}
if (mailbox->dma.cur_status != DMA_STATE_READY) {
// failure: abort remaining chunks
dmareq->success = false;
more_chunks = false;
} else if (wordcount_transferred == dmareq->wordcount) {
// last chunk completed
dmareq->success = true;
more_chunks = false;
} else {
// more data to transfer: next chunk.
dmareq->chunk_unibus_start_addr = mailbox->dma.cur_addr + 2;
// dmarequest remains prl->active and ->busy
// re-activate this request, or choose another with higher slot priority,
// inserted in parallel (interrupt this DMA)
prl->active = NULL;
request_activate_lowest_slot(PRIORITY_LEVEL_INDEX_NPR);
request_execute_active_on_PRU(PRIORITY_LEVEL_INDEX_NPR);
more_chunks = true;
// DEBUG("DMA chunk: %s @ %06o..%06o, wordcount %d, data=%06o, %06o, ... %s",
// unibus->control2text(mailbox->dma.control), mailbox->dma.startaddr,
// mailbox->dma.cur_addr, mailbox->dma.wordcount, mailbox->dma.words[0],
// mailbox->dma.words[1], dmareq->success ? "OK" : "TIMEOUT");
}
if (!more_chunks) {
DEBUG("DMA ready: %s @ %06o..%06o, wordcount %d, data=%06o, %06o, ... %s",
unibus->control2text(dmareq->unibus_control), dmareq->unibus_start_addr,
dmareq->unibus_end_addr, dmareq->wordcount, dmareq->buffer[0],
dmareq->buffer[1], dmareq->success ? "OK" : "TIMEOUT");
// clear from schedule table of this level
request_active_complete(PRIORITY_LEVEL_INDEX_NPR);
// check and execute DMA on other priority_slot
if (request_activate_lowest_slot(PRIORITY_LEVEL_INDEX_NPR))
request_execute_active_on_PRU(PRIORITY_LEVEL_INDEX_NPR);
}
}
}
@@ -866,9 +935,9 @@ void unibusadapter_c::worker_intr_complete_event(uint8_t level_index) {
// Must run under pthread_mutex_lock(&requests_mutex) ;
priority_request_level_c *prl = &request_levels[level_index];
// if 1.st opcode of an ISR is a clear of INTR condition,
// cancle_INTR() may be called in worker_deviceregsiter_event()
// then the request is aloready remoced ferom schedule table.
// if 1st opcode of an ISR is a "clear of INTR" condition,
// cancel_INTR() may be called in worker_deviceregister_event()
// then the request is already removed from schedule table.
//assert(prl->active);
// clear from schedule table of this level
@@ -986,7 +1055,7 @@ void unibusadapter_c::worker(unsigned instance) {
if (mailbox->events.event_dma) {
any_event = true;
pthread_mutex_lock(&requests_mutex);
worker_dma_chunk_complete_event();
worker_dma_chunk_complete_event(mailbox->events.event_dma_cpu_transfer);
pthread_mutex_unlock(&requests_mutex);
mailbox->events.event_dma = 0; // PRU may re-raise and change mailbox now
}

View File

@@ -53,15 +53,18 @@ public:
class unibusadapter_c: public device_c {
private:
// handle arbitration for each of the 5 request levels in parallel
// handle arbitration for each of the 5 device request levels in parallel
priority_request_level_c request_levels[PRIORITY_LEVEL_COUNT];
// access of master CPU to memory not handled via priority arbitration
dma_request_c *cpu_data_transfer_request ; // needs no link to CPU
pthread_mutex_t requests_mutex;
void worker_init_event(void);
void worker_power_event(void);
void worker_deviceregister_event(void);
void worker_dma_chunk_complete_event(void);
void worker_dma_chunk_complete_event(bool cpu_DATA_transfer);
void worker_intr_complete_event(uint8_t level_index);
void worker(unsigned instance) override; // background worker function
@@ -99,8 +102,9 @@ public:
uint32_t unibus_addr, uint16_t *buffer, uint32_t wordcount);
void INTR(intr_request_c& intr_request, unibusdevice_register_t *interrupt_register,
uint16_t interrupt_register_value);
void cancel_INTR(intr_request_c& intr_request) ;
void cancel_INTR(intr_request_c& intr_request);
void cpu_DATA_transfer(dma_request_c& dma_request, uint8_t unibus_control, uint32_t unibus_addr, uint16_t *buffer);
void print_shared_register_map(void);

View File

@@ -30,7 +30,7 @@
from d:\RetroCmp\dec\pdp11\UniBone\91_3rd_party\pru-c-compile\pru-software-support-package\examples\am335x\PRU_gpioToggle
Test GPIO, shared mem and interrupt
a) waits until ARM writes a value to mailbox.arm2pru_req
b) ACKs the value in mailbox.arm2pru_resp, clears arm2pru_req
b) ACKs with clear of arm2pru_req
c) toggles 1 mio times GPIO, with delay as set by ARM
d) signal EVENT0
e) goto a
@@ -82,12 +82,6 @@ void main(void) {
while (1) {
// display opcode (active for one cycle
// __R30 = (mailbox.arm2pru_req & 0xf) << 8;
/*
mailbox.arm2pru_resp = mailbox.arm2pru_req ;
__R30 = (mailbox.arm2pru_resp & 0xf) << 8;
mailbox.arm2pru_resp = mailbox.arm2pru_req ;
}
*/
/*** Attention: arm2pru_req (and all mailbox vars) change at ANY TIME
* - ARM must set arm2pru_req as last operation on mailbox,
* memory barrier needed.

View File

@@ -36,7 +36,7 @@
from d:\RetroCmp\dec\pdp11\UniBone\91_3rd_party\pru-c-compile\pru-software-support-package\examples\am335x\PRU_gpioToggle
Test GPIO, shared mem and interrupt
a) waits until ARM writes a value to mailbox.arm2pru_req
b) ACKs the value in mailbox.arm2pru_resp, clears arm2pru_req
b) ACKs with clear of arm2pru_req
c) toggles 1 mio times GPIO, with delay as set by ARM
d) signal EVENT0
e) goto a
@@ -126,7 +126,8 @@ void main(void) {
// fast: a complete slave data cycle
if (!sm_data_slave_state)
sm_data_slave_state = (statemachine_state_func) &sm_slave_start;
while ((sm_data_slave_state = sm_data_slave_state()) && !mailbox.events.event_deviceregister)
while ((sm_data_slave_state = sm_data_slave_state())
&& !mailbox.events.event_deviceregister)
// throws signals to ARM,
// Acess to interna lregsitres may may issue AMR2PRU opcode, so exit loop then
;// execute complete slave cycle, then check NPR/INTR
@@ -196,19 +197,32 @@ void main(void) {
mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
break;
case ARM2PRU_DMA:
// request INTR, arbitrator must've been selected with ARM2PRU_ARB_MODE_*
sm_arb.request_mask |= PRIORITY_ARBITRATION_BIT_NP;
// sm_arb_worker() evaluates this,extern Arbitrator raises Grant, excution starts in future loop
// end of DMA is signaled to ARM with signal
if (mailbox.arbitrator.cpu_BBSY) {
// ARM CPU emulation did a single word DATA transfer with cpu_DATA_transfer()
if (mailbox.arbitrator.device_BBSY) {
// ARM started cpu DATA transfer, but arbitration logic
// GRANTed a device request in the mean time. Tell ARM.
mailbox.arm2pru_req = PRU2ARM_DMA_CPU_TRANSFER_BLOCKED; // error
mailbox.arbitrator.cpu_BBSY = false;
}
// start bus cycle
sm_data_master_state = (statemachine_state_func) &sm_dma_start;
} else {
// Emulated device: raise request for emulated or physical Arbitrator.
// request DMA, arbitrator must've been selected with ARM2PRU_ARB_MODE_*
sm_arb.request_mask |= PRIORITY_ARBITRATION_BIT_NP;
// sm_arb_worker() evaluates this,extern Arbitrator raises Grant, excution starts in future loop
// end of DMA is signaled to ARM with signal
/* TODO: speed up DMA
While DMA is active:
- SACK active: no GRANT forward necessary
no arbitration necessary
- INIT is monitored: no DC_LO/INIT monitoring necessary
- no scan for new ARM2PRU commands: ARM2PRU_DMA is blocking
- smaller chunks ?
*/
/* TODO: speed up DMA
While DMA is active:
- SACK active: no GRANT forward necessary
no arbitration necessary
- INIT is monitored: no DC_LO/INIT monitoring necessary
- no scan for new ARM2PRU commands: ARM2PRU_DMA is blocking
- smaller chunks ?
*/
}
mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
break;
case ARM2PRU_INTR:
@@ -231,10 +245,10 @@ void main(void) {
case ARM2PRU_INTR_CANCEL:
// cancels an INTR request. If already Granted, the GRANT is forwarded,
// and canceled by reaching a "SACK turnaround terminator" or "No SACK TIMEOUT" in the arbitrator.
sm_arb.request_mask &= ~ mailbox.intr.priority_arbitration_bit ;
sm_arb.request_mask &= ~mailbox.intr.priority_arbitration_bit;
// no completion event, could interfer with othe INTRs?
mailbox.arm2pru_req = ARM2PRU_NONE; // done
break ;
break;
case ARM2PRU_INITPULSE:
if (!sm_init_state)
sm_init_state = (statemachine_state_func) &sm_init_start;
@@ -247,8 +261,8 @@ void main(void) {
break;
case ARM2PRU_HALT:
mailbox.arm2pru_req = ARM2PRU_NONE; // ACK: done
__halt() ; // LA: trigger on timeout of REG_WRITE
break ;
__halt(); // LA: trigger on timeout of REG_WRITE
break;
}
}

View File

@@ -59,7 +59,7 @@
if PRU detects BGIN which was requests, it "blocks the GRANT" )sets SACK and
transmit the INT (BG*) or becomes
"no interrupt request while NPR transfer active!"
Meaning: bus masterchip acquired by NPG may not be used to transmit an
Meaning: bus mastership acquired by NPG may not be used to transmit an
INTR vector.
Device may take bus if BBSY==0 && SSYN==0 && NPG==0
@@ -68,8 +68,8 @@
BBSY is set before SACK is released. SACK is relased imemdiatley after BBSY,
enabling next arbitration in parallel to curretn data transfer
"Only the device with helds SACk asserted can assert BBSY
Several arbitration "workers" which set request, monitor or generate GRANT signals
and allocate SACK.
Which worker to use depends on wether a physical PDP-11 CPU is Arbitrator,
@@ -85,20 +85,24 @@
#include "pru1_utils.h"
#include "mailbox.h"
#include "pru1_timeouts.h"
#include "pru1_buslatches.h"
#include "pru1_statemachine_arbitration.h"
#include "pru1_statemachine_arbitration.h"
statemachine_arbitration_t sm_arb;
/********** NPR/NPG/SACK arbitrations **************/
// to be called on INIT signal: abort the abritration rpcoess
// to be called on INIT signal: abort the arbitration process
void sm_arb_reset() {
// cleanup: clear all REQUESTS and SACK
buslatches_setbits(1, PRIORITY_ARBITRATION_BIT_MASK | BIT(5), 0);
sm_arb.request_mask = 0;
sm_arb.bbsy_wait_grant_mask = 0;
sm_arb.arbitrator_grant_mask = 0;
timeout_cleanup(TIMEOUT_SACK);
}
/* sm_arb_workers_*()
@@ -114,7 +118,8 @@ void sm_arb_reset() {
uint8_t sm_arb_worker_none() {
// Unconditionally forward GRANT IN to GRANT OUT
uint8_t grant_mask = buslatches_getbyte(0) & PRIORITY_ARBITRATION_BIT_MASK; // read GRANT IN
buslatches_setbits(0, PRIORITY_ARBITRATION_BIT_MASK, ~grant_mask) ;
buslatches_setbits(0, PRIORITY_ARBITRATION_BIT_MASK, ~grant_mask)
;
// ignore BR* INTR requests, only ack DMA.
if (sm_arb.request_mask & PRIORITY_ARBITRATION_BIT_NP) {
@@ -124,10 +129,10 @@ uint8_t sm_arb_worker_none() {
return 0;
}
/* worker_client:
/* worker_client:
Issue request to extern Arbitrator (PDP-11 CPU).
Watch for GRANTs on the bus signal lines, then raise SACK.
Wait for current bus master to release bus => Wait for BBSY clear.
Wait for current bus master to release bus => Wait for BBSY clear.
Then return GRANTed request.
"Wait for BBSY clear" may not be part of the arbitration protocol.
But it guarantees caller may now issue an DMA or INTR.
@@ -140,7 +145,7 @@ uint8_t sm_arb_worker_client() {
;
// read GRANT IN lines from CPU (Arbitrator). Only one at a time may be active
// Arbitrator asserts SACK is inactive
// latch[0]: BG signals are inverted, "get" is non-inverting: bit = active signal.
// latch[0]: BG signals are inverted, "get" is non-inverting: bit = active signal.
// "set" is inverting!
grant_mask = buslatches_getbyte(0) & PRIORITY_ARBITRATION_BIT_MASK; // read GRANT IN
// forward un-requested GRANT IN to GRANT OUT for other cards on neighbor slots
@@ -148,27 +153,17 @@ uint8_t sm_arb_worker_client() {
;
if (sm_arb.bbsy_wait_grant_mask == 0) {
// State 1: Wait For GRANT:
// State 1: Wait For GRANT:
// process the requested grant for an open requests.
grant_mask &= sm_arb.request_mask;
if (grant_mask) {
// one of our requests was granted:
// one of our requests was granted:
// set SACK
// AND simultaneously Clear granted requests BR*/NPR
// BIT(5): SACK mask and level
buslatches_setbits(1, (PRIORITY_ARBITRATION_BIT_MASK & sm_arb.request_mask) | BIT(5),
~grant_mask | BIT(5))
buslatches_setbits(1, (PRIORITY_ARBITRATION_BIT_MASK & sm_arb.request_mask) | BIT(5),
~grant_mask | BIT(5))
;
/*
buslatches_setbits(1, BIT(5), BIT(5));
// Arbitrator now clears GRANT IN
// clear granted requests BR* / NPR on UNIBUS
// "~": disable BR for set BG
buslatches_setbits(1, PRIORITY_ARBITRATION_BIT_MASK & sm_arb.request_mask, ~grant_mask)
;
*/
// clear granted requests internally
sm_arb.request_mask &= ~grant_mask;
@@ -190,13 +185,117 @@ uint8_t sm_arb_worker_client() {
}
/* "worker_master"
Act as Arbitrator.
Act as Arbitrator, Interrupt Fielding Processor and Client
Is assumed to be on first slot, so BG*IN/NPGIN lines are ignored
BR/NPR are set in device request, as in worker_client()
Grant highest of requests, if SACK deasserted.
Execute UNIBUS priority algorithm:
- Grant DMA request, if present
- GRANT BR* in descending priority, when CPU execution level allows .
- Cancel GRANT, if no device responds with SACK within timeout period
*/
// decode set of requests lines to highest INTR level
// BR4 = 0x01 -> 4, BR5 = 0x02 -> 5, etc.
// Index only PRIORITY_ARBITRATION_INTR_MASK, [0] invalid.
static const uint8_t requests_2_highests_intr[16] = { //
/*0000*/9, /*0001*/4, /*0010*/5, /*0011*/5,
/*0100*/6, /*0101*/6, /*0110*/6, /*0111*/6,
/*1000*/7, /*1001*/7, /*1010*/7, /*0011*/7,
/*1100*/7, /*1101*/7, /*1110*/7, /*0111*/7 };
uint8_t sm_arb_worker_master() {
return 0;
/******* arbitrator logic *********/
uint8_t intr_request_mask;
uint8_t latch1val = buslatches_getbyte(1);
if (latch1val & BIT(5)) {
// SACK set by a device
// priority arbitration disabled, remove GRANT.
sm_arb.arbitrator_grant_mask = 0;
// CPU looses now access to UNIBUS after current cycle
mailbox.arbitrator.device_BBSY = 1; // DATA section used by device now
timeout_cleanup(TIMEOUT_SACK);
} else if (latch1val & PRIORITY_ARBITRATION_BIT_NP) {
// device NPR
PRU_DEBUG_PIN_PULSE_100NS ;
if (sm_arb.arbitrator_grant_mask == 0) {
PRU_DEBUG_PIN_PULSE_100NS ;
// no 2nd device's request may modify GRANT before 1st device acks with SACK
sm_arb.arbitrator_grant_mask = PRIORITY_ARBITRATION_BIT_NP;
timeout_set(TIMEOUT_SACK, MILLISECS(ARB_MASTER_SACK_TIMOUT_MS));
}
} else if ((intr_request_mask = latch1val & PRIORITY_ARBITRATION_INTR_MASK)) {
// device BR4,BR5,BR6 or BR7
if (sm_arb.arbitrator_grant_mask == 0) {
// no 2nd device's request may modify GRANT before 1st device acks with SACK
// GRANT request depending on CPU priority level
// find highest request line
uint8_t requested_intr_level = requests_2_highests_intr[intr_request_mask];
// compare against cpu run level 4..7
if (requested_intr_level > mailbox.arbitrator.cpu_priority_level) {
// GRANT request, set GRANT line:
// BG4 is signal bit maskl 0x01, etc ...
sm_arb.arbitrator_grant_mask = BIT(requested_intr_level - 4);
timeout_set(TIMEOUT_SACK, MILLISECS(ARB_MASTER_SACK_TIMOUT_MS));
}
}
} else if (sm_arb.arbitrator_grant_mask && timeout_reached(TIMEOUT_SACK)) {
// no SACK, no requests, but GRANTs: SACK timeout?
sm_arb.arbitrator_grant_mask = 0;
mailbox.arbitrator.device_BBSY = 0; // started by SACK, but UNIBUS DATA section not used by device
timeout_cleanup(TIMEOUT_SACK);
}
/***** client device logic *****/
// Always update UNIBUS BR/NPR lines, are ORed with requests from other devices.
buslatches_setbits(1, PRIORITY_ARBITRATION_BIT_MASK, sm_arb.request_mask)
;
// read GRANT IN lines from CPU (Arbitrator). Only one at a time may be active
// Arbitrator asserts SACK is inactive
uint8_t grant_mask = sm_arb.arbitrator_grant_mask;
// forward GRANTs not requested by UniBone devices to other cards on neighbor slots
buslatches_setbits(0, PRIORITY_ARBITRATION_BIT_MASK & ~sm_arb.request_mask, ~grant_mask)
;
// GRANTs for UniBone internal devices are not visible on UNIBUS
// (emualted GRANT OUT - GRANT IN connections)
if (sm_arb.bbsy_wait_grant_mask == 0) {
// State 1: Wait For GRANT:
// process the requested grant for an open requests.
grant_mask &= sm_arb.request_mask;
if (grant_mask) {
// one of our requests was granted:
// set SACK
// AND simultaneously Clear granted requests BR*/NPR
// BIT(5): SACK mask and level
buslatches_setbits(1, (PRIORITY_ARBITRATION_BIT_MASK & sm_arb.request_mask) | BIT(5),
~grant_mask | BIT(5))
;
// clear granted requests internally
sm_arb.request_mask &= ~grant_mask;
// Arbitrator should remove GRANT now. Data section on Bus still BBSY
sm_arb.bbsy_wait_grant_mask = grant_mask; // next is State 2: wait for BBSY clear
}
return 0; // no GRANT for us, or wait for BBSY
} else {
// wait for BBSY to clear
if (buslatches_getbyte(1) & BIT(6))
return 0; // BBSY still set
grant_mask = sm_arb.bbsy_wait_grant_mask;
sm_arb.bbsy_wait_grant_mask = 0; // Next State is 1
return grant_mask; // signal what request we got granted.
}
// UNIBUS DATA section is indepedent: MSYN, SSYN, BBSY may still be active.
// -> DMA and INTR statemachine must wait for BBSY.
}

View File

@@ -28,6 +28,10 @@
#include <stdint.h>
// Arbitration master restes GRANT if devcie does not respond with
// SACK within this period.
#define ARB_MASTER_SACK_TIMOUT_MS 10
// a priority-arbitration-worker returns a bit mask with the GRANT signal he recognized
typedef uint8_t (*statemachine_arb_worker_func)();
@@ -43,9 +47,14 @@ typedef struct {
// for BBSY clear.
// 0: not waitong for BBSY.
// != saves GRANTed reqiest and idnicates BBSY wait state
uint8_t bbsy_wait_grant_mask ;
uint8_t bbsy_wait_grant_mask;
/*** master ****/
// only used wif working as Arbitrator/Interupt Fielding Processor
uint8_t ifs_priority_level; // priority level of Interrupt Fielding processor (CPU)
uint8_t arbitrator_grant_mask; // GRANT line set by master
} statemachine_arbitration_t;
/* receives a grant_mask with 1 bit set and returns the index of that bit

View File

@@ -84,6 +84,8 @@ static statemachine_state_func sm_dma_state_99(void);
// startaddr, wordcount, cycle, words[] ?
// "cycle" must be UNIBUS_CONTROL_DATI or UNIBUS_CONTROL_DATO
// Wait for BBSY, SACK already held asserted
// Sorting between device and CPU transfers:
// IF a device DMA is busy mailbox.device_BBSY is set.
statemachine_state_func sm_dma_start() {
// assert BBSY: latch[1], bit 6
// buslatches_setbits(1, BIT(6), BIT(6));
@@ -100,15 +102,14 @@ statemachine_state_func sm_dma_start() {
}
/*
// wait for BBSY deasserted, then assert
static statemachine_state_func sm_dma_state_1() {
if (buslatches_getbyte(1) & BIT(6))
return (statemachine_state_func) &sm_dma_state_1; // wait
buslatches_setbits(1, BIT(6), BIT(6)); // assert BBSY
return (statemachine_state_func) &sm_dma_state_1;
}
*/
// wait for BBSY deasserted, then assert
static statemachine_state_func sm_dma_state_1() {
if (buslatches_getbyte(1) & BIT(6))
return (statemachine_state_func) &sm_dma_state_1; // wait
buslatches_setbits(1, BIT(6), BIT(6)); // assert BBSY
return (statemachine_state_func) &sm_dma_state_1;
}
*/
// place address and control bits onto bus, also data for DATO
// If slave address is internal (= implemented by UniBone),
@@ -329,16 +330,21 @@ static statemachine_state_func sm_dma_state_99() {
buslatches_setbits(4, 0x3f, 0);
// remove BBSY: latch[1], bit 6
buslatches_setbits(1, BIT(6), 0);
// SACK already de-asserted at wordcount==1
timeout_cleanup(TIMEOUT_DMA);
mailbox.dma.cur_status = final_dma_state; // signal to ARM
timeout_cleanup(TIMEOUT_DMA);
timeout_cleanup(TIMEOUT_DMA);
// device or cpu cycle ended: now CPU may become UNIBUS master again
mailbox.events.event_dma_cpu_transfer = mailbox.arbitrator.cpu_BBSY ;
mailbox.arbitrator.device_BBSY = false;
mailbox.arbitrator.cpu_BBSY = false;
// SACK already de-asserted at wordcount==1
mailbox.dma.cur_status = final_dma_state; // signal to ARM
// signal to ARM
mailbox.events.event_dma = 1;
// ARM is clearing this, before requesting new DMA.
// no concurrent ARP+PRU access
// no concurrent ARM+PRU access
PRU2ARM_INTERRUPT
;

View File

@@ -33,6 +33,7 @@
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
//#include "devices.h"
#include "mailbox.h"
@@ -97,6 +98,9 @@ static statemachine_state_func sm_intr_state_2() {
// deassert BBSY
buslatches_setbits(1, BIT(6), 0);
// device cycle ended: now CPU may become UNIBUS master again
mailbox.arbitrator.device_BBSY = false ;
// SACK already removed
// signal to ARM which INTR was completed

View File

@@ -79,7 +79,7 @@ void timeout_set(uint32_t *target_cycles_var, uint32_t delta_cycles) {
timeouts_active++; // now one more active
}
// msut be called, if timeout polled anymore for "timeout-reached()
// must be called, if timeout not polled anymore for "timeout-reached()
void timeout_cleanup(uint32_t *target_cycles_var) {
if (*target_cycles_var > 0) {
*target_cycles_var = 0;

View File

@@ -29,12 +29,13 @@
#include <stdbool.h>
// predefined timeouts
#define TIMEOUT_COUNT 3
#define TIMEOUT_COUNT 4
// fixed pointers
#define TIMEOUT_DMA (&timeout_target_cycles[0])
#define TIMEOUT_INIT (&timeout_target_cycles[1])
#define TIMEOUT_POWERCYCLE (&timeout_target_cycles[2])
#define TIMEOUT_SACK (&timeout_target_cycles[3])
// cycle end count for each active timeoput.
extern uint32_t timeout_target_cycles[TIMEOUT_COUNT];

View File

@@ -46,10 +46,11 @@
#define ARM2PRU_ARB_MODE_CLIENT 12 // DMA with arbitration by external Arbitrator
#define ARM2PRU_ARB_MODE_MASTER 13 // DMA as Arbitrator
#define ARM2PRU_DMA 14 // DMA with selcted arbitration
#define ARM2PRU_INTR 15 // INTR with arbitration by external Arbitrator
#define ARM2PRU_INTR_CANCEL 16 // clear INTR which has been requested
#define ARM2PRU_DDR_FILL_PATTERN 17 // fill DDR with test pattern
#define ARM2PRU_DDR_SLAVE_MEMORY 18 // use DDR as UNIBUS slave memory
#define PRU2ARM_DMA_CPU_TRANSFER_BLOCKED 15 // possible result of ARM2PRU_DMA
#define ARM2PRU_INTR 16 // INTR with arbitration by external Arbitrator
#define ARM2PRU_INTR_CANCEL 17 // clear INTR which has been requested
#define ARM2PRU_DDR_FILL_PATTERN 18 // fill DDR with test pattern
#define ARM2PRU_DDR_SLAVE_MEMORY 19 // use DDR as UNIBUS slave memory
// possible states of DMA machine
#define DMA_STATE_READY 0 // idle
@@ -106,10 +107,30 @@ typedef struct {
uint8_t data_8_15;
} mailbox_buslatch_test_t;
// data for bus arbitrator
typedef struct {
// arbitrator.device_BBSY indicates a device wants or has acquired the UNIBUS
// cpu DATA transfer must be delayed until == 00
// set by arbitration logic
uint8_t device_BBSY ;
// Command by ARM on DMA start: DATA transfer as CPU, else as device
uint8_t cpu_BBSY ;
uint8_t cpu_priority_level ; // Priority level of CPU, visible in PSW. 7,6,5,4 <4.
uint8_t _dummy1 ; // keep 32 bit borders
} mailbox_arbitrator_t ;
// data for a requested DMA operation
typedef struct {
// take care of 32 bit word borders for struct members
uint8_t cur_status; // 0 = idle, 1 = running, 2 = timeout error
uint8_t cur_status; // 0 = idle, 1 = DMA running, 2 = timeout error
// 0x80: set on start to indicate CPU access
uint8_t control; // cycle to perform: only DATO, DATI allowed
uint16_t wordcount; // # of remaining words transmit/receive, static
// ---dword---
@@ -167,12 +188,12 @@ typedef struct {
After that, mailbox_dma_t is updated and signal raised.
*/
uint8_t event_dma; // trigger flag
uint8_t event_dma_cpu_transfer ; // 1: ARM must process DMa as compelted cpu DATA transfer
/*** Event priority arbitration data transfer complete
After ARM2PRU_INTR, one of BR4/5/6/7 NP was requested,
granted, and the data transfer was handled as bus master.
*/
uint8_t _dummy1;
// ---dword---
uint8_t event_intr; // trigger flag: 1 = one of BR4,5,6,7 vector on UNIBUS
uint8_t event_intr_level_index; // 0..3 -> BR4..BR7
@@ -193,11 +214,13 @@ typedef struct {
// generic request/response flags
uint32_t arm2pru_req;
uint32_t arm2pru_resp;
// physical location of shared DDR memory. PDP-11 memory words.
volatile ddrmem_t *ddrmem_base_physical;
mailbox_arbitrator_t arbitrator;
// set by PRU, read by ARM on event
mailbox_events_t events;
@@ -230,7 +253,7 @@ extern volatile mailbox_t *mailbox;
void mailbox_print(void);
int mailbox_connect(void);
void mailbox_test1(void);
void mailbox_execute(uint8_t request);
bool mailbox_execute(uint8_t request);
#else
// included by PRU code

View File

@@ -51,9 +51,7 @@ cpu_c::cpu_c() :
default_base_addr = 0; // none
default_intr_vector = 0;
default_intr_level = 0;
priority_slot.value = 1 ;
dma_request.set_priority_slot(priority_slot.value);
priority_slot.value = 0; // not used
// init parameters
runmode.value = false;
@@ -91,24 +89,25 @@ extern "C" {
// Result: 1 = OK, 0 = bus timeout
int cpu_dato(unsigned addr, unsigned data) {
uint16_t wordbuffer = (uint16_t) data;
unibusadapter->DMA(the_cpu->dma_request, true, UNIBUS_CONTROL_DATO, addr, &wordbuffer, 1);
return the_cpu->dma_request.success;
unibusadapter->cpu_DATA_transfer(the_cpu->data_transfer_request, UNIBUS_CONTROL_DATO, addr, &wordbuffer);
return the_cpu->data_transfer_request.success ;
}
int cpu_datob(unsigned addr, unsigned data) {
uint16_t wordbuffer = (uint16_t) data;
// TODO DATOB als 1 byte-DMA !
unibusadapter->DMA(the_cpu->dma_request, true, UNIBUS_CONTROL_DATOB, addr, &wordbuffer, 1);
return the_cpu->dma_request.success;
unibusadapter->cpu_DATA_transfer(the_cpu->data_transfer_request, UNIBUS_CONTROL_DATOB, addr, &wordbuffer);
return the_cpu->data_transfer_request.success ;
}
int cpu_dati(unsigned addr, unsigned *data) {
uint16_t wordbuffer;
unibusadapter->DMA(the_cpu->dma_request, true, UNIBUS_CONTROL_DATI, addr, &wordbuffer, 1);
unibusadapter->cpu_DATA_transfer(the_cpu->data_transfer_request, UNIBUS_CONTROL_DATI, addr, &wordbuffer);
*data = wordbuffer;
// printf("DATI; ba=%o, data=%o\n", addr, *data) ;
return the_cpu->dma_request.success;
return the_cpu->data_transfer_request.success ;
}
}

View File

@@ -29,6 +29,7 @@
using namespace std;
#include "utils.hpp"
#include "unibusadapter.hpp"
#include "unibusdevice.hpp"
extern "C" {
#include "cpu20/11.h"
@@ -46,8 +47,8 @@ public:
cpu_c();
~cpu_c();
// CPU accesses memory actively
dma_request_c dma_request = dma_request_c(this);
// used for DATI/DATO, operated by unibusadapter
dma_request_c data_transfer_request = dma_request_c(this);
bool on_param_changed(parameter_c *param) override; // must implement

View File

@@ -24,7 +24,7 @@ p emulation_speed 10 # 10x speed. Load disk in 5 seconds
# set type to "rl02"
p runstopbutton 0 # released: "LOAD"
p powerswitch 1 # power on, now in "load" state
p image scratch2.rl02 # mount image file with test pattern
p image scratch0.rl02 # mount image file with test pattern
p runstopbutton 1 # press RUN/STOP, will start
#.end

View File

@@ -122,10 +122,10 @@ void application_c::menu_devices(bool with_CPU) {
char *s_choice;
char s_opcode[256], s_param[3][256];
// with_CPU: the emulated CPU is answering BR and NPR requests.
// with_CPU: PRU Arbitrator is answering BR and NPR requests.
if (with_CPU)
arbitration_mode = unibus_c::ARBITRATION_MODE_NONE;
// arbitration_mode = unibus_c::ARBITRATION_MODE_MASTER;
arbitration_mode = unibus_c::ARBITRATION_MODE_MASTER;
// arbitration_mode = unibus_c::ARBITRATION_MODE_NONE;
else
arbitration_mode = unibus_c::ARBITRATION_MODE_CLIENT;
@@ -138,6 +138,10 @@ void application_c::menu_devices(bool with_CPU) {
// now PRU executing UNIBUS master/slave code, physical PDP-11 CPU as arbitrator required.
buslatches_output_enable(true);
// without PDP-11 CPU no INIT after power ON was generated.
// Devices may trash the bus lines.
unibus->init();
unibusadapter->enabled.set(true);
// 2 demo controller
@@ -229,9 +233,9 @@ void application_c::menu_devices(bool with_CPU) {
printf("d <regname> <val> Deposit octal value into named device register\n");
printf("e <regname> Examine single device register (regno decimal)\n");
printf("e Examine all device registers\n");
printf("e <addr> Examine octal UNIBUS address.\n");
printf("d <addr> <val> Deposit octal val into UNIBUS address.\n");
}
printf("e <addr> Examine octal UNIBUS address.\n");
printf("d <addr> <val> Deposit octal val into UNIBUS address.\n");
if (DL11->enabled.value) {
printf(
"dl11 rcv [<wait_ms>] <string> inject characters as if DL11 received them.\n");
@@ -413,10 +417,12 @@ void application_c::menu_devices(bool with_CPU) {
p->parse(sval);
print_params(cur_device, p);
}
} else if (unibuscontroller && !strcasecmp(s_opcode, "d") && n_fields == 3) {
} else if (!strcasecmp(s_opcode, "d") && n_fields == 3) {
uint32_t addr;
uint16_t wordbuffer;
unibusdevice_register_t *reg = unibuscontroller->register_by_name(s_param[0]);
unibusdevice_register_t *reg = NULL ;
if (unibuscontroller)
reg = unibuscontroller->register_by_name(s_param[0]);
if (reg) // register name given
addr = reg->addr;
else
@@ -437,14 +443,14 @@ void application_c::menu_devices(bool with_CPU) {
printf("DEPOSIT %06o <- %06o\n", addr, wordbuffer);
if (timeout)
printf("Bus timeout at %06o.\n", mailbox->dma.cur_addr);
} else if (unibuscontroller && !strcasecmp(s_opcode, "e") && n_fields <= 2) {
} else if (!strcasecmp(s_opcode, "e") && n_fields <= 2) {
bool timeout;
uint32_t addr;
unibusdevice_register_t *reg;
if (n_fields == 2) { // single reg number given
unibusdevice_register_t *reg = NULL ;
if (n_fields == 2) { // single reg number or address given
uint16_t wordbuffer; // exam single word
reg = unibuscontroller->register_by_name(s_param[0]);
if (unibuscontroller)
reg = unibuscontroller->register_by_name(s_param[0]);
if (reg)
addr = reg->addr;
else
@@ -452,7 +458,7 @@ void application_c::menu_devices(bool with_CPU) {
timeout = !unibus->dma(arbitration_mode, true, UNIBUS_CONTROL_DATI, addr,
&wordbuffer, 1);
printf("EXAM %06o -> %06o\n", addr, wordbuffer);
} else { // list all regs
} else if (n_fields == 1 && unibuscontroller) { // list all regs
unsigned wordcount = 0; // default: no EXAM
uint16_t wordbuffer[MAX_REGISTERS_PER_DEVICE];
addr = unibuscontroller->base_addr.value; // all device registers
@@ -471,6 +477,9 @@ void application_c::menu_devices(bool with_CPU) {
timeout = false;
printf("Device has no UNIBUS registers.\n");
}
} else {
// no device: no "display all"
show_help = true;
}
if (timeout)
printf("Bus timeout at %06o.\n", mailbox->dma.cur_addr);