I'm currently planning on not providing any built-in CSRs to the Polaris core. At all. Rather, I'm planning on providing access to CSRs via an external bus. This allows Polaris to focus exclusively on its core competency (instruction execution).
The CSR bus would look something like this sketch:
- CADR_O[11:0] -- CSR address bus. This reflects bits 31:20 of the current CPU instruction.
- CV_I -- CSR Valid. True iff the address on the CADR_O bus maps to a supported CSR register. If Polaris executes any CSR-instruction and CV_I is false, then an illegal instruction trap happens. Note that CV_I must be valid within the same clock cycle that CADR_O goes valid. That means asynchronous decoding logic.
- CDAT_O[63:0] -- CSR data bus. The value on this bus is to be written to the addressed CSR if CWE_O is asserted in the same cycle.
- CDAT_I[63:0] -- CSR data bus. The value appearing on this bus is the current value of the addressed CSR if COE_O is asserted in the same cycle.
- CWE_O -- Write Enable. Asserted if Rs1/zimm != 0 in the RISC-V CSRR* instruction. This enables write-back to the CSR, and therefore enables any write-triggered behavior associated with the addressed CSR.
- COE_O -- Output Enable. Asserted if Rd != 0 in the RISC-V CSRR* instruction. This enables reading from the CSR, and therefore enables any read-triggered behavior associated with the addressed CSR.
I envision the CSRs will be clocked using the same clock as the Polaris core. Except in the case of CSRRW, one cannot read and write to the CSR in the same clock cycle. So, the Polaris core will need to spread CSR access out over several cycles.
Trap Vectors and Other Configuration Settings
When Polaris gains the capacity for interrupts, how will the value of mtvec be communicated, since mtvec sits in a CSR? In all likelihood, I'll just provide configuration ports on Polaris (e.g., MTVEC_I[63:2]), to which the CSRs feed back into, and which must be valid when any ERR_I or IRQ_I signal is asserted. So, altering mtvec will simply alter a bunch of bits that drive these configuration ports.
Privilege Levels
How will I handle privilege levels going forward? In all likelihood, I won't. Polaris doesn't appear to have or need any privilege-sensitive instructions, and none are currently defined in RV64G as far as I can tell (note that RV64IS is a proper subset of RV64G). Thus, privilege enforcement can be off-loaded to logic external from the Polaris core itself.
In exchange, Polaris core does need to expose control signals which will help outside logic deal with possible privilege-sensitive semantics. For example, the FENCE and FENCE.I instructions both assert a FENCE_O signal on the bus. When a trap occurs (e.g., illegal instruction), TRAP_O asserts, letting external logic know that a transition to machine-mode is required, etc.
This technique is not unproven; prior art exists. The 68010 CPU was built to handle architectural enhancements of this nature. And, of course, the 65816 CPU also provides similar capabilities, allowing one to start with an ultra-basic expanded 6502-like CPU, and to that you can bolt on virtual memory (thanks to the ABORT# signal), different privilege modes (thanks to VP# to indicate when a system call [BRK or COP] or interrupt is being processed), etc. IP built around the 65816 core can provide distinctly non-6502-like semantics; for example the 65265 microcontroller provides a lot more than your standard RESET, IRQ, and NMI interrupt vectors.
Summary
My plans for Polaris might change given the current implementation's flexibility. If this CSR interface works out as I envision, I might dive even further in this direction. Polaris might have a future as a modular RISC-V core, which stands in stark isolation from all the other "kitchen-sink included" cores I see on Github, where configuration is handled with Verilog "`define" settings instead of through module composition.
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.